MySQL——一致性非鎖定讀(快照讀)&MVCC


MySQL——一致性非鎖定讀(快照讀)

MySQL數據庫中讀分為一致性非鎖定讀、一致性鎖定讀

  • 一致性非鎖定讀(快照讀),普通的SELECT,通過多版本並發控制(MVCC)實現。
  • 一致性鎖定讀(當前讀),SELECT ... FOR UPDATE/SELECT ... LOCK IN SHARE MODE/INSERT/UPDATE/DELETE,通過鎖實現。

本文主要介紹一下一致性非鎖定讀,簡單看一下2個場景:

-- 創建表tx
create table t (id int auto_increment,name varchar(10),primary key(id)) engine=innodb;
-- 寫入數據
insert into t(name) values ('a');

場景一

session1 session2
查看事務隔離級別 select @@session.tx_isolation;
select @@session.tx_isolation;
查看數據 select * from t where id=1;
select * from t where id=1;
開啟事務 begin; begin;
會話1更新數據 update t set name='b' where id=1;
查看數據 select * from t where id=1;
select * from t where id=1;
會話1事務提交 commit;
會話2查看數據 select * from t where id=1;
會話2事務提交 commit;

場景二

session1 session2
查看事務隔離級別 select @@session.tx_isolation;
select @@session.tx_isolation;
查看數據 select * from t where id=1;
select * from t where id=1;
開啟事務 begin; begin;
會話1更新數據 update t set name='c' where id=1;
查看數據 select * from t where id=1;
會話1事務提交 commit;
會話2查看數據 select * from t where id=1;
會話2事務提交 commit;

場景一、二session1 分別對數據進行修改,事務隔離級別為可重復讀(Repeatable read);session2進行讀取,場景二讀取到了session1修改的數據;
是不是有點困惑?這就是所謂的MySQL一致性非鎖定讀,通過MVCC實現,細看場景一、二的不同,發現場景二session2在事務開啟之后session1事務提交之前從未讀取數據,這就是場景一、二結果不一樣的原因(即數據版本的選取)。

一致性非鎖定讀是指InnoDB存儲引擎通過多版本控制的方式來讀取當前執行時間數據庫中行的數據。如果讀取的行正在執行DELETE或UPDATE操作,此時的讀取操作不會因此去等待行上的鎖釋放。相反地,InnoDB存儲引擎會去讀取行的一個快照數據。

一致性非鎖定讀之所以成為非鎖定讀,因為不需要等待訪問數據行的X鎖的釋放。快照數據是指該行數據的之前版本,改實現通過undo log完成。而undo是用來事務回滾的數據,因此快照數據本身沒有額外的開銷;此外快照數據不需要上鎖,因為沒有事務需要對歷史數據進行修改操作。

一致性非鎖定讀機制極大的提高了數據庫的並發性。在InnoDB存儲引擎的默認設置下,這個默認的讀取方式,即讀取不會占用和等待表上的鎖(SELECT ... FOR UPDATE/SELECT ... LOCK IN SHARE MODE除外);一致性行非鎖定讀,用到到數據的之前的歷史版本,可能會有多個歷史版本,由此帶來的並發控制,就是大名鼎鼎的多版本並發控制(MVCC)

因為MYSQL數據庫InnoDB存儲引擎支持4中事務隔離級別,並不是每個事務隔離級別都采用一致性非鎖定讀。
在事務隔離級別已提交讀(Read committed)、可重復讀(Repeatable read InnoDB存儲引擎默認的事務隔離級別)下,InnoDB存儲引擎采用一致性非鎖定讀。

  • 已提交讀(Read committed):每次普通的SELECT(非SELECT ... FOR UPDATE/SELECT ... LOCK IN SHARE MODE)都會讀取數據的最新行版本(每次創建最新快照read view)。
  • 可重復讀(Repeatable read):事務開啟后第一次普通的SELECT(非SELECT ... FOR UPDATE/SELECT ... LOCK IN SHARE MODE)讀取最新行版本(第一次創建快照,以后沿用read view)。
    對於Innodb存儲引擎的聚集索引即數據行存在2個隱藏的列trx_id事務id、roll_pointer回滾指針,trx_id用來一致性非鎖定讀判斷數據是否可見,roll_pointer用於事務回滾(指向數據的上一個版本);undo log中存在數據的版本鏈

對於ReadView的實現:ReadView創建是根據當前活躍事務的列表,生成一個ReadView數據結構,包含當前所有活躍的事務id的ids集合、maxId最大事務id、minId最小事務id;

當讀取數據時,數據行的事務id為trx_id

  • trx_id大於ReadView的maxId,則表示此trx_id的事務在生成ReadView之后開啟的,數據的當前版本不可見
  • trx_id小於ReadView的minId,則表示此trx_id的事務在生成ReadView之前開啟並提交的,數據的當前版本可見
  • trx_id處於minId與maxId之間的,需判斷trx_id是否在ids里,在則數據的當前版本不可見,不在則數據的當前版本可見

上面簡述了ReadView的原理和在事務隔離級別已提交讀(Read committed)、可重復讀(Repeatable read)的生成策略;接下來回顧一下本文開始的場景;

  • 場景一,session2的事務隔離級別為可重復讀,ReadView在第一次SELECT時創建,此時session1的事務已開啟且尚未提交處於活躍狀態;ReadView中ids含session1的trx_id,在之后讀取的時復用ReadView(ids含session1的trx_id),即使session1的事務提交后,session1修改的數據仍不可見。
  • 場景一,session2的事務隔離級別為可重復讀,ReadView在第一次SELECT時創建,此時session1的事務已提交;ReadView中ids不含session1的trx_id,session1修改的數據可見。

trx_id事務id,trx_id的生成時機、策略

-- MySQL查看當前事務的trx_id
SELECT tx.trx_id FROM information_schema.innodb_trx tx WHERE tx.trx_mysql_thread_id = connection_id()


  • 通過演示可知事務trx_id:
    • trx_id全局唯一,遞增
    • 事務trx_id的在一致性鎖定讀(當前讀),SELECT ... FOR UPDATE/SELECT ... LOCK IN SHARE MODE/INSERT/UPDATE/DELETE時生成,且每個事務只有一個;對於一致性非鎖定讀(快照讀),普通的SELECT,若當事務尚未分配trx_id會生成一個偽trx_id(只讀事務max_trx_id 2^48=421982752402168)(不涉及對數據的增刪改),若當前事務已經分配trx_id則復用

上面介紹了trx_id,以便於理解Mysql數據庫InnoDB存儲引擎一致性非鎖定讀的ReadView。

本文簡單簡單描述了一下MySQ InnoDB存儲引擎的一致性非鎖定讀以及實現原理MVCC,因本人能力有限,如有不妥之處請多多指教。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM