關於數據庫鎖,事務,隔離級別

一,數據庫中的鎖

MySQL採用的是C/S結構的數據庫系統,這就意味着同一時間可能會出現多個客戶端訪問數據庫,進行讀寫,修改等操作。這樣一來就會出現一些列的問題。

比如,買車票,同一趟車次只有一張票了,A和B同時讀取了,這個數據。此時如果沒有約束,雙方都以爲自己有票,下單後就會出現A和B坐在了同一趟車次的同一個座位上。

對此問題的解決方案有多種,對於MyISAM 存儲引擎來說,只能使用鎖定(Locking)機制來實現,簡單的來說就是一張或者多張表在同一時間只能有一個客戶端來使用,在解除鎖定之前,其他客戶端無法對錶數據進行修改甚至讀取,具體要看鎖的類型。

鎖的使用語法:

LOCK TABLE table1 locktype ,table2 locktype ...

locktype的取值:

READ:被鎖定的表對全體客戶端只讀,不允許修改,包括加鎖的客戶端,READ LOCK只在當前表上沒有任何WRITE LOCK的時候才生效。

READ LOCAL :類似於READ鎖定類型,但是允許不影響現有數據的任何INSERT 命令執行。

WRITE:被鎖定只允許當前客戶端讀取和修改。其他客戶端完全被排外,不能進行任何讀寫操作,WRITE LOCK只在當前表上沒有任何READ LOCK的時候才生效。

LOW PRIORITY WRITE:類似於WRITE鎖定類型,允許其他客戶端在等待WRITE LOCK,READ LOCK期間加一個READ LOCK。

使用UNLOCK TABLE 解除鎖定。

提示:

Mysql總是以這樣的方式執行單條命令:不讓它收到其他任何命令的影響,因此在執行單條命令(UPDATE或者DELETE)的時候不需要使用鎖定機制。只有當執行連續的命令且不希望其他客戶端在命令執行期間修改正在使用的數據時,才需要使用鎖定機制。如果先讀取緊接着修改此數據。不要對InnoDB類型的數據表操作時使用lock機制,因爲在mysql早期版本中使用lock機制會和innoDB本身的鎖定機制發生衝突。如果要對InnoDB表進行整個表的鎖定,在mysql5.3之後提供了LOCK TABLE TRANSACTIONAL命令。

對於InnoDB存儲引擎來說,它支持事務來解決上述問題。

 

二,什麼是事務:

事務就是能保證一組操作要麼執行,要麼不執行。

比如A給B轉賬操作,需要兩步首先把A的賬戶減去數額,然後把B的賬戶加上相應數額。事務就保證了這兩個操作要麼成功要麼失敗,不會出現其中一個成功的情況,即使是斷電等異常情況下。

爲啥要用事務:

在回答爲什麼要用事務的時候,數據庫的理論家往往將其總結爲一個詞:ACID,原子性(Atomicity),一致性後者穩定性(Consistency),隔離性(Isolation)和可靠性(Durability).

原子性(Atomicity):保證了事務像原子一樣不可分割,數據庫系統必須保證事務內的所有操作命令,要麼全部執行成功,要麼不會真正執行,即使執行了也會撤銷,即使在事務執行過程中,發現了計算機奔潰等極端事件。

一致性後者穩定性(Consistency):保證事務執行完畢後,數據庫必須處於一個穩定的狀態,如果發現執行了某個事務讓數據違反了有關數據合法性規定(也就是一些類似外鍵約束,出現了非法數據)。就會撤銷事務。回滾到事務執行之前的狀態。

隔離性(Isolation):這意味着多個事務可以獨立執行,在執行時不會相互影響。每一個事務看到的數據庫在這個事務開始之前和結束之後,除了事務本身做出的修改外不會發生任何變化。換句話說即使一個事務插入或者修改刪除了某個數據,只要事務沒被提交,與他同期執行的事務就不會被影響,在這個事務提交了之後,所受其結果影響的其他事務會被自動回滾。並返回錯誤信息給客戶端。讓事務百分之百的隔離是要付出巨大代價的,這個代價表現在速度方面。爲此,定義了四種事務隔離級別供程序員在安全和速度兩方面根據具體需要選擇。不通的數據庫系統有不通的默認隔離級別。InnoDB採用的默認隔離級別是 REPEATABLE READ.

可靠性(Durability):這意味着,事務必須能夠經受住,軟件和硬件崩潰或者其他意外故障,在故障解決後任然能夠繼續執行。InnoDB的做法是將所有的修改都記到一個日誌文件中,如果發生意外,在重啓之後,InnoDB數據表驅動程序會讀取這個日誌文件,根據文件構造所有修改再傳輸給數據庫。可靠性和速度不可兼得,InnoDB提供了參數來選擇什麼時候記錄日誌。

 

三,事務的隔離級別:

READ_UNCOMMITED (讀未提交):SELECT 命令在讀取記錄時會把尚未完成的其他事務對數據的修改也會考慮在內。換句話說SELECT 命令和其他事務沒有隔離。注意:READ_UNCOMMITED不隔離SELECT命令但卻隔離UPDATE.

注:這種隔離級別會出現髒讀,所謂髒讀就是讀取其他未提交的事務修改的數據後,其他事務出現回滾後,讀取的數據就是錯誤的現象。

READ_COMMITED(讀已提交):SELECT 命令在讀取記錄時會把已經完成的其他事務對數據的修改也會考慮在內。這意味着同一個事務裏的同樣的兩條SELECT 命令可以有不同的結果。

注:這種隔離級別解決了髒讀問題,但是這種隔離級別會出現不可重複讀情況,所謂不可重複讀就是,同樣的兩條SELECT命令,命令1 讀取的時候其他事務對讀取的數據修改操作未提交,但是在命令2執行的時候其他的事務的修改操作提交了,這就導致同樣的查詢命令在同一個事務中查到的結果不同。

REPEATABLE READ(可重讀):SELECT 命令在讀取記錄時會把不會把其他事務對數據的修改也會考慮在內,不管其他事務有沒有提交。這種隔離級別完全符合ACID對SELECT 命令的隔離要求。同一個事務裏同樣的讀命令當然要返回同樣的數據。

注:這種隔離級別解決了不可重複讀問題和幻讀問題,所謂幻讀同樣的兩條SELECT命令,命令1 讀取的時候其他事務對讀取的數據修改操作未提交,但是在命令2執行的時候其他的事務的修改操作提交了,這就導致同樣的查詢命令在同一個事務中查到的數量不同。

SERIALIZABLE(串行化),這種模式和REPEATABLE READ很相似,唯一的區別就是這種模式會把普通的SELECT也當做是SELECT ...LOCK IN SHARE MODE形勢來執行,並給受其影響的其他數據記錄統一加上一把共享鎖。髒讀,不可重讀和幻讀:

【1】髒讀(讀取未提交數據)

A事務讀取B事務尚未提交的數據,此時如果B事務發生錯誤並執行回滾操作,那麼A事務讀取到的數據就是髒數據。就好像原本的數據比較乾淨、純粹,此時由於B事務更改了它,這個數據變得不再純粹。

這個時候A事務立即讀取了這個髒數據,但事務B良心發現,又用回滾把數據恢復成原來乾淨、純粹的樣子,而事務A卻什麼都不知道,最終結果就是事務A讀取了此次的髒數據,稱爲髒讀。

【2】不可重複讀(前後多次讀取,數據內容不一致)

事務A在執行讀取操作,由整個事務A比較大,前後讀取同一條數據需要經歷很長的時間 。而在事務A第一次讀取數據,比如此時讀取了小明的年齡爲20歲,事務B執行更改操作,

將小明的年齡更改爲30歲,此時事務A第二次讀取到小明的年齡時,發現其年齡是30歲,和之前的數據不一樣了,也就是數據不重複了,系統不可以讀取到重複的數據,成爲不可重複讀。

【3】幻讀(前後多次讀取,數據總量不一致)

事務A在執行讀取操作,需要兩次統計數據的總量,前一次查詢數據總量後,此時事務B執行了新增數據的操作並提交後,這個時候事務A讀取的數據總量和之前統計的不一樣,就像產生了幻覺一樣,平白無故的多了幾條數據,成爲幻讀。

 

隔離級別是如何實現的:MVCC

MVCC(Multi-Version Concurrency Control多版本併發控制):MVCC每次更新操作都會複製一條新的記錄,新紀錄的創建時間爲當前事務id優勢爲讀不加鎖,讀寫不衝突InnoDb存儲引擎中,每行數據包含了一些隱藏字段 DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE BIT

DATA_TRX_ID 字段記錄了數據的創建和刪除時間,這個時間指的是對數據進行操作的事務的id

DATA_ROLL_PTR 指向當前數據的undo log記錄,回滾數據就是通過這個指針

DELETE BIT位用於標識該記錄是否被刪除,這裏的不是真正的刪除數據,而是標誌出來的刪除。真正意義的刪除是在mysql進行數據的GC,清理歷史版本數據的時候。

具體的DML:

INSERT:創建一條新數據,DB_TRX_ID中的創建時間爲當前事務id,DB_ROLL_PT爲NULL

UPDATE:將當前行的DB_TRX_ID中的刪除時間設置爲當前事務id,DELETE BIT設置爲1

DELETE:複製了一行,新行的DB_TRX_ID中的創建時間爲當前事務id,刪除時間爲空, DB_ROLL_PT指向了上一個版本的記錄,事務提交後DB_ROLL_PT置爲NULL

可知,爲了提高併發度,InnoDb提供了這個「非鎖定讀」,即不需要等待訪問行上的鎖釋放,讀取行的一個快照即可。 既然是多版本讀,那麼肯定讀不到隔壁事務的新插入數據了,所以解決了幻讀。

 

MVCC與隔離級別

Read Uncommitted每次都讀取記錄的最新版本,會出現髒讀,未實現MVCC

Serializable對所有讀操作都加鎖,讀寫發生衝突,不會使用MVCC

SELECT (REPEATABLE READ級別)InnoDb檢查每行數據,確保它們符合兩個標準:

只查找創建時間早於當前事務id的記錄,這確保當前事務讀取的行都是事務之前已經存在的,或者是由當前事務創建或修改的行行的DELETE BIT爲1時,查找刪除時間晚於當前事務id的記錄,確定了當前事務開始之前,行沒有被刪(READ_COMMITED級別)每次重新計算read view,read view的範圍爲InnoDb中最大的事務id,爲避免髒讀讀取的是DB_ROLL_PT指向的記錄

就這麼簡單嗎? 其實幻讀有很多種出現形式,簡單的SELECT不加條件的查詢在RR下肯定是讀不到隔壁事務提交的數據的。但是仍然可能在執行INSERT/UPDATE時遇到幻讀現象。因爲SELECT 不加鎖的快照讀行爲是無法限制其他事務對新增重合範圍的數據的插入的。

所以還要引入第二個機制。

Next-Key Lock

其實更多的幻讀現象是通過寫操作來發現的,如SELECT了3條數據,UPDATE的時候可能返回了4個成功結果,或者INSERT某條不在的數據時忽然報錯說唯一索引衝突等。

首先來了解一下InnoDb的鎖機制,InnoDB有三種行鎖:

Record Lock:單個行記錄上的鎖

Gap Lock:間隙鎖,鎖定一個範圍,但不包括記錄本身。GAP鎖的目的,是爲了防止同一事務的兩次當前讀,出現幻讀的情況

Next-Key Lock:前兩個鎖的加和,鎖定一個範圍,並且鎖定記錄本身。對於行的查詢,都是採用該方法,主要目的是解決幻讀的問題

如果是帶排他鎖操作(除了INSERT/UPDATE/DELETE這種,還包括SELECT FOR UPDATE/LOCK IN SHARE MODE等),它們默認都在操作的記錄上加了Next-Key Lock。

只有使用了這裏的操作後纔會在相應的記錄周圍和記錄本身加鎖,即Record Lock + Gap Lock,所以會導致有衝突操作的事務阻塞進而超時失敗。性能隔離級別越高併發度越差,性能越差,雖然MySQL默認的是RR,但是如果業務不需要嚴格的沒有幻讀現象,是可以降低爲RC的或修改配置innodb_locks_unsafe_for_binlog爲1 來避免Gap Lock的。

注意有的時候MySQL會自動對Next-Key Lock進行優化,退化爲只加Record Lock,不加Gap Lock,如相關條件字段爲主鍵時直接加Record Lock。

四,事務和鎖

在絕大多數的時候一旦開始一個事務,InnoDB就會把鎖定操作安排好,但是在某些情況下,默認的安排並不是最優解,因此InnoDB提供了幾種方式來選擇。

1,SELECT ..... LOCK IN SHARE MODE

按照InnoDB的默認隔離級別,SELECT命令在鎖定的的記錄上也能返回結果,並且其結果不收未提交事務的影響,這樣的好處是效率高,壞處是查詢的結果可能已經過時了。如果加上LOCK IN SHARE MODE 它會等到那些事務處理全部結束後才進行查詢,如果LOCK IN SHARE MODE 本身也在一個事務中,在它開始執行之後和事務結束之前,數據表裏與其結果記錄相關的所有數據都會被鎖定,其他客戶只能讀取不能修改,或者刪除。因爲裏面有個關鍵字SHART人們稱之爲共享鎖,共享鎖可以確保在事務執行過程中讀取的數據記錄不會是其他事務修改或者刪除的。

共享鎖不會阻斷其他客戶來讀取這些數據記錄,就是其他客戶在讀取這些數據時也使用了 LOCK IN SHARE MODE 。共享鎖只會阻斷其他客戶對被鎖定的數據進行修改和刪除操作。其他客戶只能等到事務結束後才能對這些記錄進行刪除和修改。

2,SELECT ......FOR UPDATE

關鍵字FOR UPDATE 代表這對普通的SELECT 命令的另一種功能擴展。這個關鍵字將會給數據表裏面與這條SELECT 命令相關的所有數據加一個排他鎖(exclusive lock)排他鎖不禁止其他客戶使用SELECT 來讀取被鎖定的記錄,但是其他客戶對那些數據進行修改和刪除,以及使用 LOCK IN SHARE MODE命令進行讀取操作都將被阻斷。共享鎖和排他鎖的唯一區別在於是否阻斷其他客戶發出的LOCK IN SHARE MODE命令。

INSERT ,UPDATE ,DELETE三個命令在開始執行後都會給將要修改或者刪除的數據加上排他鎖,直到事務結束,如果修改或者刪除的數據有外鍵關係,則關聯的記錄也會被加排他鎖。

3,放插入鎖

InnoDB默認會將帶範圍條件查詢的(where id >100 )SELECT ...LOCK IN SHARE MODE ,SELECT ......FOR UPDATE 或者DELETE 命令加一把防插入鎖,效果是不僅將符合查詢條件的當期記錄鎖定,連符合條件的並不存在的也會被鎖定,也就是說不能再插入符合條件的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章