在使用數(shù)據(jù)庫的過程中,事務(wù)通常是我們一個很熟悉的概念。
事務(wù)的主要目標是確保一組數(shù)據(jù)庫操作全部成功或全部失敗。
在MySQL中,事務(wù)支持是在存儲引擎級別實現(xiàn)的。
需要注意的是,MySQL 是一個支持多種存儲引擎的系統(tǒng),但并非所有引擎都提供事務(wù)支持。例如,MySQL原生的MyISAM存儲引擎不支持事務(wù),這也是InnoDB取代MyISAM的重要原因之一。
在這篇文章中,將以InnoDB為例,深入研究MySQL對事務(wù)支持的具體實現(xiàn),并根據(jù)這些原則提供實用的建議。希望這些案例研究能夠加深您對MySQL事務(wù)原理的理解。
隔離性
當提到事務(wù)時,ACID(原子性、一致性、隔離性、持久性)肯定是我們首先想到的一個術(shù)語。今天,我們將深入研究其中一個方面,即ACID的Isolation隔離性。
當多個事務(wù)在數(shù)據(jù)庫上同時運行時,可能會導(dǎo)致諸如臟讀,不可重復(fù)讀和幻讀之類的問題。
為了解決這些問題,引入了隔離級別的概念。
我們要清楚的是,隔離級別越高,執(zhí)行效率就越低。因此,很多時候我們需要在兩者之間取得平衡。
SQL標準定義了四種事務(wù)隔離級別:
- 讀未提交:事務(wù)提交之前,它所做的更改對于其他事務(wù)是可見的。
- 已提交讀:提交事務(wù)后,它所做的更改對其他事務(wù)可見。
- 可重復(fù)讀:事務(wù)執(zhí)行期間看到的數(shù)據(jù)與事務(wù)啟動時最初看到的數(shù)據(jù)保持一致。在Repeatable Read隔離級別中,未提交的更改對于其他事務(wù)也是不可見的。
- Serialized:顧名思義,對于同一行記錄,“寫”操作會加一個“寫鎖”,“讀”操作會加一個“讀鎖”。如果讀鎖和寫鎖之間發(fā)生沖突,稍后嘗試訪問的事務(wù)必須等待前一個事務(wù)完成才能繼續(xù)。
已提交讀和可重復(fù)讀的隔離級別確實有點難以掌握。
示例
為了幫助說明這些隔離級別,讓我們考慮一個示例。假設(shè)我們有一個數(shù)據(jù)表 T,只有一列和一行包含值1。
mysql > create table test_table(id int ) engine = InnoDB;
mysql > insert test_table(id) values ( 1 ) ;
以下是按時間順序執(zhí)行的兩個事務(wù)的行為:
現(xiàn)在我們看看在不同隔離級別下會產(chǎn)生的不同結(jié)果,以及場景中V1、V2和 V3的值是什么。
- 未提交讀: V1的值是2。在這種情況下,即使事務(wù) 2 尚未提交,事務(wù)1也能看到事務(wù)2更新后的結(jié)果。因此,V2 和 V3 也都是 2。
- 已提交讀:V1是1,V2是2。事務(wù) 2 的更新僅在提交后對事務(wù) 1 可見。因此,V3的值也是2。
- 可重復(fù)讀:V1和V2都是1,但V3是2。V2是1的原因是為了滿足事務(wù)執(zhí)行過程中看到的數(shù)據(jù)必須從頭到尾保持一致的要求
- 串行化:當事務(wù) 2 嘗試將值更新為 2 時,它將被鎖定。它只能在事務(wù) 1 提交后才能繼續(xù)。因此,從事務(wù)1的角度來看,V1和V2的值為1,而V3的值為2。
在實現(xiàn)上,數(shù)據(jù)庫創(chuàng)建一個視圖,訪問是基于這個視圖的邏輯結(jié)果。
在可重復(fù)讀隔離級別中,該視圖在事務(wù)開始時創(chuàng)建,并在整個事務(wù)中使用。
在讀已提交隔離級別中,此視圖是在每個 SQL 語句執(zhí)行開始時創(chuàng)建的。
值得注意的是,讀未提交隔離級別直接返回記錄上的最新值,沒有視圖的概念,而序列化隔離級別則采用鎖定來直接防止并發(fā)訪問。
很明顯,不同隔離級別下的數(shù)據(jù)庫行為有所不同。事實上,Oracle 的默認隔離級別是已提交讀。因此,對于從Oracle 遷移到MySQL 的應(yīng)用程序來說,MySQL為了保持數(shù)據(jù)庫隔離級別的一致性,必須記住將 MySQL 的隔離級別設(shè)置為“已提交讀”。
事務(wù)隔離的實現(xiàn)
現(xiàn)在我們了解了事務(wù)隔離級別,讓我們仔細看看事務(wù)隔離是如何實現(xiàn)的。
我們先深入研究可重復(fù)讀隔離級別的細節(jié)。
在MySQL中,實際上,每條記錄在更新時都伴隨有一條回滾記錄。可以通過使用此回滾記錄訪問先前的狀態(tài)來獲取記錄上的最新值。
假設(shè)某個值依次從1過渡到2、3、4。在回滾日志中,您會發(fā)現(xiàn)類似以下的記錄:
當前值為4,但是查詢這條記錄時,不同時間開始的事務(wù)會有不同的read-views。
如圖所示,在read-views 1、2 和 3中,該記錄的值分別為1、2和4。同一條記錄在系統(tǒng)中可以有多個版本,這稱為多版本并發(fā)控制(MVCC)。
Read-View 1如果想獲取 value 1,必須按順序執(zhí)行 圖中所示的所有回滾操作。
此外,即使當前有另一個事務(wù)更改4為5,該事務(wù)也不會與與讀取視圖 1、2 和 3 關(guān)聯(lián)的事務(wù)發(fā)生沖突。
您可能想知道回滾日志何時被刪除,因為它們不能無限期地保留。答案是當不再需要它們時將其刪除。
換句話說,當沒有事務(wù)需要訪問回滾日志時,系統(tǒng)將確定可以刪除它們。
什么時候不再需要它們?當系統(tǒng)中沒有早于回滾日志的讀取視圖時會將他們刪除。
基于上面提供的解釋,我們來討論一下為什么建議盡可能避免長時間運行的事務(wù)。
長時間運行的事務(wù)會導(dǎo)致系統(tǒng)中出現(xiàn)非常多舊的事務(wù)視圖。由于這些事務(wù)可以隨時訪問數(shù)據(jù)庫中的任何數(shù)據(jù),因此它們可能需要的所有回滾記錄都必須保留在數(shù)據(jù)庫中,直到事務(wù)提交為止。這反過來又導(dǎo)致大量的存儲空間消耗。
在 MySQL 5.5 之前的版本中,回滾日志與數(shù)據(jù)字典一起存儲在 ibdata 文件中。即使長時間運行的事務(wù)最終提交并且回滾段被清除,文件大小也不會減少。
長時間運行的事務(wù)還會占用鎖資源,并有可能影響整個數(shù)據(jù)庫的性能。
開啟事務(wù)的方式
如前所述,長時間運行的交易存在潛在風(fēng)險,因此強烈建議盡可能避免它們。
在許多情況下,開發(fā)人員并沒有故意使用長時間運行的事務(wù);通常,這是由于誤用造成的。
MySQL提供了幾種發(fā)起事務(wù)的方式:
- BEGIN使用語句或顯式啟動事務(wù)START TRANSACTION。提交和回滾對應(yīng)的語句是COMMIT和ROLLBACK。
- using SET AUTOCOMMIT=0是一個禁用當前線程自動提交的命令。這意味著如果您只執(zhí)行一條 SELECT 語句,則會啟動一個事務(wù),并且不會自動提交。此事務(wù)將持續(xù)存在,直到您主動執(zhí)行COMMITorROLLBACK語句或與數(shù)據(jù)庫斷開連接。
SET AUTOCOMMIT=0某些客戶端連接框架可能會在連接成功后默認執(zhí)行命令。
這會導(dǎo)致后續(xù)查詢位于事務(wù)內(nèi),如果連接長時間保持打開狀態(tài),則可能會導(dǎo)致意外的長時間運行事務(wù)。
因此,建議通過語句SET AUTOCOMMIT=1顯式使用和啟動事務(wù)。






