MySQL MVVC多版本並發控制如何實現

WBOY
發布: 2023-05-31 13:14:01
轉載
1644 人瀏覽過

一、概述​​

多版本並發控制(MVCC)是一種並發控制的技術。實作資料庫並發控制的MVVC與undo log中的版本鏈密不可分,它透過維護資料行多個版本來完成。

簡單的說就是當前事務查詢另一個事務正在更改的行(如果此時讀取就會發生髒讀),不用加鎖等待,而是讀取該資料的歷史版本,降低響應時間。

MVVC是透過undo log和Read View兩種技術來實現的。

二、快照讀取與目前讀取

MVCC在MySQL InnoDB中的實作主要是為了提高資料庫並發效能,用更好的方式去處理讀取-寫入衝突,做到即使有讀寫衝突時,也能做到不加鎖,非阻塞並發讀,而這個讀指的就是快照讀, 而非當前讀。目前讀取其實是一種加鎖的操作。

1.目前讀取

為了確保讀取的記錄是最新的數據,並且避免其他並發事務修改該記錄,在讀取時需要對該記錄進行加鎖。

加鎖的讀稱為目前讀,還有資料的增刪改都是要先讀取資料的,而這個讀取過程也是目前讀。

SELECT * FROM t LOCK IN SHARE MODE; # 共享锁
SELECT * FROM t FOR UPDATE; # 排他锁
UPDATE SET t..
登入後複製

2.快照讀

快照讀又叫一致性讀,讀取的是資料行的快照版本。在MySQL中,普通的select語句(不加for update或lock in share mode的select語句)預設就是使用的快照讀,不加鎖。

SELECT * FROM table WHERE ...
登入後複製

之所以這樣,是因為快照讀取可以避免加鎖操作,並降低開銷。

當交易的隔離等級是串列時,快照讀取就沒有用了,會退化為目前讀取。

三、隔離等級與版本鏈複習

隔離等級:

在MySQL中預設的隔離等級就是可重複讀取RR,可以解決不可重複讀取問題,在MySQL中,特別的還額外支援解決幻讀問題。

它是如何解決幻讀問題的呢?有兩種方式:

  • 使用間隙鎖和臨鍵鎖解決,簡而言之就是加鎖,在此期間其他事務不能夠插入資料

  • #MVCC方式,無須加鎖,消耗低(缺點是沒有完全解決幻讀問題)。

undo log版本鏈:

對應InnoDB來說,叢集索引中的每個記錄都包含了兩個必要的隱藏欄位:

  • trx_id:每次一個交易對某條叢集索引記錄進行變更時,都會把該交易的交易id賦值給trx_id隱藏欄位。

  • roll_pointer:回滾指針,每次修改數據時,都會把舊數據放入undo log日誌中,新的數據指向該舊數據,做成一個版本鏈,該指針欄位就稱為回滾指針,透過該指針可以找到修改前的資料。

範例:

有一個id為8的事務創建了一條數據,那麼該記錄的示意圖大概如下:

MySQL MVVC多版本並發控制如何實現

假設之後兩個id分別為10、20的交易對這條記錄進行update操作,流程如下:

##交易10事務20BEGIN;  BEGIN;UPDATE student SET name='李四' WHERE id=1; UPDATE student SET name='王五' WHERE id=1; COMMIT;  UPDATE student SET name='趙六' WHERE id=1; UPDATE student SET name='錢七' WHERE id=1; COMMIT;#

每个修改都会生成一个undo log日志,并与其他日志相互链接形成版本链,从而呈现出该条数据的图示

MySQL MVVC多版本並發控制如何實現

每个版本中还包含生成该版本时对应的事务id 。

四、Read View

有了undo log就可以读取到记录的历史版本,那么在什么情况下,读取哪个版本的记录呢?这就用到了Read View,它帮我们解决了行的可见性问题。

读视图是指在使用MVCC机制进行快照读操作时产生的事务视图。这个视图是对当前数据库中所有活跃的、尚未提交的事务ID列表进行拍照的。

1.实现原理

四种隔离级别里,读未提交和串行化是不会使用MVVC的,因为读未提交直接读取某个数据的最新数据即可,串行化是通过加锁来读的。

读已提交和可重复读都必须保证读到的数据都是其他事务提交了的,所以,其他事务修改了数据但是还未提交,我们不能够访问该数据,但可以通过MVVC机制读取该记录的历史版本,核心问题就是需要判断版本链中的哪条历史版本是当前事务可见的,这也是ReadView要解决的问题。

Read View包含4个比较重要的内容:

  • creator_trx_id:创建这个Read View的事务id,Read View和事务是一一对应的。

当事务对表中的记录作出修改时,才会分配一个事务ID,否则如果事务仅进行读取操作,则该事务的ID默认为0。

  • trx_ids:表示在生成Read View时当前系统中活跃的事务id列表。提交了的事务不在其中。

  • up_limit_id:活跃的事务中最小的事务id。

  • low_limit_id:表示生成Read View时系统应该分配给下一个事务的id值,同样也表示系统中最大的事务id值。

请注意,低限制事务ID不一定是trx_ids中的最大值,因为事务ID是按递增顺序分配的。例如,有三个事务的id分别为1、2、5,其中id为5的事务提交了。那么一个新的读事务在生成ReadView时, trx_ids就包括1和2,up_limit_id的值就是1,low_limit_id的值就是6。

MySQL MVVC多版本並發控制如何實現

2.Read View规则

MySQL MVVC多版本並發控制如何實現

版本链

当某个事务有了Read View,访问某条记录时,需要按照下面的步骤判断该记录的哪个版本可见:

  • 如果该版本记录的trx_id和Read View的creator_trx_id相同,意味着该版本的记录是由当前事务修改的,因此该版本可以被当前事务访问

  • 如果该版本记录的trx_id小于Read View的up_limit_id,证明当前事务生成Read View时,此事务已经提交了,所以当前事务可以读取该版本。

  • 如果该版本的trx_id大于等于low_limit_id,证明生成该版本的事务在当前事务生成Read View之后才开启,所以该版本不可以被当前事务访问。

  • 如果被访问版本的trx_id属性值在ReadView的up_limit_id和low_limit_id之间,那就需要判断一下trx_id属性值是不是在trx_ids列表中,如果不在的话才能访问,否则不能访问。

3.整体流程

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

  • 首先获取事务自己的版本号,也就是事务ID;

  • 获取 ReadView;

  • 查询得到的数据,然后与 ReadView 中的事务版本号进行比较;

  • 如果不符合 ReadView 规则,就需要从Undo Log中获取历史快照;

  • 最后返回符合规则的数据。

在隔离级别为读已提交时,一个事务中的每一次SELECT查询都会重新获取一次Read View,而可重复读是第一SELECT操作才会生成Read View,之后的查询操作复用这一个。

导致这两种的差距是因为:可重复读要保证一个事务中相同的SELECT读取的内容是相同的。

MySQL MVVC多版本並發控制如何實現

五、举例

1.READ

COMMITTED隔离级别下

现在有两个事务id分别为10、20的事务在执行:

-- id为10的事务
begin;
update t set name='李四' where id=1;
update t set name='王五' where id=1;
-- id为20的事务
更新其他行的数据
登入後複製

此刻,表中id为1的记录得到的版本链表如下所示:

MySQL MVVC多版本並發控制如何實現

此时新来一个事务执行如下操作:

begin;
select * from t where id=1;
-- 事务10、20未提交
登入後複製

查询到的结果为张三。

具体的过程如下:

  • 在执行select语句前,先生成一个Read View,Read View的creator_trx_id为0,trx_ids列表的内容是[10,20],up_limit_id为10,low_limit_id为21。

  • 查询name为王五的最新版本的记录,按规则进行对比,因为trx_id为10,10刚好是trx_ids中的记录,所以这条记录对当前事务不可见,根据回滚指针得到下一个版本

  • 下一个版本name为李四,也不行

  • 继续找到name为张三的版本,trx_id为8,8小于up_limit_id,所以该版本对当前事务可见,得到最终结果

接下来,再将id为10的事务进行commit提交。然后id为20的事务来更新记录:

begin;
-- id为20的事务
update t set name='赵六' where id=1;
update t set name='钱七' where id=1;
登入後複製

此时版本链更新为:

MySQL MVVC多版本並發控制如何實現

再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个id 为1的记录,得到的结果为name=王五的那条记录。执行过程如下:

  • 生成Read View,Read View的creator_trx_id为0,trx_ids列表的内容是[20],up_limit_id为20,low_limit_id为21。

  • 因为前两个版本的记录trx_id为20,存在trx_ids中,所以跳过

  • 到第三条记录时,trx_id为10,小于20,可以读取,所以最终结果为王五

注意:READ COMMITTED,每次读取数据前都生成一个新的ReadView。

2.REPEATABLE READ隔离级别下

假如此时id为10的事务和id为20的事务正在修改,都未提交,修改内容和前面的一样,但是还未提交,此时当前事务做一个查询。

MySQL MVVC多版本並發控制如何實現

步骤为:

  • 生成Read View,Read View的creator_trx_id为0,trx_ids列表的内容是[10,20],up_limit_id为10,low_limit_id为21。

  • trx_id为10和20的都不满足要求

  • 最后查找到name为张三的历史版本的数据

此时,id为10的记录提交事务。

MySQL MVVC多版本並發控制如何實現

当前事务又需要select id为1的记录,步骤为:

  • 因为是可重复读,且第一次select已经生成过Read View了,所有会复用它,不重新生成。

  • 所以trx_id为10和20的记录依旧不符合规则,最终得到的数据还是张三,符合可重复读的规范

注意:REPEATABLE READ,每次读取都复用第一次生成的Read View

3.如何解决幻读

假设现在有一条数据,id为1

MySQL MVVC多版本並發控制如何實現

当前活跃的事务有10和20。

此时当前事务启动了,执行如下SQL语句:

begin;
select * from student where id>=1;
登入後複製

在开始前生成Read View,内容如下:creator_trx_id=0,trx_ids= [10,20] , up_limit_id=10, low_limit_id=21。

由于id大于等于1的数据只有一个,且该数据的trx_id为8,小于up_limit_id,所以可以读取到。

在这之后id为10的事务新增了一行数据,增加了id为2的数据,且提交了。

MySQL MVVC多版本並發控制如何實現

此时当前线程继续查找id>=1的数据,因为是可重复读,复用刚刚的Read View。

得到两行数据,但是因为id为2的数据trx_id为10,该值在Read View的trx_ids中存在,所以该记录对当前事务不可见,所以最后查询到的数据只有一条记录。

如果当前事务再插入id为2的数据就插不进去,所以说MVVC只解决了一半的幻读问题。

以上是MySQL MVVC多版本並發控制如何實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:yisu.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板