InnoDB與MyISAM的最大不同有兩點:一是支持事務(TRANSACTION);二是采用了行級鎖。行級鎖與表級鎖本來就有許多不同之處,另外,事務的引入也帶來了一些新問題。下面我們先介紹一點背景知識,然后詳細討論InnoDB的鎖問題。
背景知識
事務(Transaction)及其ACID屬性
事務是由一組SQL語句組成的邏輯處理單元,事務具有以下4個屬性,通常簡稱為事務的ACID屬性。
- 原子性(Atomicity):所謂原子性就是將一組操作作為一個操作單元,是原子操作,即要么全部執行,要么全部不執行。
- 一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態。這意味着所有相關的數據規則都必須應用於事務的修改,以保持數據的完整性;事務結束時,所有的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的。(拿銀行轉賬來說,一致性要求事務的執行不應改變A、B 兩個賬戶的金額總和。如果沒有這種一致性要求,轉賬過程中就會發生錢無中生有,或者不翼而飛的現象。事務應該把數據庫從一個一致性狀態轉換到另外一個一致性狀態。)
- 隔離性(Isolation):隔離性指並發的事務是相互隔離的。即一個事務內部的操作及正在操作的數據必須封鎖起來,不被其它企圖進行修改的事務看到。
- 持久性(Durable):事務完成之后,它對於數據的修改是永久性的,即使出現系統故障也能夠保持。
並發事務處理帶來的問題
相對於串行處理來說,並發事務處理能大大增加數據庫資源的利用率,提高數據庫系統的事務吞吐量,從而可以支持更多的用戶。但並發事務處理也會帶來一些問題,主要包括以下幾種情況。
更新丟失(Lost Update):當兩個或多個事務選擇同一行,然后基於最初選定的值更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題--最后的更 新覆蓋了由其他事務所做的更新。例如,兩個編輯人員制作了同一文檔的電子副本。每個編輯人員獨立地更改其副本,然后保存更改后的副本,這樣就覆蓋了原始文 檔。最后保存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改。如果在一個編輯人員完成並提交事務之前,另一個編輯人員不能訪問同一文件,則可避免此問 題。
臟讀(Dirty Reads):一個事務正在對一條記錄做修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加 控制,第二個事務讀取了這些“臟”數據,並據此做進一步的處理,就會產生未提交的數據依賴關系。這種現象被形象地叫做"臟讀"。
不可重復讀(Non-Repeatable Reads):一個事務在讀取某些數據后的某個時間,再次讀取以前讀過的數據,卻發現其讀出的數據已經發生了改變、或某些記錄已經被刪除了!這種現象就叫做“不可重復讀”。
幻讀(Phantom Reads):一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱為“幻讀”。
2、事務並發會產生問題例子:
1)第一類丟失更新:在沒有事務隔離的情況下,兩個事務都同時更新一行數據,但是第二個事務卻中途失敗退出, 導致對數據的兩個修改都失效了。
例如:
張三的工資為5000,事務A中獲取工資為5000,事務B獲取工資為5000,匯入100,並提交數據庫,工資變為5100,
隨后
事務A發生異常,回滾了,恢復張三的工資為5000,這樣就導致事務B的更新丟失了。
2)臟讀:臟讀就是指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。
例如:
張三的工資為5000,事務A中把他的工資改為8000,但事務A尚未提交。
與此同時,
事務B正在讀取張三的工資,讀取到張三的工資為8000。
隨后,
事務A發生異常,而回滾了事務。張三的工資又回滾為5000。
最后,
事務B讀取到的張三工資為8000的數據即為臟數據,事務B做了一次臟讀。
3)不可重復讀:是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由於第二個事務的修改,那么第一個事務兩次讀到的的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。
例如:
在事務A中,讀取到張三的工資為5000,操作沒有完成,事務還沒提交。
與此同時,
事務B把張三的工資改為8000,並提交了事務。
隨后,
在事務A中,再次讀取張三的工資,此時工資變為8000。在一個事務中前后兩次讀取的結果並不致,導致了不可重復讀。
4)第二類丟失更新:不可重復讀的特例。有兩個並發事務同時讀取同一行數據,然后其中一個對它進行修改提交,而另一個也進行了修改提交。這就會造成第一次寫操作失效。
例如:
在事務A中,讀取到張三的存款為5000,操作沒有完成,事務還沒提交。
與此同時,
事務B,存儲1000,把張三的存款改為6000,並提交了事務。
隨后,
在事務A中,存儲500,把張三的存款改為5500,並提交了事務,這樣事務A的更新覆蓋了事務B的更新。
5)幻讀:是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。
例如:
目前工資為5000的員工有10人,事務A讀取所有工資為5000的人數為10人。
此時,
事務B插入一條工資也為5000的記錄。
這是,事務A再次讀取工資為5000的員工,記錄為11人。此時產生了幻讀。
提醒:
不可重復讀的重點是修改,同樣的條件,你讀取過的數據,再次讀取出來發現值不一樣了
幻讀的重點在於新增或者刪除,同樣的條件,第 1 次和第 2 次讀出來的記錄數不一樣
事務隔離級別
在上面講到的並發事務處理帶來的問題中,“更新丟失”通常是應該完全避免的。但防止更新丟失,並不能單靠數據庫事務控制器來解決,需要應用程序對要更新的數據加必要的鎖來解決,因此,防止更新丟失應該是應用的責任。
“臟讀”、“不可重復讀”和“幻讀”,其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決。數據庫實現事務隔離的方式,基本上可分為以下兩種。
一種是在讀取數據前,對其加鎖,阻止其他事務對數據進行修改。
另一種是不用加任何鎖,通過一定機制生成一個數據請求時間點的一致性數據快照(Snapshot),並用這個快照來提供一定級別(語句級或事務級)的一 致 性讀取。從用戶的角度來看,好像是數據庫可以提供同一數據的多個版本,因此,這種技術叫做數據多版本並發控制(MultiVersion Concurrency Control,簡稱MVCC或MCC),也經常稱為多版本數據庫。
數據庫的事務隔離越嚴格,並發副作用越 小,但付出的代價也就越大,因為事務隔離實質上就是使事務在一定程度上 “串行化”進行,這顯然與“並發”是矛盾的。同時,不同的應用對讀一致性和事務隔離程度的要求也是不同的,比如許多應用對“不可重復讀”和“幻讀”並不敏 感,可能更關心數據並發訪問的能力。
為了解決“隔離”與“並發”的矛盾,ISO/ANSI SQL92定義了4個事務隔離級別,每個級別的隔離程度不同,允許出現的副作用也不同,應用可以根據自己的業務邏輯要求,通過選擇不同的隔離級別來平衡 “隔離”與“並發”的矛盾。表20-5很好地概括了這4個隔離級別的特性。
表20-5 4種隔離級別比較
讀數據一致性及允許的並發副作用 隔離級別 |
讀數據一致性 |
臟讀 |
不可重復讀 |
幻讀 |
未提交讀(Read uncommitted) |
最低級別,只能保證不讀取物理上損壞的數據 |
是 |
是 |
是 |
已提交度(Read committed) |
語句級 |
否 |
是 |
是 |
可重復讀(Repeatable read) |
事務級 |
否 |
否 |
是 |
可序列化(Serializable) |
最高級別,事務級 |
否 |
否 |
否 |
最后要說明的是:各具體數據庫並不一定完全實現了上述4個隔離級別,例如,Oracle只提供Read committed和Serializable兩個標准隔離級別,另外還提供自己定義的Read only隔離級別;SQL Server除支持上述ISO/ANSI SQL92定義的4個隔離級別外,還支持一個叫做“快照”的隔離級別,但嚴格來說它是一個用MVCC實現的Serializable隔離級別。MySQL 支持全部4個隔離級別,但在具體實現時,有一些特點,比如在一些隔離級別下是采用MVCC一致性讀,但某些情況下又不是,這些內容在后面的章節中將會做進 一步介紹。
mysql默認的事務處理級別是'REPEATABLE-READ',也就是可重復讀
mysql> show status like 'innodb_row_lock%'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | Innodb_row_lock_current_waits | 0 | | Innodb_row_lock_time | 0 | | Innodb_row_lock_time_avg | 0 | | Innodb_row_lock_time_max | 0 | | Innodb_row_lock_waits | 0 | +-------------------------------+-------+
如果發現鎖爭用比較嚴重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比較高,還可以通過設置InnoDB Monitors來進一步觀察發生鎖沖突的表、數據行等,並分析鎖爭用的原因。
可以用下面的語句來進行查看:
mysql> Show engine innodb status\G; *************************** 1. row *************************** Type: InnoDB Name: Status: … … ------------ TRANSACTIONS ------------ Trx id counter 0 117472192 Purge done for trx's n:o < 0 117472190 undo n:o < 0 0 History list length 17
在SHOW INNODB STATUS的顯示內容中,會有詳細的當前鎖等待的信息,包括表名、鎖類型、鎖定記錄的情況等,便於進行進一步的分析和問題的確定。打開監視器以后,默認 情況下每15秒會向日志中記錄監控的內容,如果長時間打開會導致.err文件變得非常的巨大,所以用戶在確認問題原因之后,要記得刪除監控表以關閉監視 器,或者通過使用"--console"選項來啟動服務器以關閉寫日志文件。
1.行鎖和表鎖
在mysql 的 InnoDB引擎支持行鎖,與Oracle不同,mysql的行鎖是通過索引加載的,即是行鎖是加在索引響應的行上的,要是對應的SQL語句沒有走索引,則會全表掃描,
行鎖則無法實現,取而代之的是表鎖。
表鎖:不會出現死鎖,發生鎖沖突幾率高,並發低。
行鎖:會出現死鎖,發生鎖沖突幾率低,並發高。
鎖沖突:例如說事務A將某幾行上鎖后,事務B又對其上鎖,鎖不能共存否則會出現鎖沖突。(但是共享鎖可以共存,共享鎖和排它鎖不能共存,排它鎖和排他鎖也不可以)
死鎖:例如說兩個事務,事務A鎖住了1~5行,同時事務B鎖住了6~10行,此時事務A請求鎖住6~10行,就會阻塞直到事務B施放6~10行的鎖,而隨后事務B又請求鎖住1~5行,事務B也阻塞直到事務A釋放1~5行的鎖。死鎖發生時,會產生Deadlock錯誤。
鎖是對表操作的,所以自然鎖住全表的表鎖就不會出現死鎖。
2.行鎖的類型
行鎖分 共享鎖 和 排它鎖。
共享鎖又稱:讀鎖。當一個事務對某幾行上讀鎖時,允許其他事務對這幾行進行讀操作,但不允許其進行寫操作,也不允許其他事務給這幾行上排它鎖,但允許上讀鎖。
排它鎖又稱:寫鎖。當一個事務對某幾個上寫鎖時,不允許其他事務寫,但允許讀。更不允許其他事務給這幾行上任何鎖。包括寫鎖。
上共享鎖的寫法:lock in share mode
例如: select math from zje where math>60 lock in share mode;
上排它鎖的寫法:for update
例如:select math from zje where math >60 for update;
3.行鎖的實現
注意幾點:
1.行鎖必須有索引才能實現,否則會自動鎖全表,那么就不是行鎖了。
2.兩個事務不能鎖同一個索引,例如:
# 事務A先執行: select math from zje where math>60 for update; # 事務B再執行: select math from zje where math<60 for update; # 這樣的話,事務B是會阻塞的。如果事務B把 math索引換成其他索引就不會阻塞, # 但注意,換成其他索引鎖住的行不能和math索引鎖住的行有重復。
3.insert ,delete , update在事務中都會自動默認加上排它鎖。
實現:
會話1: begin; select math from zje where math>60 for update; |
會話2: begin; update zje set math=99 where math=68; 阻塞........... |
會話相當與用戶
如上,會話1先把zje表中math>60的行上排它鎖。然后會話2試圖把math=68的行進行修改,math=68處於math>60中,所以是已經被鎖的,會話2進行操作時,
就會阻塞,等待會話1把鎖釋放。當commit時或者程序結束時,會釋放鎖。
由於InnoDB預設是Row-Level Lock,所以只有「明確」的指定主鍵,MySQL才會執行Row lock (只鎖住被選取的資料例) ,否則MySQL將會執行Table Lock (將整個資料表單給鎖住)。 舉個例子: 假設有個表單products ,里面有id跟name二個欄位,id是主鍵。 例1: (明確指定主鍵,並且有此筆資料,row lock) SELECT * FROM products WHERE id='3' FOR UPDATE; SELECT * FROM products WHERE id='3' and type=1 FOR UPDATE; 例2: (明確指定主鍵,若查無此筆資料,無lock) SELECT * FROM products WHERE id='-1' FOR UPDATE; 例2: (無主鍵,table lock) SELECT * FROM products WHERE name='Mouse' FOR UPDATE; 例3: (主鍵不明確,table lock) SELECT * FROM products WHERE id<>'3' FOR UPDATE; 例4: (主鍵不明確,table lock) SELECT * FROM products WHERE id LIKE '3' FOR UPDATE; 注1: FOR UPDATE僅適用於InnoDB,且必須在交易區塊(BEGIN/COMMIT)中才能生效。 假設kid 是表table 的 一個索引字段 且值不唯一 1.如果kid 有多個值為12的記錄那么: update table set name=’feie’ where kid=12; 會鎖表 2.如果kid有唯一的值為1的記錄那么: update table set name=’feie’ where kid=1; 不會鎖表 總結:用索引字段做為條件進行修改時, 是否表鎖的取決於這個索引字段能否確定記錄唯一, 當索引值對應記錄不唯一,會進行鎖表,相反則行鎖。 如果有兩個delete kid1 與 kid2是索引字段 語句1 delete from table where kid1=1 and kid2=2; 語句2 delete from table where kid1=1 and kid2=3; 這樣的兩個delete 是不會鎖表的 語句1 delete from table where kid1=1 and kid2=2; 語句2 delete from table where kid1=1 ; 這樣的兩個delete 會鎖表 總結:同一個表,如果進行刪除操作時,盡量讓刪除條件統一,否則會相互影響造成鎖表
行級鎖定不是MySQL自己實現的鎖定方式,而是由其他存儲引擎自己所實現的,如廣為大家所知的InnoDB存儲引擎,以及MySQL的分布式存儲引擎NDBCluster等都是實現了行級鎖定。考慮到行級鎖定君由各個存儲引擎自行實現,而且具體實現也各有差別,而InnoDB是目前事務型存儲引擎中使用最為廣泛的存儲引擎,所以這里我們就主要分析一下InnoDB的鎖定特性。
1.InnoDB鎖定模式及實現機制
考慮到行級鎖定君由各個存儲引擎自行實現,而且具體實現也各有差別,而InnoDB是目前事務型存儲引擎中使用最為廣泛的存儲引擎,所以這里我們就主要分析一下InnoDB的鎖定特性。
總的來說,InnoDB的鎖定機制和Oracle數據庫有不少相似之處。InnoDB的行級鎖定同樣分為兩種類型,共享鎖和排他鎖,而在鎖定機制的實現過程中為了讓行級鎖定和表級鎖定共存,InnoDB也同樣使用了意向鎖(表級鎖定)的概念,也就有了意向共享鎖和意向排他鎖這兩種。
當一個事務需要給自己需要的某個資源加鎖的時候,如果遇到一個共享鎖正鎖定着自己需要的資源的時候,自己可以再加一個共享鎖,不過不能加排他鎖。但是,如果遇到自己需要鎖定的資源已經被一個排他鎖占有之后,則只能等待該鎖定釋放資源之后自己才能獲取鎖定資源並添加自己的鎖定。而意向鎖的作用就是當一個事務在需要獲取資源鎖定的時候,如果遇到自己需要的資源已經被排他鎖占用的時候,該事務可以需要鎖定行的表上面添加一個合適的意向鎖。如果自己需要一個共享鎖,那么就在表上面添加一個意向共享鎖。而如果自己需要的是某行(或者某些行)上面添加一個排他鎖的話,則先在表上面添加一個意向排他鎖。意向共享鎖可以同時並存多個,但是意向排他鎖同時只能有一個存在。所以,可以說InnoDB的鎖定模式實際上可以分為四種:共享鎖(S),排他鎖(X),意向共享鎖(IS)和意向排他鎖(IX),我們可以通過以下表格來總結上面這四種所的共存邏輯關系:
如果一個事務請求的鎖模式與當前的鎖兼容,InnoDB就將請求的鎖授予該事務;反之,如果兩者不兼容,該事務就要等待鎖釋放。
意向鎖是InnoDB自動加的,不需用戶干預。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖;
原文地址:https://blog.csdn.net/luzhensmart/article/details/81675527