MySQL歸納總結InnoDB之MVCC原理
本篇文章為大家帶來了關於mysql的相關知識,其中主要介紹了關於InnoDB之MVCC原理的相關問題,MVCC即多版本並發控制,主要是為了提高資料庫的並發性能,下面一起來看一下,希望對大家有幫助。
推薦學習:mysql影片教學
MVCC全名為Multi-Version Concurrency Control,即多版本並發控制,主要是為了提高資料庫的並發效能。同一行資料平時發生讀寫請求時,會上鎖阻塞住。但MVCC用更好的方式去處理讀取—寫請求,做到發生讀取—寫入請求衝突時不用加鎖。這個讀是指的快照讀,而不是當前讀,當前讀是一種加鎖操作,是悲觀鎖。那它到底是怎麼做到讀—寫不用加鎖的,快照讀和當前讀是指什麼?我們後面都會學到。
MySQL在REPEATABLE READ隔離等級下,是可以很大程度上避免幻讀問題的發生的,MySQL是怎麼做到的?
版本鏈
我們知道,對於使用InnoDB儲存引擎的表來說,它的叢集索引記錄中都包含兩個必要的隱藏欄位(row_id不是必要的,我們建立的表中有主鍵或非NULL的UNIQUE鍵時都不會包含row_id列):
trx_id:每次一個事務對某條聚簇索引記錄進行改動時,都會把該交易的交易id賦值給trx_id隱藏欄位。
roll_pointer:每次對某一叢集索引記錄進行改動時,都會把舊的版本寫入到undo日誌中,然後這個隱藏列就相當於一個指針,可以透過它來找到該記錄修改前的資訊。
為了說明這個問題,我們建立一個演示表:
CREATE TABLE `teacher` ( `number` int(11) NOT NULL, `name` varchar(100) DEFAULT NULL, `domain` varchar(100) DEFAULT NULL, PRIMARY KEY (`number`)) ENGINE=InnoDB DEFAULT CHARSET=utf8
然後向這個表裡插入一條數據:
mysql> insert into teacher values(1, 'J', 'Java');Query OK, 1 row affected (0.01 sec)
現在裡的數據就是這樣的:
mysql> select * from teacher; +--------+------+--------+ | number | name | domain | +--------+------+--------+ | 1 | J | Java | +--------+------+--------+ 1 row in set (0.00 sec)
假設插入該記錄的事務id為60,那麼此刻該記錄的示意圖如下所示:
假設之後兩個事務id分別為80、120的事務對這條記錄進行UPDATE操作,操作流程如下:
#Trx80 | Trx120 |
---|---|
begin | |
#begin | |
update teacher set name='S' where number=1; |
|
update teacher set name='T' where number=1; |
|
commit | |
每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:
对该记录每次更新后,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,我们把这个链表称之为版本链,版本链的头节点就是当前记录最新的值。另外,每个版本中还包含生成该版本时对应的事务id。于是可以利用这个记录的版本链来控制并发事务访问相同记录的行为,那么这种机制就被称之为多版本并发控制(Mulit-Version Concurrency Control MVCC)。
ReadView
对于使用READ UNCOMMITTED隔离级别的事务来说,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。
对于使用SERIALIZABLE隔离级别的事务来说,InnoDB使用加锁的方式来访问记录。
对于使用READ COMMITTED和REPEATABLE READ隔离级别的事务来说,都必须保证读到已经提交了的事务修改过的记录,也就是说假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是:READ COMMITTED和REPEATABLE READ隔离级别在不可重复读和幻读上的区别,这两种隔离级别关键是需要判断一下版本链中的哪个版本是当前事务可见的。
为此,InnoDB提出了一个ReadView的概念,这个ReadView中主要包含4个比较重要的内容:
m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1,2,3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时,m_ids就包括1和2,min_trx_id的值就是1,max_trx_id的值就是4。
creator_trx_id:表示生成该ReadView的事务的事务id。
有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
- 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
- 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的trx_id属性值大于或等于ReadView中的max_trx_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
- 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间(min_trx_id
- 如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。
在MySQL中,READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同。
我们还是以表teacher为例,假设现在表teacher中只有一条由事务id为60的事务插入的一条记录,接下来看一下READ COMMITTED和REPEATABLE READ所谓的生成ReadView的时机不同到底不同在哪里。
READ COMMITTED每次读取数据前都生成一个ReadView
假设现在系统里有两个事务id分别为80、120的事务在执行:
# Transaction 80 set session transaction isolation level read committed; begin update teacher set name='S' where number=1; update teacher set name='T' where number=1;
此刻,表teacher中number为1的记录得到的版本链表如下所示:
假设现在有一个使用READ COMMITTED隔离级别的事务开始执行:
set session transaction isolation level read committed; # 使用READ COMMITTED隔离级别的事务 begin; # SELECE1:Transaction 80、120未提交 SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'J'
这个SELECE1的执行过程如下:
在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[80, 120],min_trx_id为80,max_trx_id为121,creator_trx_id为0。
然后从版本链中挑选可见的记录,最新版本的列name的内容是’T’,该版本的trx_id值为80,在m_ids列表内,根据步骤4不符合可见性要求,根据roll_pointer跳到下一个版本。
下一个版本的列name的内容是’S’,该版本的trx_id值也为80,也在m_ids列表内,根据步骤4也不符合要求,继续跳到下一个版本。
下一个版本的列name的内容是’J’,该版本的trx_id值为60,小于ReadView 中的min_trx_id值,根据步骤2判断这个版本是符合要求的。
之后,我们把事务id为80的事务提交一下,然后再到事务id为120的事务中更新一下表teacher 中number为1的记录:
set session transaction isolation level read committed; # Transaction 120 begin update teacher set name='K' where number=1; update teacher set name='F' where number=1;
此刻,表teacher 中number为1的记录的版本链就长这样:
然后再到刚才使用READ COMMITTED隔离级别的事务中继续查找这个number 为1的记录,如下:
# 使用READ COMMITTED隔离级别的事务 begin; # SELECE1:Transaction 80、120未提交 SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'J' # SELECE2:Transaction 80提交、120未提交 SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'T'
这个SELECE2 的执行过程如下:
- 在执行SELECT语句时会又会单独生成一个ReadView,该ReadView的m_ids列表的内容就是[120](事务id为80的那个事务已经提交了,所以再次生成快照时就没有它了),min_trx_id为120,max_trx_id为121,creator_trx_id为0。
- 然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’F’,该版本的trx_id值为120,在m_ids列表内,根据步骤4不符合可见性要求,根据roll_pointer跳到下一个版本。
- 下一个版本的列name 的内容是’K’,该版本的trx_id值为120,也在m_ids列表内,根据步骤4不符合可见性要求,根据roll_pointer跳到下一个版本。
- 下一个版本的列name的内容是’T’,该版本的trx_id值为80,小于ReadView中的min_trx_id值120,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’‘T’'的记录。
以此类推,如果之后事务id为120的记录也提交了,再次在使用READCOMMITTED隔离级别的事务中查询表teacher中number值为1的记录时,得到的结果就是’F’了,具体流程我们就不分析了。
总结一下就是:使用READCOMMITTED隔离级别的事务在每次查询开始时都会生成一个独立的ReadView。
REPEATABLE READ —— 在第一次读取数据时生成一个ReadView
对于使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了。我们还是用例子看一下是什么效果。
假设现在系统里有两个事务id分别为80、120的事务在执行:
# Transaction 80 begin update teacher set name='S' where number=1; update teacher set name='T' where number=1;
此刻,表teacher中number为1的记录得到的版本链表如下所示:
假设现在有一个使用REPEATABLE READ隔离级别的事务开始执行:
# 使用REPEATABLE READ隔离级别的事务 begin; # SELECE1:Transaction 80、120未提交 SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'J'
这个SELECE1的执行过程如下(与READ COMMITTED的过程一致):
在执行SELECT语句时会先生成一个ReadView,ReadView的m_ids列表的内容就是[80, 120],min_trx_id为80,max_trx_id为121,creator_trx_id为0。
然后从版本链中挑选可见的记录,最新版本的列name的内容是’T’,该版本的trx_id值为80,在m_ids列表内,根据步骤4不符合可见性要求,根据roll_pointer跳到下一个版本。
下一个版本的列name的内容是’S’,该版本的trx_id值也为80,也在m_ids列表内,根据步骤4也不符合要求,继续跳到下一个版本。
下一个版本的列name的内容是’J’,该版本的trx_id值为60,小于ReadView 中的min_trx_id值,根据步骤2判断这个版本是符合要求的。
之后,我们把事务id为80的事务提交一下,然后再到事务id为120的事务中更新一下表teacher 中number为1的记录:
# Transaction 80 begin update teacher set name='K' where number=1; update teacher set name='F' where number=1;
此刻,表teacher 中number为1的记录的版本链就长这样:
然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找这个number为1的记录,如下:
# 使用REPEATABLE READ隔离级别的事务 begin; # SELECE1:Transaction 80、120未提交 SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'J' # SELECE2:Transaction 80提交、120未提交 SELECT * FROM teacher WHERE number = 1; # 得到的列name的值为'J'
这个SELECE2的执行过程如下:
- 因為目前交易的隔離等級為REPEATABLE READ,而之前在執行SELECE1時已經產生過ReadView了,所以此時直接復用之前的ReadView,之前的ReadView的m_ids列表的內容就是[80, 120],min_trx_id為80,max_trx_id為121,creator_trx_id為0。
- 然後從版本鏈中挑選可見的記錄,從圖中可以看出,最新版本的列name的內容是'F',該版本的trx_id值為120,在m_ids列表內,根據步驟4不符合可見性要求,根據roll_pointer跳到下一個版本。
- 下一個版本的列name的內容是’K’,該版本的trx_id值為120,也在m_ids列表內,根據步驟4不符合可見性要求,根據roll_pointer跳到下一個版本。
- 下一個版本的列name的內容是’T’,該版本的trx_id值為80,也在m_ids列表內,根據步驟4不符合可見性要求,根據roll_pointer跳到下一個版本。
- 下一個版本的列name的內容是’S’,該版本的trx_id值為80,也在m_ids列表內,根據步驟4不符合可見性要求,根據roll_pointer跳到下一個版本。
- 下一個版本的列name的內容是'J',該版本的trx_id值為60,小於ReadView中的min_trx_id值80,表示產生此版本的交易在目前交易產生ReadView前已提交,所以這個版本是符合要求的,最後回傳給使用者的版本就是這條列name為''J''的記錄。
也就是說兩次SELECT查詢得到的結果是重複的,記錄的列值都是’’‘J’’’,這就是可重複讀取的意思。
如果我們之後再把事務id為120的記錄提交了,然後再到剛才使用REPEATABLE READ隔離級別的事務中繼續查找這個number為1的記錄,得到的結果還是'J',具體執行過程大家可以自己分析一下。
MVCC下的幻讀現象和幻讀解決
前面我們已經知道了,REPEATABLE READ隔離等級下MVCC可以解決不可重複讀取問題,那麼幻讀呢? MVCC是怎麼解決的?幻讀是當一個事務按照某個相同條件多次讀取記錄時,後讀取時讀到了之前沒有讀到的記錄,而這個記錄來自另一個事務添加的新記錄。
我們可以想想,在REPEATABLE READ隔離等級下的事務T1先根據某個搜尋條件讀取到多筆記錄,然後事務T2插入一條符合對應搜尋條件的記錄並提交,然後事務T1再根據相同搜尋條件執行查詢。結果會是什麼?依照ReadView中的比較規則:
不管事務T2比事務T1是否先開啟,事務T1都是看不到T2的提交的。請自行按照上面介紹的版本鏈、ReadView以及判斷可見性的規則來分析一下。
但是,在REPEATABLE READ隔離等級下InnoDB中的MVCC可以很大程度上地避免幻讀現象,而不是完全禁止幻讀。怎麼回事呢?我們來看下面的情況:
T1 | T2 |
---|---|
begin; | |
以上是MySQL歸納總結InnoDB之MVCC原理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

MySQL在Web應用中的主要作用是存儲和管理數據。 1.MySQL高效處理用戶信息、產品目錄和交易記錄等數據。 2.通過SQL查詢,開發者能從數據庫提取信息生成動態內容。 3.MySQL基於客戶端-服務器模型工作,確保查詢速度可接受。

Laravel 是一款 PHP 框架,用於輕鬆構建 Web 應用程序。它提供一系列強大的功能,包括:安裝: 使用 Composer 全局安裝 Laravel CLI,並在項目目錄中創建應用程序。路由: 在 routes/web.php 中定義 URL 和處理函數之間的關係。視圖: 在 resources/views 中創建視圖以呈現應用程序的界面。數據庫集成: 提供與 MySQL 等數據庫的開箱即用集成,並使用遷移來創建和修改表。模型和控制器: 模型表示數據庫實體,控制器處理 HTTP 請求。

MySQL和phpMyAdmin是強大的數據庫管理工具。 1)MySQL用於創建數據庫和表、執行DML和SQL查詢。 2)phpMyAdmin提供直觀界面進行數據庫管理、表結構管理、數據操作和用戶權限管理。

在開發一個小型應用時,我遇到了一個棘手的問題:需要快速集成一個輕量級的數據庫操作庫。嘗試了多個庫後,我發現它們要么功能過多,要么兼容性不佳。最終,我找到了minii/db,這是一個基於Yii2的簡化版本,完美地解決了我的問題。

MySQL与其他编程语言相比,主要用于存储和管理数据,而其他语言如Python、Java、C 则用于逻辑处理和应用开发。MySQL以其高性能、可扩展性和跨平台支持著称,适合数据管理需求,而其他语言在各自领域如数据分析、企业应用和系统编程中各有优势。

文章摘要:本文提供了詳細分步說明,指導讀者如何輕鬆安裝 Laravel 框架。 Laravel 是一個功能強大的 PHP 框架,它 упростил 和加快了 web 應用程序的開發過程。本教程涵蓋了從系統要求到配置數據庫和設置路由等各個方面的安裝過程。通過遵循這些步驟,讀者可以快速高效地為他們的 Laravel 項目打下堅實的基礎。

MySQL的基本操作包括創建數據庫、表格,及使用SQL進行數據的CRUD操作。 1.創建數據庫:CREATEDATABASEmy_first_db;2.創建表格:CREATETABLEbooks(idINTAUTO_INCREMENTPRIMARYKEY,titleVARCHAR(100)NOTNULL,authorVARCHAR(100)NOTNULL,published_yearINT);3.插入數據:INSERTINTObooks(title,author,published_year)VA

在使用Thelia開發電商網站時,我遇到了一個棘手的問題:MySQL模式設置不當,導致某些功能無法正常運行。經過一番探索,我找到了一個名為TheliaMySQLModesChecker的模塊,它能夠自動修復Thelia所需的MySQL模式,徹底解決了我的困擾。
