這篇文章帶大家聊聊MySQL中的事務特性,介紹一下多版本並發控制MVCC實現原理,希望對大家有幫助!
# 一般指的是邏輯上的一組運算,或者作為單一邏輯單元執行的一系列操作,一個事務中的所有操作會被封裝成一個不可分割的執行單元,這個單元的所有操作要么全部執行成功,要么全部執行失敗,只要其中任意一個操作執行失敗,整個事務就會執行回滾操作。
##原子性(atomicity)
交易的原子性指的是所有構成事務的操作要麼全部執行成功,要麼全部執行是失敗。一致性(consistency)
交易的一致性指的是事務執行之前和執行之後,資料始終處於一致的狀態。隔離性(isolation)
交易的隔離性指的是並發執行的兩個交易之間互不干擾,也就是說,一個交易執行的過程中是無法看到其他事務運行過程的中間狀態的。?注意:MySQL
是透過鎖定
MVCC機制來保證交易的隔離。
持久性(duration)
交易的持久性指的是一旦交易被提交後,此交易對資料的變更操作會被持久化到資料庫中,並且不會被回滾。通常基於關係型資料庫控制的事務可以稱為傳統事務或本地事務。
本地事務的執行流程
本地交易的缺點在於:
不具備分散式交易的處理能力
張三的帳戶為100元,目前有兩個事務:事務1和事務2,事務1是將張三帳戶餘額增加100元,事務2是將張三餘額增加200,起初事務1和事務2同時讀到張三的帳戶餘額都是100元,然後事務1和事務2分別更新張三月,假設事務1先於事務2提交,但是最近兩個事務都提交後張三的餘額為300元(正常情況應該是400元),
也就是說:後提交的事務2覆蓋了事務1的更新操作,這就是所謂的更新遺失,更新遺失(髒寫)本質上是寫操作的衝突,然而解決髒寫的方式是讓每個事務串列方式執行,保證事務按照一定的順序執行寫操作。
#
一個交易讀取到另一個交易未提交的資料。例如:事務1正在對張三的餘額增加100元,在這個事務沒提交之前,另一個事務2讀取了正在修改的這條數據,如果沒有事務的控制下,第二條事務就會讀取到沒有被提交的髒數據,並且對髒數據叢下一步的處理,此時就會產生未提交數據的依賴關係,通常這種現像被稱為臟讀
,也就是說:髒讀是一個事務讀取了另一個事務沒提交的資料。
?髒讀本質上是讀寫操作的衝突,解決方法是先寫後讀,也就是寫完後再讀。
一個事務讀取了某些數據,在一段時間後,這個事務再次讀取之前讀過的數據,此時發現讀取的資料發生了變化,或者其中某些資料記錄已經被刪除,這種現像被稱為:不可重複讀,即同一個事務,使用同樣的查詢語句,在不同時刻讀取到的結果不一致。
不可重複讀取的本質上也是讀寫操作衝突,解決方法是先讀後寫,也就是讀完之後再寫。
一個事務按照相同的查詢條件重新讀取之前讀過的數據,此時發現其他事務插入了滿足當前查詢條件的新數據,數據結果集變多,這種現象稱為幻讀,即一個事務兩次讀取一個範圍的數據記錄,兩次讀到的結果不同。
幻讀的本質上也是讀寫操作衝突,解決方法是先讀後寫,也就是讀完之後再寫。
那到這裡,很多小夥伴就有疑問,同樣本質都是讀寫操作衝突,解決方法是先讀後寫,不可重複讀和幻讀到底有何區別?,我以下見簡單解釋:
1️⃣ 不可重複讀的重點在於更新和刪除操作,而幻讀的重點在於插入操作。
2️⃣ MySQL使用鎖定機制實作交易的隔離等級時,在可重複讀取隔離等級中,SQL語句第一個讀取到資料後,會將對應的資料加鎖,使得其他事務無法修改和刪除這些數據,透過鎖的方式實現可重複讀取。但是這種方法無法對新數據的插入加鎖,如果事務1讀取了數據,或者修改和刪除了數據,事務2還可以進行插入操作,導致事務1莫名其妙地多了一條之前沒有的數據,這就是幻讀。
3️⃣ 所以,幻讀是無法透過鎖定機制來避免,需要使用串行化的事務隔離級別,但是這種事務隔離級別會大大降低資料庫的並發能力。
✔MySQL是透過MVCC(多版本的同時控制)機制來避免無法重複讀取和幻讀。
#使用下面的指令可以查詢全域層級和會話層級的事務隔離等級:
# 默认数据库的事务隔离级别为:可重复读(REPEATABLE-READ) SELECT @@global.tx_isolation; SELECT @@session.tx_isolation; SELECT @@tx_isolation;
Multi-Version Concurrency Control
多版本並發控制,MVCC
是一種並發控制的方法,一般在資料庫管理系統中,實現對資料庫的並發存取。
可以將MVCC
看成行級鎖定的一種妥協,它在許多情況下避免了使用鎖,同時可以提供更小的開銷。根據實現的不同,它可以允許非阻塞式讀,在寫入操作進行時只鎖定必要的記錄。
MVCC
的是通过保存数据澡某个时间点的快照来实现的,也就是说,不管事务执行多长的时间,每个事务看到的数据都是一致的。根据事务的开始时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。InnonDB
主要通过为每一行记录添加两个额外的隐藏的值来实现MVCC
,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。但是InnoDB
并不存储这些事件发生时的 实际时间 ,相反它只存储这些事件发生时的系统 版本号(version) 。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。
《高性能MySQL》书籍中介绍到:
MVCC
只在REPEATABLE READ
和READ COMMITTIED
两个隔离级别下工作,其他两个隔离级别都和MVCC
不兼容,因为READ UNCOMMITTED
总是读取最新的数据行,而不符合当前事务版本数据行,而SERIALIZABLE
串行化隔离级别则会对所有读取的行都加锁。
接下来举个例子说明在可重复读事务隔离级别下,MVCC
机制是如何完成增删改查操作的。
在查询操作中,InnoDB存储引擎跟根据以下两个条件查询对应的行记录,只有满足对应条件才会被返回:
1️⃣ 只查找不晚于当前事务版本的数据行,也就是说,InnoDB存储引擎只会查找版本号小于或者等于当前事务版本的数据行,这些数据行要么在该事务开始前就存在,要么就是事务本身插入或者更新的行。
2️⃣ 对于数据删除,数据行删除的版本要么还没有被定义,要么大于当前事务的版本号,只有这样才能确保事务读到的行,在事务开始前并没有被删除。
举个例子,存在 事务A 和 事务B 两个事务,事务A中存在两套相同的SELECT
语句,事务B存在一条UPDATE
语句,事务A 的第一条查询语句在事务B提交之前执行,第二条查询语句在 事务B 提交之后执行,事务A 如下所示:
-- 事务A操作 START TRANSACTION; SELECT * FROM account WHERE id = 1; //在事务B提交之前执行 SELECT * FROM account WHERE id = 1; //在事务B提交之后执行 COMMIT;
事务B:
-- 事务B操作 START TRANSACTION; UPDATE account SET balance = balance+100 WHERE id = 1; COMMIT;
✔结论:如果没有使用MVCC机制,则事务A中的第一条SELECT语句读取的数据是修改前的数据,而第二条SELECT语句读取的是修改后的数据,两次读取的数据不一致,想想,那不就乱了吗?? 如果使用了MVCC机制,无论事务B如何修改数据,事务A的两条查询语句的到的结果始终是一致的。
在插入操作中,InnoDB
会将新插入的每一条行记录的当前系统版本包保存为行版本号。
比如:向account
表插入一条数据,同时MVCC
的两个版本号分别为create_version
和delete_version
,create_version
代表创建行的版本号,delete_version
代表删除行的版本号,另外还有一个事务ID字段,如下面所示:
INSERT INTO account(id, name, balance) values(1001, 'austin', 100);
对应的版本号信息如下表:
id | name | balance | transaction_id | create_version | delete_version |
---|---|---|---|---|---|
1001 | austin | 100 | 1 | 1 | 未定义 |
可以看出,当向数据表新增记录时,需要设置保存行的版本号,而删除行的版本号未定义。
在更新操作中,InnoDB
存储引擎会插入一行新记录,并保存当前系统的版本号为新记录行的版本号,同时保存当前系统的版本号到原来数据行作为删除标识。比如:将account数据表中id为1001的用户账户月增加100元,对应SQL如下:
UPDATE account SET balance = balance+100 WHERE id = 1001;
执行SQL, 在MVCC机制下的更新操作如下表所示:
id | name | balance | transaction_id | create_version | delete_version |
---|---|---|---|---|---|
1001 | austin | 100 | 1 | 1 | 2 |
1001 | austin | 200 | 2 | 2 | 未定义 |
可以明显看出,执行更新操作时,MVCC
机制是先将原来的数据复制一份,将balance
字段增加100后,再讲create_version
字段的值设置为当前系统的版本号,而delete_version
字段的值未定义。
注意的是:原来的行会被复制到Undo Log中。
在删除操作中,InnoDB存储引擎会保存删除的每一个行记录当前的系统版本号,作为删除标识。比如:删除account数据表中id为1001的数据,SQL如下所示:
DELETE FROM account WHERE id = 1001;
对应MVCC机制下的删除操作如下表所示:
id | name | balance | transaction_id | create_version | delete_version |
---|---|---|---|---|---|
1001 | austin | 200 | 3 | 2 | 3 |
可以看出,当删除数据表数据行时,MVCC机制会将当前系统的版本号写入删除数据行版本字段delete_version中,以此来表示当前数据行已经被删除。
本文主要讲述了事务的特性、类型和本地事务和分布式事务的区别,通过不同的示例解释并发事务带来的更新丢失、脏读、不可重复读、幻读的问题,同时介绍了多版本并发控制MVCC的实现原理,如果文章对你有帮助,感谢点赞?+评论?+收藏❤,我是:??austin流川枫,我们下期见!
【相关推荐:mysql视频教程】
以上是深入聊聊MySQL中的事務特性與實作原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!