1. 程式人生 > >MySQL事務與鎖詳解

MySQL事務與鎖詳解

事務

事務支援ACID特性
A原子性:所有操作要麼都做要麼都不做
C一致性:事務將資料庫從一種狀態變為另一種狀態一致性,保證資料庫完整性約束,例如唯一索引約束等
I隔離性:事務與事務之間是不可見的
D永續性:事務一旦提交那麼事務就是永久性的

ANSI/ISO SQL標準定義了4中事務隔離級別:未提交讀(read uncommitted),讀提交(read committed),可重複讀(repeatable read),序列讀(serializable)。
對於不同的事務,採用不同的隔離級別分別有不同的結果。不同的隔離級別有不同的現象。主要有下面3種:
1、髒讀(dirty read):一個事務可以讀取另一個尚未提交事務的修改資料。
2、不可重複讀(nonrepeatable read):在同一個事務中,如果資料被其他事務修改,不能重複讀取該記錄原始值。
3、幻像讀(phantom read):在同一事務中,同一查詢多次進行時候,由於其他插入操作(insert)的事務提交,導致每次返回不同的結果集。

不同的隔離級別會產生不同的現象,4種事務隔離級別分別表現的現象如下表:

隔離級別 髒讀 非重複讀 幻像讀
read uncommitted 允許 允許 允許
read committed 不允許 允許 允許
repeatable read 不允許 不允許 不允許
serializable 不允許 不允許 不允許

注意:
1.read committed:不可以杜絕幻象讀.
2.serializable: 當沒有開啟autocommit,InnoDB會將select轉換為 SELECT ... LOCK IN SHARE MODE(加S鎖).如果開啟autocommit,那麼和普通事務中的select一樣為一致性非鎖定讀.
3.repeatable read: 支援next-key,可以避免換讀現象的發生.

實現方式

  • redo日誌:當事務提交時寫入重做日誌檔案,與二進位制日誌不同的是,重做日誌是InnoDB儲存引擎設定的,二進位制日誌是MySQL層的任何儲存引擎都會產生二進位制日誌.其次,重做日誌是物理日誌,記錄著頁修改資訊,二進位制是邏輯日誌.

redo日誌可以配置重做日誌快取實現,先寫快取然後再通過磁碟同步策略寫入磁碟(CheckPoint,檔案緩衝).
LSN:log sequence number日誌序列號,重做日誌寫入檔案的序列號.三個作用:重做日誌寫入總量,Checkpoint的位置,頁的版本.

  • undo日誌:記錄之前版本記錄的日誌.是邏輯日誌,記錄每一個操作會執行一個相反的操作.例如:一個INSERT操作會執行一個DELETE操作.MVCC機制就是通過undo日誌實現.undo頁為了提升磁碟空間利用效率,採用了迴圈寫檔案形式節省磁碟空間,對於標記可重用的undo頁會分配給下個事務.
    當事務提交時,將undo日誌放入連結串列(以記錄進行組織),連結串列可以管理undo日誌用於MVCC以及空間釋放,undo頁可能存放著不同的事務的undo日誌.

如果長時間存在未提交的事務,那麼會導致undo日誌空間暴漲.

分散式事務

分散式事務指的是允許多個獨立的事務資源參與到一個全域性的事務中.
相對於一階段提交,兩階段提交的第一階段是為第二階段是提交還是回滾提供判斷.一階段提交無法完成兩階段的處理,當某些參與者的節點無法完成處理時,這時會出現一些節點已經提交,一些需要回滾,但是已經提交的缺不能回滾,破壞了事務的原子特性.
分散式當中需要一個特殊的東西就是協調器,協調不同資源管理器(一般指資料庫)之間的事務處理.
分散式使用兩段式提交:

1.第一階段(投票階段):協調器向所有參與全域性事務的節點都詢問準備狀態,各節點將要提交的資料寫入undo與redo日誌,各參與者返回狀態同意將進入下一階段,否則返回中止.
2.第二階段(完成階段):如果第一階段任何一個節點返回中止所有的節點都需要回滾,事務管理器告訴資源管理器執行ROLLBACK,如果是同意執行COMMIT.

兩階段提交前提條件:
每個節點都有穩定的儲存,每個節點都預寫日誌,日誌中的資料永遠不會丟失.並且每個節點永遠不會down掉,並且任意兩個節點可以通訊.

兩階段提交最大缺點是阻塞協議,當一協調器Down掉那麼,資源管理器會等待協調器的ROLLBACK或COMMIT.
兩階段提交的前提條件過於嚴苛,當然我們可以通過一些監控,高可用方案來保證事務的最終一致性.

分散式事務的隔離級別須為SERIAlIZABLE.讀鎖為分散式事務的資料一致性提供了可靠保障.
在MySQL中不同的版本對XA事務對複製有一些限制(MySQL服務中斷,XA事務可能會導致Binary log主從不一致等)使用時需要額外注意.

鎖並不是和事務繫結一起的,不同的語句可能鎖集不同,但是事務的隔離級別會影響鎖的範圍.
不在事務當中的UPDATE,INSERT等也都會用到鎖.

鎖的範圍

從範圍可以將鎖大致分為三種(mysql的官方問檔也介紹了其他的鎖):
1.record lock:記錄鎖,當我們加入select * from orders whereid = 1 for update這樣會阻止其他語句增刪改這條資料.
2.gap lock:間隙鎖,這個鎖會鎖住該資料上一個鄰近位置(根據索引或者主鍵)與下一個鄰近位置之間的範圍.(對於索引而言)
針對主鍵或者是唯一索引gap lock會被優化為記錄鎖.可以理解下因為他們都是唯一的不需要間隙鎖來防止幻讀發生.
鎖住區間範圍(鄰近的index-1,index) (index,鄰近的index+1);
注意: 根據索引的排序大於index的第一個索引以及小於index的第一個索引(不僅僅是針對數字型別,字串按照字串的比較的方式),鄰近區間範圍結合B+tree的特性,索引的連結串列去思考.
3.next-key lock:gap lock + recordlock
事務級別最低為repeatable read,會啟用這個鎖,如果低於這個級別會不起用這個鎖.
因此鎖的範圍就變為了(鄰近的index-1,index],(index,鄰近的index+1];
鄰近的index+1不是真正的record lock,只是鎖住了最大索引值的間隙,目的是為了避免幻讀.
鎖的問題只有我們在select語句當中明顯的加入,FOR UPDATE語句才可以明顯的指出這個這個事務或者會話當中採用鎖.

將事務改為READ COMMITED,就會關閉gap lock.
對於事務REPEATABLE READ 預設開啟next-key lock.

讀寫鎖

A shared (S) lock permits the transaction that holds the lock to read a row.
共享鎖:允許獲取到鎖的事務去讀取這一行.
共享鎖又稱讀鎖,是讀取操作建立的鎖。其他使用者可以併發讀取資料,但任何事務都不能對資料進行修改(獲取資料上的排他鎖),直到已釋放所有共享鎖。
An exclusive (X) lock permits the transaction that holds the lock to update or delete a row.

排它鎖:允許獲取到事務的鎖更新刪除這一行.
排他鎖又稱寫鎖、獨佔鎖,如果事務T對資料A加上排他鎖後,則其他事務不能再對A加任何型別的封鎖。獲准排他鎖的事務既能讀資料,又能修改資料。

事務中對於 FOR UPDATE有以下幾點需要注意:
1.凡是用explain顯示為ALL或者index,range會導致鎖住的記錄過多影響併發.
2.減少事務中的執行的語句
3.只有當執行事務提交後,fo updater的語句對應的鎖才會被釋放.

針對死鎖的一些優化
1.在delete,update只採用逐漸或者唯一鍵,我們可以將資料庫的資料型別改為reade commited,降低鎖的範圍.
2.不同事務間訪問表的順序儘量一致.

一致性非鎖定讀

一致性非鎖定讀:通過行多版本控制的方式來讀取當前執行時間資料庫中行的資料.
當一行被加入X鎖,那麼InnoDB會根據當前事務的隔離級別讀取對應的Undo日誌.

Read Commited:每次讀取最新的Undo日誌.
Repeatable Read:每次都讀取事務開始時的Undo資料.

一致性鎖定讀

SELECT ... FOR UPDATE
SELECT ... IN SHARE MODE

對當前的讀取的資料加鎖,但是對於一致性非鎖定讀還是可以正常進行讀取資料.

鎖相關的表

INNODB_TRX 主要欄位介紹:

欄位 說明
trx_id 內部唯一事務ID
trx_state 當前事務狀態
trx_started 事務開始時間
trx_requested_lock_id 事務等待鎖的的ID
trx_wait_started 事務等待開始的時間
trx_weight 事務權重
trx_mysql_thread_d 執行緒ID
trx_query 事務執行的SQL語句
trx_rows_locked 鎖住行數近似數
trx_adaptive_hash_latched 自適應雜湊索引是否被當前的事務鎖住

INNODB_LOCKS

欄位名 說明
lock_id 鎖ID
lock_trx_id 事務ID
lock_mode 鎖的模式
lock_table 要加鎖的表
lock_index 鎖住的索引
lock_space 鎖住物件的space_id
lock_page 事務鎖定頁數量
lock_rec 事務鎖定行數量
lock_data 事務鎖定記錄的主鍵值

為表鎖時,lock_page,lock_rec,lock_data這幾個值為NULL.

INNODB_LOCK_WAITS:

欄位 說明
request_trx_id 申請鎖資源的事務ID
request_lock_id 申請的鎖的ID
blocking_trx_id 阻塞的事務ID
blocking_lock_id 阻塞鎖的ID

通過這幾張表可以查詢當前鎖等待的情況.

死鎖

死鎖是指兩個或兩個以上的事務在執行過程中,因爭奪鎖資源而造成的一種互相等待.

等待圖(wait-for-graph)可以用來檢測死鎖,通過深度優先演算法實現,只要圖中存在迴圈的迴路.那麼存在死鎖.
解決死鎖時會回滾當中undo日誌最小的一個事務

意向鎖

意向鎖是表級鎖,指明事務在將要對錶中的行加哪種意向鎖.
IS:指明事務將會在表中的行加共享鎖.
IX:指明事務將會在表中的行加排它鎖.

意向鎖的意義:表明某個事務正在鎖或者將要鎖表中的一行資料.
意向鎖不會阻塞除全表操作以外的(例如:LOCK TABLES … WRITE)任何請求
如果一個事務想在表中行加S鎖,那麼必須先獲取IS鎖
如果一個事務想在表中行加X鎖,那麼必須現貨區IX鎖

X IX S IS
X Conflict Conflict Conflict
IX Conflict Compatible Conflict
S Conflict Conflict Compatible
IS Conflict Compatible Compatible

假設一個事務要更新整個表(ALTER TABLE),這事會有個X表鎖,X表鎖會和當前所有事務的意向鎖(也是表級鎖)進行相容性檢測,意向鎖和表級鎖檢測是很快的,但是檢測表級鎖和行級鎖是比較困難的.
可見意向鎖主要是為了方便實現與表級鎖檢測判斷,提升效能.