這篇文章帶大家了解下MySQL中的事務,聊聊事務隔離性的實作原理,希望能夠給大家幫忙!
說到資料庫事務,大家腦子裡一定很容易蹦出一堆事務的相關知識,如事務的ACID特性,隔離級別,解決的問題(臟讀,不可重複讀,幻讀)等等,但是可能很少有人真正的清楚事務的這些特性又是怎麼實現的,為什麼要有四個隔離級別。
今天我們就先來聊聊MySQL中交易的隔離性的實作原理,後續還會繼續出文章分析其他特性的實作原理。
當然MySQL博大精深,文章疏漏之處在所難免,歡迎批評指正。
說明
MySQL的事務實作邏輯是位於引擎層的,且不是所有的引擎都支援事務的,以下的說明都是以InnoDB引擎為基準。
隔離性(isolation)指的是不同事務先後提交並執行後,最終呈現出來的效果是串行的,也就是說,對於事務來說,它在執行過程中,感知到的資料變化應該只有自己操作所引起的,不存在其他交易引發的資料變化。
隔離性解決的是並發交易出現的問題。
隔離性最簡單的實作方式就是各個事務都串列執行了,如果前面的事務還沒執行完畢,後面的交易就都等待。但是這樣的實作方式很明顯並發效率不高,並不適合在實際環境中使用。
為了解決上述問題,實現不同程度的並發控制,SQL的標準制定者提出了不同的隔離等級:未提交讀取(read uncommitted)、提交讀取(read committed)、可重複讀取(repeatable read)、序列化讀取(serializable)。其中最高級隔離等級就是序列化讀,而在其他隔離等級中,由於交易是並發執行的,所以或多或少允許出現一些問題。請參閱以下的矩陣表:
隔離等級(: 允許出現,-:不允許出現) | #髒讀 | 不可重複讀取 | 幻讀 |
---|---|---|---|
- | |||
- | |||
注意,MySQL的InnoDB引擎在可重複讀取層級透過間隙鎖定解決了幻讀問題,透過MVCC解決了不可重複讀取的問題,具體見下面的分析。
我們上面遇到的問題其實就是並發交易下的控制問題,解決並發交易最常見的方式就是悲觀並發控制了(也就是資料庫中的鎖)。標準SQL事務隔離等級的實現是依賴鎖定的,我們來看下具體是怎麼實現的:
則所』 | |
---|---|
交易對目前被讀取的資料不加鎖; | 交易在更新某資料的瞬間(就是發生更新的瞬間),必須先加 行級共享鎖定 ,直到交易結束才釋放。 |
交易對目前已讀取的資料加上 | 行級共享鎖定(當閱讀時才加鎖定),一旦讀完該行,立即釋放該行級共享鎖定;事務在更新某資料的瞬間(就是發生更新的瞬間),必須先對其加 行級排他鎖定 ,直到事務結束才釋放。 |
交易在讀取某資料的瞬間(就是開始讀取的瞬間),必須先將其加上 | 行層級共享鎖定,直到交易結束才釋放;交易在更新某資料的瞬間(就是發生更新的瞬間),必須先將其加 行級排他鎖定 ,直到事務結束才釋放。 |
交易在讀取資料時,必須先將其加上 | 表級共享鎖定 ,直到交易結束才釋放;交易在更新資料時,必須先對其加 表格級排他鎖定 ,直到交易結束才釋放。 |
事务隔离级别 | 实现方式 |
---|---|
未提交读(RU) | 事务对当前被读取的数据不加锁,都是当前读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级共享锁,直到事务结束才释放。 |
提交读(RC) | 事务对当前被读取的数据不加锁,且是快照读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record),直到事务结束才释放。 |
可重复读(RR) | 事务对当前被读取的数据不加锁,且是快照读; 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁(Record,GAP,Next-Key),直到事务结束才释放。 通过间隙锁,在这个级别MySQL就解决了幻读的问题 通过快照,在这个级别MySQL就解决了不可重复读的问题 |
序列化读(S) | 事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放,都是当前读; 事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。 |
可以看到,InnoDB通过MVCC很好的解决了读写冲突的问题,而且提前一个级别就解决了标准级别下会出现的幻读问题,大大提升了数据库的并发能力。
不可重复读:前后多次读取一行,数据内容不一致,针对其他事务的update和delete操作。为了解决这个问题,使用行共享锁,锁定到事务结束(也就是RR级别,当然MySQL使用MVCC在RC级别就解决了这个问题)
幻读:当同一个查询在不同时间生成不同的行集合时就是出现了幻读,针对的是其他事务的insert操作,为了解决这个问题,锁定整个表到事务结束(也就是S级别,当然MySQL使用间隙锁在RR级别就解决了这个问题)
网上很多文章提到幻读和提交读的时候,有的说幻读包括了delete的情况,有的说delete应该属于提交读的问题,那到底真相如何呢?我们实际来看下MySQL的官方文档(如下)
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a
SELECT
) is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html
可以看到,幻读针对的是结果集前后发生变化,所以看起来delete的情况应该归为幻读,但是我们实际分析下上面列出的标准SQL在RR级别的实现原理就知道,标准SQL的RR级别是会对查到的数据行加行共享锁,所以这时候其他事务想删除这些数据行其实是做不到的,所以在RR下,不会出现因delete而出现幻读现象,也就是幻读不包含delete的情况。
网上很多文章会说MVCC或者MVCC+间隙锁解决了幻读问题,实际上MVCC并不能解决幻读问题。如以下的例子:
begin; #假设users表为空,下面查出来的数据为空 select * from users; #没有加锁 #此时另一个事务提交了,且插入了一条id=1的数据 select * from users; #读快照,查出来的数据为空 update users set name='mysql' where id=1;#update是当前读,所以更新成功,并生成一个更新的快照 select * from users; #读快照,查出来id为1的一条记录,因为MVCC可以查到当前事务生成的快照 commit;
可以看到前后查出来的数据行不一致,发生了幻读。所以说只有MVCC是不能解决幻读问题的,解决幻读问题靠的是间隙锁。如下:
begin; #假设users表为空,下面查出来的数据为空 select * from users lock in share mode; #加上共享锁 #此时另一个事务B想提交且插入了一条id=1的数据,由于有间隙锁,所以要等待 select * from users; #读快照,查出来的数据为空 update users set name='mysql' where id=1;#update是当前读,由于不存在数据,不进行更新 select * from users; #读快照,查出来的数据为空 commit; #事务B提交成功并插入数据
注意,RR级别下想解决幻读问题,需要我们显式加锁,不然查询的时候还是不会加锁的。
原文地址:https://segmentfault.com/a/1190000025156465
作者: X先生
【相关推荐:mysql视频教程】
以上是淺析MySQL中的事務隔離級別,聊聊其實現原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!