MySQL中的事務與鎖
事務
資料庫事務(簡稱:事務)是資料庫管理系統執行過程中的一個邏輯單位,由一個有限的資料庫操作序列構成。一個數據庫事務通常包含了一個序列的對資料庫的讀/寫操作。它的存在包含有以下兩個目的:
1 2 |
|
當事務被提交給了DBMS(資料庫管理系統),則DBMS(資料庫管理系統)需要確保:
1 2 |
|
Basically any time you have a unit of work that is either sensitive to outside changes or needs the ability to rollback every change, if an error occurs or some other reason.
ACID
並非任意的對資料庫的操作序列都是資料庫事務。資料庫事務擁有以下四個特性: 原子性(Atomiocity)、一致性(Consistency)、隔離性(Isolation)和永續性(Durability),習慣上被稱之為ACID特性。
- 原子性(Atomicity)
一個事務中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
- 一致性(Consistency)
事務應確保資料庫的狀態從一個一致狀態轉變為另一個一致狀態。一致狀態的含義是資料庫中的資料應滿足完整性約束。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、關聯性以及後續資料庫可以自發性地完成預定的工作,如兩使用者轉款前後的金額總和要一樣。
- 隔離性(Isolation)
當兩個或者多個事務併發訪問(此處訪問指查詢和修改的操作)資料庫的同一資料時所表現出的相互關係。通常來說,一個事務的執行不能被其他事務干擾,即一個事務內部的操作及使用的資料對其他事務是隔離的,併發執行的各個事務之間互相不干擾。但事務之間的真實隔離性取決於事務的隔離模式。
SQL標準中定義了4中隔離級別(或稱為隔離模式),低級別的隔離可以執行更高的併發,系統的開銷也更低。這4種級別包括讀未提交(read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(serializable)。
給出每種隔離級別的實際程式碼例子
- READ UNCOMMITTED(未提交讀)
該隔離級別下,事務中的修改,即使沒有提交,對其他事務也都是可見的。因此,對於其他業務,可能會產生“髒讀”,從而引起很多問題。同時從效能層面考慮,READ UNCOMMITED 和其他隔離級別也差不多,因此實際場景中一般很少使用。
髒讀: 讀取到部分修改的、事務未提交的資料, 即SELECT會讀取其他事務修改而還沒有提交的資料。比如:事務T1更新了一行記錄的內容,但是並沒有提交所做的修改;事務T2讀取更新後的行,然後T1執行回滾操作,取消了剛才所做的修改。現在T2所讀取的行就無效了。
- READ COMMITTED(提交讀)
大多資料庫的預設隔離級別如Oracle,但MySQL不是。本隔離級別下,滿足隔離的基本定義:事務在提交前所做的修改對其他業務不可見。該級別下,兩次執行同樣的查詢,可能會得到不一樣的結果,產生不可重複讀的效果。
不可重複讀: SELECT的時候無法重複讀,即同一個事務中兩次執行同樣的查詢語句,若在第一次與第二次查詢之間時間段,其他事務又剛好修改了其查詢的資料且提交了,則兩次讀到的資料不一致。比如:事務T1讀取一行記錄,緊接著事務T2修改了T1剛才讀取的那一行記錄。然後T1又再次讀取這行記錄,發現與剛才讀取的結果不同。這就稱為“不可重複”讀,因為T1原來讀取的那行記錄已經發生了變化。
- REPEATABLE READ(可重複讀)
MySQL預設隔離級別,該隔離級別解決了不可重複讀問題——SELECT的時候可以重複讀,即同一個事務中兩次執行同樣的查詢語句,得到的資料始終都是一致的,但還是存在幻讀。
可重複讀: 在同一個事務內的查詢都與事務開始時刻一致的,InnoDB預設級別。
幻讀: 事務T1讀取一條指定的WHERE子句所返回的結果集。然後事務T2新插入一行記錄,這行記錄恰好可以滿足T1所使用的查詢條件中的WHERE 子句的條件。然後T1又使用相同的查詢再次對錶進行檢索,但是此時卻看到了事務T2剛才插入的新行。這個新行就稱為“幻像”,因為對T1來說這一行就像突然出現的一樣。InnoDB 通過多版本併發控制(MVCC)解決幻讀問題。
- SERIALIZE(可序列化)
強制事務序列執行。該隔離級別下,會對讀取的每一行資料上都加上鎖,因而對鎖機制的管理比較耗系統資源,資料庫一般都不會用這個隔離級別。與可重複讀的唯一區別是,預設把普通的SELECT語句改成SELECT …. LOCK IN SHARE MODE。即為查詢語句涉及到的資料加上共享瑣,阻塞其他事務修改真實資料。
在MySQL中,可以通過 select @@tx_isolation;
命令檢視當前的事務隔離級別,如:
也可以通過執行命令set session transaction isolation level read committed;
修改事務隔離級別,如:
需要注意的是上述方式修改的事務隔離級別僅對當前session有效。如果要對所有新建的連線設定隔離級別,可以用set global transaction isolation level read committed;
它將決定新建連線的初始隔離級,但不會改變已有連線的隔離級。可以通過select @@global.tx_isolation;
命令檢視global transaction isolation level:
如果想要全域性修改事務隔離級別,可以在my.cnf 配置檔案中修改,只需在最後加上”
1 2 3 |
|
- 永續性(Durability)
已被提交的事務對資料庫的修改應該永久儲存在資料庫中,接下來其他的其他操作或故障不應該對其執行結果有任何影響,即提交的事務一定保證寫入磁碟。
事務的實現
1. 如何保證原子性(A)?
資料庫中與原子性相關的操作有rollback和commit。commit用於正常提交一個事務,rollback用於將事務中之前的操作回滾。第三種情況是資料庫出現異常時,如斷電,事務執行一半而退出。 事務的整個執行過程說明如下: (1) 每個事務開始時,系統會為該事務分配一個時間戳(唯一標識該事務)、回滾段和undo段。 (2) 事務中的每條SQL在執行修改操作前都會寫undo日誌,然後再將更新的內容寫入undo段。 (3) 執行commit時,系統將修改的資料寫入實際記憶體,並將修改資訊寫入回滾段。 (4) 執行rollback時,系統將undo段內容失效。 (5) 當系統在執行事務過程中出現異常退出後,系統再次啟動,會從undo日誌中恢復。
2. 如何保證一致性(C)?
事務一致性的保證和原子性和隔離性都有關係,即系統保證事務一致性的前提是保證事務的原子性和隔離性。上文中的“髒讀”、“不可重複讀”、“幻讀”,其實都是資料庫讀一致性問題,必須由資料庫提供一定的事務隔離機制來解決。
3. 如何保證隔離性(I)?
目前資料庫實現的事務隔離方式分兩種:
1 2 3 |
|
- 悲觀併發控制
悲觀併發控制,正如其名,它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中(當前事務中),將資料處於鎖定狀態,即讀取資料時給加鎖,其它事務無法修改這些資料,修改刪除資料時也要加鎖,其它事務無法讀取這些資料。那只有當這個事務把鎖釋放,其他事務才能夠執行與該鎖衝突的操作。
悲觀鎖的實現,往往依靠資料庫提供的鎖機制(Lock-Based Concurrency Control)(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。
悲觀併發控制主要用於資料爭用激烈的環境,以及發生併發衝突時使用鎖保護資料的成本要低於回滾事務的成本的環境中。然而,資料庫又是個高併發的應用,同一時間會有大量的併發訪問,如果加鎖過度,會極大的降低併發處理能力,於是就有了樂觀併發控制(又名“樂觀鎖”,Optimistic Concurrency Control,縮寫“OCC”)。
- 樂觀併發控制
在關係資料庫管理系統裡,樂觀併發控制(又名“樂觀鎖”,Optimistic Concurrency Control,縮寫“OCC”)是一種併發控制的方法。相對悲觀鎖而言,樂觀鎖機制採取了更加寬鬆的加鎖機制。它假設多使用者併發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分資料。在提交資料更新之前,每個事務會先檢查在該事務讀取資料後,有沒有其他事務又修改了該資料。如果其他事務有更新的話,正在提交的事務會進行回滾。
樂觀併發控制多數用於資料爭用不大、衝突較少的環境中,這種環境中,偶爾回滾事務的成本會低於讀取資料時鎖定資料的成本,因此可以獲得比其他併發控制方法更高的吞吐量。
樂觀鎖,大多是基於MVCC (Multi-Version Concurrency Control),即多版本控制協議實現。MVCC最大的好處是讀不加鎖,讀寫不衝突。不加任何鎖,通過一定的機制生成一個數據請求時間點的一致性資料快照(snapshot),並用這個快照來提供一定級別(語句級或事務級)的一致性讀取。從使用者角度來看,好像是資料庫可以提供同一資料的多個版本,因此這種技術又叫做多版本併發控制(Mutil Version Concurrency Control,簡稱MVCC或MCC),也稱為多版本資料庫。 在讀多寫少的OLTP應用中,讀寫不衝突是非常重要的,極大的增加了系統的併發效能,這也是為什麼現階段,幾乎所有的RDBMS,都支援了MVCC。要說明的是,MVCC的實現沒有固定的規範,每個資料庫都會有不同的實現方式,這裡討論的是InnoDB的MVCC。
4. 如何保證永續性(D)?
永續性的保證需要日誌的支援,資料庫寫日誌的原則是執行寫資料前要先寫日誌。
針對事務一些推薦的做法
- 開啟新事務前先rollback一下
- 每次做完update後校驗affected_rows是否是期望的
- 考慮重連邏輯
- 儘量避免大事務
- 加鎖資源使用要有一定的順序, 避免死鎖
- mysql的事務儘量小,使用完,立即commit或rollback.不要起一個過大的事務
- 避免嘗試去鎖一個不存在的記錄,for update語句where條件請使用主鍵
- 避免過多的for update集合
- mysql單表記錄保持在1000W以下,以獲得較好的效能
- 需要修改mysql 鎖等待時間,避免for update等待時間超長,造成系統阻塞。innodb_lock_wait_timeout 引數
鎖
鎖就是事務T在對某個資料物件例如表、記錄等操作之前,先向系統發出請求,對其加鎖。加鎖後事務T就對該資料物件有了一定的控制。當多個客戶端同時訪問和修改相同的資料時,鎖機制可以保證資料的一致性。
InnoDB的鎖結構如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
|