本篇文章帶大家聊聊MySQL中的事務(wù)特性,介紹一下多版本并發(fā)控制MVCC實(shí)現(xiàn)原理,希望對大家有所幫助!
原子性(atomicity)
事務(wù)的原子性指的是構(gòu)成事務(wù)的所有操作要么全部執(zhí)行成功,要么全部執(zhí)行是失敗。
(資料圖片)
一致性(consistency)
事務(wù)的一致性指的是事務(wù)執(zhí)行之前和執(zhí)行之后,數(shù)據(jù)始終處于一致的狀態(tài)。
隔離性(isolation)
事務(wù)的隔離性指的是并發(fā)執(zhí)行的兩個(gè)事務(wù)之間互不干擾,也就是說,一個(gè)事務(wù)執(zhí)行的過程中是無法看到其他事務(wù)運(yùn)行過程的中間狀態(tài)的。
??注意:MySQL是通過鎖個(gè)MVCC機(jī)制來保證事務(wù)的隔離性。
持久性(duration)
事務(wù)的持久性指的是一旦事務(wù)被提交后,此事務(wù)對數(shù)據(jù)的更改操作會被持久化到數(shù)據(jù)庫中,并且不會被回滾。
本地事務(wù)
通?;陉P(guān)系型數(shù)據(jù)庫控制的事務(wù)可以稱作為傳統(tǒng)事務(wù)或者本地事務(wù)。
本地事務(wù)的執(zhí)行流程
客戶端開始事務(wù)操作之前,需要開啟一個(gè)連接回話;
開始回話之后,客戶端發(fā)起開啟事務(wù)的指令;
事務(wù)開始后,客戶端發(fā)送各種SQL語句處理數(shù)據(jù);
正常情況下,客戶端會發(fā)起提交事務(wù)的指令,如果發(fā)生異常情況,客戶端會發(fā)起回滾事務(wù)命令;
上述流程完成后,關(guān)閉會話。
?本地事務(wù)是有資源管理器在本地進(jìn)行管理的。
本地事務(wù)的缺點(diǎn)在于:
不具備分布式事務(wù)的處理能力一次事務(wù)過程只能連接一個(gè)支持事務(wù)的數(shù)據(jù)庫,既不能用于多個(gè)事務(wù)性數(shù)據(jù)庫。當(dāng)兩個(gè)會在兩個(gè)以上的事務(wù)同時(shí)操作同一行數(shù)據(jù),并給予最初選定的值更新該行數(shù)據(jù)時(shí),視為事務(wù)之間是無法感知彼此的存在的,所以會出現(xiàn)最后的更新操作會覆蓋之前其他事務(wù)完成的更新操作。
舉個(gè)例子:
張三的賬戶為100元,當(dāng)前有兩個(gè)事務(wù):事務(wù)1和事務(wù)2,事務(wù)1是將張三賬戶余額增加100元,事務(wù)2是將張三余額增加200,起初事務(wù)1和事務(wù)2同時(shí)讀到張三的賬戶余額都是100元,然后事務(wù)1和事務(wù)2分別更新張三月,假設(shè)事務(wù)1先于事務(wù)2提交,但是最近兩個(gè)事務(wù)都提交后張三的余額為300元(正常情況應(yīng)該是400元),也就是說:后提交的事務(wù)2覆蓋了事務(wù)1的更新操作,這就是所謂的更新丟失,更新丟失(臟寫)本質(zhì)上是寫操作的沖突,然而解決臟寫的方式是讓每個(gè)事務(wù)串行方式執(zhí)行,保證事務(wù)按照一定的順序執(zhí)行寫操作。
一個(gè)事務(wù)讀取到另一個(gè)事務(wù)未提交的數(shù)據(jù)。比如:事務(wù)1正在對張三的余額增加100元,在這個(gè)事務(wù)沒提交之前,另一個(gè)事務(wù)2讀取了正在修改的這條數(shù)據(jù),如果沒有事務(wù)的控制下,第二條事務(wù)就會讀取到?jīng)]有被提交的臟數(shù)據(jù),并且對臟數(shù)據(jù)叢下一步的處理,此時(shí)就會產(chǎn)生未提交數(shù)據(jù)的依賴關(guān)系,通常這種現(xiàn)象被稱為 臟讀,也就是說:臟讀是一個(gè)事務(wù)讀取了另一個(gè)事務(wù)沒提交的數(shù)據(jù)。
?臟讀本質(zhì)上是讀寫操作的沖突,解決方法是先寫后讀,也就是寫完之后再讀。
一個(gè)事務(wù)讀取了某些數(shù)據(jù),在一段時(shí)間后,這個(gè)事務(wù)再次讀取之前讀過的數(shù)據(jù),此時(shí)發(fā)現(xiàn)讀取的數(shù)據(jù)發(fā)生了變化,或者其中某些數(shù)據(jù)記錄已經(jīng)被刪除,這種現(xiàn)象被稱為:不可重復(fù)讀,即同一個(gè)事務(wù),使用同樣的查詢語句,在不同時(shí)刻讀取到的結(jié)果不一致。
不可重復(fù)讀的本質(zhì)上也是讀寫操作沖突,解決方法是先讀后寫,也就是讀完之后再寫。
一個(gè)事務(wù)按照相同的查詢條件重新讀取之前讀過的數(shù)據(jù),此時(shí)發(fā)現(xiàn)其他事務(wù)插入了滿足當(dāng)前查詢條件的新數(shù)據(jù),數(shù)據(jù)結(jié)果集變多,這種現(xiàn)象稱為幻讀,即一個(gè)事務(wù)兩次讀取一個(gè)范圍的數(shù)據(jù)記錄,兩次讀到的結(jié)果不同。
幻讀的本質(zhì)上也是讀寫操作沖突,解決方法是先讀后寫,也就是讀完之后再寫。
那到這里,很多小伙伴就有疑問,同樣本質(zhì)都是讀寫操作沖突,解決方法是先讀后寫,不可重復(fù)讀和幻讀到底有何區(qū)別?,我下面見簡單解釋一下:
1?? 不可重復(fù)讀的重點(diǎn)在于更新和刪除操作,而幻讀的重點(diǎn)在于插入操作。
2?? MySQL使用鎖機(jī)制實(shí)現(xiàn)事務(wù)的隔離級別時(shí),在可重復(fù)讀隔離級別中,SQL語句第一個(gè)讀取到數(shù)據(jù)后,會將相應(yīng)的數(shù)據(jù)加鎖,使得其他事務(wù)無法修改和刪除這些數(shù)據(jù),通過鎖的方式實(shí)現(xiàn)可重復(fù)讀。但是這種方法無法對新數(shù)據(jù)的插入加鎖,如果事務(wù)1讀取了數(shù)據(jù),或者修改和刪除了數(shù)據(jù),事務(wù)2還可以進(jìn)行插入操作,導(dǎo)致事務(wù)1莫名其妙地多了一條之前沒有的數(shù)據(jù),這就是幻讀。
3?? 所以,幻讀是無法通過鎖機(jī)制來避免,需要使用串行化的事務(wù)隔離級別,但是這種事務(wù)隔離級別會大大降低數(shù)據(jù)庫的并發(fā)能力。
?MySQL是通過MVCC(多版本并發(fā)控制)機(jī)制來避免不可重復(fù)讀和幻讀的。
使用下面的命令可以查詢?nèi)旨墑e和會話級別的事務(wù)隔離級別:
# 默認(rèn)數(shù)據(jù)庫的事務(wù)隔離級別為:可重復(fù)讀(REPEATABLE-READ)SELECT @@global.tx_isolation;SELECT @@session.tx_isolation;SELECT @@tx_isolation;
Multi-Version Concurrency Control多版本并發(fā)控制,MVCC是一種并發(fā)控制的方法,一般在數(shù)據(jù)庫管理系統(tǒng)中,實(shí)現(xiàn)對數(shù)據(jù)庫的并發(fā)訪問。
可以將MVCC看成行級別鎖的一種妥協(xié),它在許多情況下避免了使用鎖,同時(shí)可以提供更小的開銷。根據(jù)實(shí)現(xiàn)的不同,它可以允許非阻塞式讀,在寫操作進(jìn)行時(shí)只鎖定必要的記錄。
MVCC的是通過保存數(shù)據(jù)澡某個(gè)時(shí)間點(diǎn)的快照來實(shí)現(xiàn)的,也就是說,不管事務(wù)執(zhí)行多長的時(shí)間,每個(gè)事務(wù)看到的數(shù)據(jù)都是一致的。根據(jù)事務(wù)的開始時(shí)間不同,每個(gè)事務(wù)對同一張表,同一時(shí)刻看到的數(shù)據(jù)可能是不一樣的。InnonDB主要通過為每一行記錄添加兩個(gè)額外的隱藏的值來實(shí)現(xiàn)MVCC,這兩個(gè)值一個(gè)記錄這行數(shù)據(jù)何時(shí)被創(chuàng)建,另外一個(gè)記錄這行數(shù)據(jù)何時(shí)過期(或者被刪除)。但是InnoDB并不存儲這些事件發(fā)生時(shí)的 實(shí)際時(shí)間,相反它只存儲這些事件發(fā)生時(shí)的系統(tǒng) 版本號(version)。這是一個(gè)隨著事務(wù)的創(chuàng)建而不斷增長的數(shù)字。每個(gè)事務(wù)在事務(wù)開始時(shí)會記錄它自己的系統(tǒng)版本號。每個(gè)查詢必須去檢查每行數(shù)據(jù)的版本號與事務(wù)的版本號是否相同。
《高性能MySQL》書籍中介紹到:MVCC只在REPEATABLE READ和READ COMMITTIED兩個(gè)隔離級別下工作,其他兩個(gè)隔離級別都和MVCC不兼容,因?yàn)?code>READ UNCOMMITTED總是讀取最新的數(shù)據(jù)行,而不符合當(dāng)前事務(wù)版本數(shù)據(jù)行,而SERIALIZABLE串行化隔離級別則會對所有讀取的行都加鎖。
接下來舉個(gè)例子說明在可重復(fù)讀事務(wù)隔離級別下,MVCC機(jī)制是如何完成增刪改查操作的。
在查詢操作中,InnoDB存儲引擎跟根據(jù)以下兩個(gè)條件查詢對應(yīng)的行記錄,只有滿足對應(yīng)條件才會被返回:
1?? 只查找不晚于當(dāng)前事務(wù)版本的數(shù)據(jù)行,也就是說,InnoDB存儲引擎只會查找版本號小于或者等于當(dāng)前事務(wù)版本的數(shù)據(jù)行,這些數(shù)據(jù)行要么在該事務(wù)開始前就存在,要么就是事務(wù)本身插入或者更新的行。2?? 對于數(shù)據(jù)刪除,數(shù)據(jù)行刪除的版本要么還沒有被定義,要么大于當(dāng)前事務(wù)的版本號,只有這樣才能確保事務(wù)讀到的行,在事務(wù)開始前并沒有被刪除。
舉個(gè)例子,存在 事務(wù)A和 事務(wù)B兩個(gè)事務(wù),事務(wù)A中存在兩套相同的SELECT語句,事務(wù)B存在一條UPDATE語句,事務(wù)A的第一條查詢語句在事務(wù)B提交之前執(zhí)行,第二條查詢語句在 事務(wù)B提交之后執(zhí)行,事務(wù)A如下所示:
-- 事務(wù)A操作START TRANSACTION;SELECT * FROM account WHERE id = 1; //在事務(wù)B提交之前執(zhí)行SELECT * FROM account WHERE id = 1; //在事務(wù)B提交之后執(zhí)行COMMIT;
事務(wù)B:
-- 事務(wù)B操作START TRANSACTION;UPDATE account SET balance = balance+100 WHERE id = 1;COMMIT;
?結(jié)論:如果沒有使用MVCC機(jī)制,則事務(wù)A中的第一條SELECT語句讀取的數(shù)據(jù)是修改前的數(shù)據(jù),而第二條SELECT語句讀取的是修改后的數(shù)據(jù),兩次讀取的數(shù)據(jù)不一致,想想,那不就亂了嗎?? 如果使用了MVCC機(jī)制,無論事務(wù)B如何修改數(shù)據(jù),事務(wù)A的兩條查詢語句的到的結(jié)果始終是一致的。
插入操作(SELECT)在插入操作中,InnoDB會將新插入的每一條行記錄的當(dāng)前系統(tǒng)版本包保存為行版本號。
比如:向account表插入一條數(shù)據(jù),同時(shí)MVCC的兩個(gè)版本號分別為create_version和delete_version,create_version代表創(chuàng)建行的版本號,delete_version代表刪除行的版本號,另外還有一個(gè)事務(wù)ID字段,如下面所示:
INSERT INTO account(id, name, balance) values(1001, "austin", 100);
對應(yīng)的版本號信息如下表:
| id | name | balance | transaction_id | create_version | delete_version |
|---|---|---|---|---|---|
| 1001 | austin | 100 | 1 | 1 | 未定義 |
可以看出,當(dāng)向數(shù)據(jù)表新增記錄時(shí),需要設(shè)置保存行的版本號,而刪除行的版本號未定義。
更新操作(SELECT)在更新操作中,InnoDB存儲引擎會插入一行新記錄,并保存當(dāng)前系統(tǒng)的版本號為新記錄行的版本號,同時(shí)保存當(dāng)前系統(tǒng)的版本號到原來數(shù)據(jù)行作為刪除標(biāo)識。比如:將account數(shù)據(jù)表中id為1001的用戶賬戶月增加100元,對應(yīng)SQL如下:
UPDATE account SET balance = balance+100 WHERE id = 1001;
執(zhí)行SQL, 在MVCC機(jī)制下的更新操作如下表所示:
| id | name | balance | transaction_id | create_version | delete_version |
|---|---|---|---|---|---|
| 1001 | austin | 100 | 1 | 1 | 2 |
| 1001 | austin | 200 | 2 | 2 | 未定義 |
可以明顯看出,執(zhí)行更新操作時(shí),MVCC機(jī)制是先將原來的數(shù)據(jù)復(fù)制一份,將balance字段增加100后,再講create_version字段的值設(shè)置為當(dāng)前系統(tǒng)的版本號,而delete_version字段的值未定義。
注意的是:原來的行會被復(fù)制到Undo Log中。
刪除操作(SELECT)在刪除操作中,InnoDB存儲引擎會保存刪除的每一個(gè)行記錄當(dāng)前的系統(tǒng)版本號,作為刪除標(biāo)識。比如:刪除account數(shù)據(jù)表中id為1001的數(shù)據(jù),SQL如下所示:
DELETE FROM account WHERE id = 1001;
對應(yīng)MVCC機(jī)制下的刪除操作如下表所示:
| id | name | balance | transaction_id | create_version | delete_version |
|---|---|---|---|---|---|
| 1001 | austin | 200 | 3 | 2 | 3 |
可以看出,當(dāng)刪除數(shù)據(jù)表數(shù)據(jù)行時(shí),MVCC機(jī)制會將當(dāng)前系統(tǒng)的版本號寫入刪除數(shù)據(jù)行版本字段delete_version中,以此來表示當(dāng)前數(shù)據(jù)行已經(jīng)被刪除。
本文主要講述了事務(wù)的特性、類型和本地事務(wù)和分布式事務(wù)的區(qū)別,通過不同的示例解釋并發(fā)事務(wù)帶來的更新丟失、臟讀、不可重復(fù)讀、幻讀的問題,同時(shí)介紹了多版本并發(fā)控制MVCC的實(shí)現(xiàn)原理,如果文章對你有幫助,感謝點(diǎn)贊?+評論?+收藏?,我是:???austin流川楓,我們下期見!
以上就是深入聊聊MySQL中的事務(wù)特性和實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多請關(guān)注php中文網(wǎng)其它相關(guān)文章!
關(guān)鍵詞: mysql