事務在一個數據庫中的地位尤為重要,尤其是高並發的場合。保證數據庫操作的原子性和錯誤出現情況下的回滾,對數據的安全性和可靠性提供了保障。事務有四大原則,即ACID原則。網上關於這個問題的文章有很多,讀者可以到網上看看相關的文章,我這里就不贅述了。但是需要注意的是,MySQL默認是不開啟事務的,默認情況是autocommit自動提交,而如果想開啟事務,需要數據庫管理員或者開發者手動輸入begin來開啟事務。
本文主要介紹四大原則中的I原則,即隔離級別。並在講述I原則的時候,順帶討論MVCC。因為MVCC通常和隔離級別是討論到一塊的。
事務的隔離級別其實是SQL語言的標准,這里我就以自己比較常用的MySQL數據庫為例進行介紹。
客戶端輸入命令:
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
{READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
小提示:也可以使用小寫,MySQL是不分大小寫的。
在MySQL Workbench輸入命令查詢系統默認的隔離狀態
MySQL默認情況下是repeatable read。
設置當前會話隔離級別
設置當前會話(客戶端)的隔離級別,當客戶端關閉后重新進入,隔離級別會恢復到系統的全局隔離狀態。
理解‘當前會話’:
MySQL中認為,打開一個客戶端就是開啟一個會話。其實質是建立一個網絡連接。這個網絡連接是有狀態性的,一旦關閉,此會話級別中設置的變量就會恢復為系統的變量。
設置全局隔離級別
設置全局隔離級別的時候,只對全局進行修改,當前會話還是會保持原來的設置,退出重新登錄才會遵循全局的設置。
在MySQL Workbench中輸入命令:
或者在終端輸入
mysql> show variables like '%isolation'; +-----------------------+----------------+ | Variable_name | Value | +-----------------------+----------------+ | transaction_isolation | READ-COMMITTED | | tx_isolation | READ-COMMITTED | +---------------- ------+----------------+ 2 rows in set, 1 warning (0.00 sec)
另外一種方法:
在ini文件(Linux為conf文件)設置全局的隔離級別
[mysqld]
添加:
transaction-isolation = READ-COMMITTED
不過這種情況需要重啟服務器,所以不太建議使用這種方式,除非是幾乎沒有請求的深夜時候進行。
C:\WINDOWS\system32> net stop mysql MySQL 服務正在停止.. MySQL 服務已成功停止。 C:\WINDOWS\system32> net start mysql MySQL 服務正在啟動 . MySQL 服務已經啟動成功。
關於以上命令,有一些需要理解的點:
1)隔離級別優先級順序 read uncommittd < read committed < repeatable read < serializable
我的理解是每一個級別都是在上一級別的基礎上增加了表操作的限制。讀者往下看就可以體會到這種限制的加強。
2)session和global
1. 默認行為(不帶session和global)是為下一個(未連接)會話設置隔離級別。
2. 如果使用GLOBAL關鍵字,此命令語句在全局中對從那點開始創建的所有新連接設置事務級別。
3. 使用SESSION 關鍵字為當前連接上執行的事務設置默認事務級別。
4. 任何客戶端在任何時候都能自由改變會話/全局隔離級別(甚至在事務的中間),或者為下一個事務設置隔離級別。
實際測試中,由於是本地連接,一些情況無法模擬出來,而且手動也很難做一些模擬高並發的情形,可以借助一些測試工具來進行。例如sysbench,《高性能MySQL》中也有一章專門寫了基准測試。
下面是對四個隔離級別的介紹
1. Read Uncommitted 讀取未提交的事務
一個數據庫連接實例中,它的未提交事務執行了對數據庫的增刪改操作,這些操作對數據的更改是沒有提交到服務器磁盤的,但是在高並發的情況下,本連接的事務會不斷更新數據庫以獲取其他連接的事務對數據庫進行的最新的更改。那么第一個事務查詢操作讀取的數據可能是不真實的,人家都還沒有提交!所以這種情況下就會出現了所謂的臟讀。一般,在現實中很少使用這個級別,除非是對數據的真實性要求不高,只想獲得最新的數據的情況。想想在一些金融領域的電子現金系統或者商城購物車系統使用這個級別會是怎樣一個情形?
2. Read Committed 讀取已經提交的事務
此級別在本會話中對數據庫進行查詢,會讀取已經提交(commit)了SQL語句的事務結果,而未提交的事務的操作對數據庫所造成的影響是不會讀取的。可以這樣理解,這個級別更像是‘事務級別’,即不同事務間內部的操作互不影響,只有提交了事務才會對其他事務產生影響。
Read committed級別同時也是nonrepeatable read級別(是相對下面的Repeatable Read而言的),即不要求重復讀。一個事務前后兩次的讀取內容可以是不同的,即允許在本事務查詢的過程中其他事務對本事務查詢的數據進行其他操作,這樣就會出現前后兩次讀取的數據的不一致性。此處涉及到MVCC和樂觀鎖、悲觀鎖的概念,后面會講述。我們先討論這種機制的運行邏輯。
想象一下下面這種情形:高並發情況下,多個數據庫連接同時對數據庫提交操作。本事務由於SQL語句比較多,執行起來比較慢。而在這期間(有時可能是1s內),有另外兩個連接同時在執行操作,一個連接對一行數據的某一列進行增加2操作,而另外一個連接對同一行數據的這一列數據進行減3操作,它們在對數據庫的實現上可能相差的時間很小,可能僅在毫秒之間這個時候就會出現兩個vesion版本的結果,一個是執行了一個事務的版本,另一個是執行了兩個事務的版本。那么本事務究竟讀取哪一個版本呢?答案是指讀取執行了兩條事務的版本。因為在本事務內,會不斷去‘查看’其他事務的提交情況,並將最新的提交情況‘反饋’在本事務中。
3. Repeatable Read 可重復讀
Repeatable Read正好與上面的Read Committed相反,本事務會讀取第一個version的數據。因為在進行第一次查詢操作的時候,可能第一個事務已經提交了,而第二個事務還沒提交。而無論后面第二個事務甚至更多個事務提交了,但本事務中再次讀取的數據時只會讀取是第一讀取的相同數據,即‘無視’其他事務對此行數據的更改。可以理解,在這里是悲觀鎖發揮了作用,即鎖住了本事務,其他事務的數據的更改就無法影響到本事務。而在Read Committed級別中,發揮作用的是樂觀鎖,其實就是什么也不鎖定,只在更新數據的時候鎖定(下面有更詳細描述)。所以在本事務的前后兩次范圍查詢中會出現數據不一致的幻讀現象。
4. Serilizable 串行化
數據庫最高級別的隔離限制,事務與事務之間串行化的。即一個事務在執行任何操作的時候,都不允許其他事務對本事務查詢范圍內的數據有任何操作,這里會引入一個共享鎖的概念,即本事務的查詢操作別讀取的數據鎖住了,必須等本事務完成之后才允許其他事務獲取這個共享鎖進行其他操作,就好比如排着隊一個個執行。
MVCC
MVCC, Multiversion Concurrency Control多版本並發控制。MVCC是行級鎖的一個變種,但是它在很多情況下避免了加鎖操作, 因此服務器的開銷更低(減少了鎖的生產和分配)。雖然實現機制有所不同, 但大都實現了非阻塞的讀操作,寫操作也只鎖定必要的行。
在實現上,MySQL通過三個列實現對版本的控制,即6字節的事務ID(DB_TRX_ID)字段,7字節的回滾指針(DB_ROLL_PTR)字段 ,6字節的DB_ROW_ID字段。更多相關內容推薦看這篇文章,里面講得很詳細 https://juejin.im/entry/5a4b52eef265da431120954b
而實際上InnoDB並非完全意義上的MVCC,因為沒有實現多版本並存。關鍵在於並存兩字,即在事務執行對數據操作時同時存在多個版本。而無論如何MySQL在事務執行的時候是串行化的,即使這兩個事務幾乎同時發生。其實這正是對數據安全性的一個保障。
MVCC只在 READ COMMITTED 和 REPEATABLE READ 兩個隔離級別下工作。其他兩個隔離級別和MVCC不兼容。
悲觀鎖:
悲觀鎖的特點是先獲取鎖,再進行業務操作,即“悲觀”的認為獲取鎖是非常有可能失敗的,因此要先確保獲取鎖成功再進行業務操作。
樂觀鎖:
樂觀鎖的特點先進行業務操作,不到萬不得已不去拿鎖。即“樂觀”的認為拿鎖多半是會成功的,因此在進行完事務操作需要實際更新數據的最后一步再去拿一下鎖就好。
在實戰中怎么使用,還是要看具體的數據量和業務邏輯來進行選擇。
更新說明:隨着自己對MySQL數據庫隔離級別的更多理解,本文在之前的內容基礎上進行重新整理,並且添加上了MVCC的內容。18.12.25
參考文章:
https://www.cnblogs.com/phpper/p/7345332.html
https://juejin.im/entry/5a4b52eef265da431120954b