MySQL数据库InnoDB存储引擎多版本控制(MVCC)实现原理分析_MySQL
文/何登成
导读:
来自网易研究院的MySQL内核技术研究人何登成,把MySQL数据库InnoDB存储引擎的多版本控制(简称:MVCC)实现原理,做了深入的研究与详细的文字图表分析,方便大家理解InnoDB存储引擎实现的多版本控制技术(简称:MVCC)。
基本知识
假设对于多版本控制(MVCC)的基础知识,有所了解。MySQL数据库InnoDB存储引擎为了实现多版本的一致性读,采用的是基于回滚段的协议。
行结构
MySQL数据库InnoDB存储引擎表数据的组织方式为主键聚簇索引。由于采用索引组织表结构,记录的ROWID是可变的(索引页分裂的时候,Structure Modification Operation,SMO),因此二级索引中采用的是(索引键值, 主键键值)的组合来唯一确定一条记录。
无论是聚簇索引,还是二级索引,其每条记录都包含了一个DELETED BIT位,用于标识该记录是否是删除记录。除此之外,聚簇索引记录还有两个系统列:DATA_TRX_ID,DATA_ROLL_PTR。DATA _TRX_ID表示产生当前记录项的事务ID;DATA _ROLL_PTR指向当前记录项的undo信息。
聚簇索引行结构(与多版本一致读有关的部分,DELETED BIT省略):
二级索引行结构:
从聚簇索引行结构,与二级索引行结构可以看出,聚簇索引中包含版本信息(事务号+回滚指针),二级索引不包含版本信息,二级索引项的可见性如何判断?下面将会给出。
Read View
InnoDB存储引擎默认的隔离级别为Repeatable Read (RR),可重复读。InnoDB存储引擎在开始一个RR读之前,会创建一个Read View。Read View用于判断一条记录的可见性。Read View定义在read0read.h文件中,其中最主要的与可见性相关的属性如下:
?123456789101112131415161718192021 |
|
简单来说,Read View记录读开始时,所有的活动事务,这些事务所做的修改对于Read View是不可见的。除此之外,所有其他的小于创建Read View的事务号的所有记录均可见。可见包括两层含义:
- 记录可见,且Deleted bit = 0;当前记录是可见的有效记录。
- 记录可见,且Deleted bit = 1;当前记录是可见的删除记录。此记录在本事务开始之前,已经删除。
测试方法:
?12345678910111213141516 |
- Insert
|
-read隔离级别
repeatable read(RR)
测试结果
update primary key
代码调用流程:
?1 | ha_innobase::update_row -> row_update_for_mysql -> row_upd_step -> row_upd -> row_upd_clust_step -> row_upd_clust_rec_by_insert -> btr_cur_del_mark_set_clust_rec -> row_ins_index_entry |
简单来说,就是将cluster index的旧记录标记位删除;插入一条新纪录。该语句执行完之后,数据结构如下:
老版本仍旧存储在聚簇索引之中,其DATA_TRX_ID被设置为1811,Deleted bit设置为1,undo中记录了前镜像的事务id = 1809。新版本DATA_TRX_ID也为1811。通过此图,还可以发现,虽然新老版本是一条记录,但是在聚簇索引中是通过两条记录来标识的。同时, 由于更新了主键,二级索引也需要做相应的更新(二级索引中包含主键项)。
update non-primary key(diff value)
更新comment字段,代码调用流程与上面有部分不同,可以自行跟踪,此处省略。更新操作执行完之后,索引结构变更如下:
从上图可见,更新二级索引的键值时,聚簇索引本身并不会产生新的记录项,而是将旧版本信息记录在undo之中。与此同时,二级索引将会产生 新的索引项,其PK值保持不变,指向聚簇索引的同一条记录。细心的读者可能会发现,二级索引页面中有一个MAX_TRX_ID,此值记录的是更新二级索引 页面的最大事务ID。通过MAX_TRX_ID的过滤,INNODB能够实现大部分的辅助索引覆盖性扫描(仅仅扫描辅助索引,不需要回聚簇索引)。具体过 滤方法,将在后面的内容中给出。
update non-primary key(same value)
最后一个测试用例,是更新comment项为同样的值。在我的测试中,更新之后的索引结构如下:
聚簇索引仍旧会更新,但是二级索引保持不变。
总结
- 无论是聚簇索引,还是二级索引,只要其键值更新,就会产生新版本。将老版本数据deleted bti设置为1;同时插入新版本。
- 对于聚簇索引,如果更新操作没有更新primary key,那么更新不会产生新版本,而是在原有版本上进行更新,老版本进入undo表空间,通过记录上的undo指针进行回滚。
- 对于二级索引,如果更新操作没有更新其键值,那么二级索引记录保持不变。
- 对于二级索引,更新操作无论更新primary key,或者是二级索引键值,都会导致二级索引产生新版本数据。
- 聚簇索引设置记录deleted bit时,会同时更新DATA_TRX_ID列。老版本DATA_TRX_ID进入undo表空间;二级索引设置deleted bit时,不写入undo。
可见性判断
主键查找
select * from test where id = 1;
- 针对测试1,如果1811(DATA_TRX_ID) 无记录返回。
- 针对测试1,如果 1811(DATA_TRX_ID) >= read_view.low_limit_id,证明被标记为删除的记录1不可见,通过DATA_ROLL_PTR回滚记录,得到DATA_TRX_ID = 1809。如果1809可见,则返回记录(1,aaa);否则无记录返回。
针对测试1,如果up_limit_id,low_limit_id都无法判断可见性,那么遍历read_view中的trx_ids,依次对比事务id,如果在DATA_TRX_ID在trx_ids数组中,则不可见(更新未提交)。
select * from test where id = 9;
- 针对测试2,如果1816可见,返回(9,ccc)。
- 针对测试2,如果1816不可见,通过DATA_ROLL_PTR回滚到1811,如果1811可见,返回(9, aaa)。
针对测试2,如果1811不可见,无结果返回。
select * from test where id > 0;
- 针对测试1,索引中, 满足条件的同一记录,有两个版本(版本1,delete bit =1)。那么是否会一条记录返回两次呢?必定不会,这是因为pk = 1的可见性与pk = 9的可见性是一致的,同时pk = 1是标记了deleted bit的版本。如果事务ID = 1811可见。那么pk = 1 delete可见,无记录返回,pk = 9返回记录;如果1811不可见,回滚到1809可见,那么pk = 1返回记录,pk = 9回滚后无记录。
总结:
- 通过主键查找记录,需要配合read_view,记录DATA_TRX_ID,记录DATA_ROLL_PTR指针共同判断。
- read_view用于判断当前记录是否可见(判断DATA_TRX_ID)。DATA_ROLL_PTR用于将当前记录回滚到前一版本。
非主键查找
select comment from test where comment > ‘ ‘;
- 针对测试2,二级索 引,当前页面的最大更新事务MAX_TRX_ID = 1816。如果MAX_TRX_ID lock_sec_rec_cons_read_sees)
-
针对测试2,二级索 引,如果当前页面不能满足MAX_TRX_ID ?
1234567 if
(clust_rec
&& (old_vers || rec_get_deleted_flag(
rec,dict_table_is_comp(sec_index->table)))
&& !row_sel_sec_rec_is_for_clust_rec(rec, sec_index, clust_rec, clust_index))
满足if判断的所有聚簇索引记录,都直接丢弃,以上判断的逻辑如下:
- 需要回聚簇索引扫描,并且获得记录
- 聚簇索引记录为回滚版本,或者二级索引中的记录为删除版本
- 聚簇索引项,与二级索引项,其键值并不相等
为什么满足if判断,就可以直接丢弃数据?用白话来说,就是我们通过二级索引记录,定位聚簇索引记录,定位之后,还需要再次检查聚簇索引记录是否仍旧是我在二级索引中看到的记录。如果不是,则直接丢弃;如果是,则返回。
根据此条件,结合查询与测试2中的索引结构。可见版本为事务1811.二级索引中的两项pk = 9都能通过聚簇索引回滚到1811版本。但是,二级索引记录(ccc,9)与聚簇索引回滚后的版本(aaa,9)不一致,直接丢弃。只有二级索引记录 (aaa,9)保持一致,直接返回。
总结:
- 二级索引的多版本可见性判断,需要通过聚簇索引完成。
- 二级索引页面中保存了MAX_TRX_ID,可以快速判断当前页面中,是否所有项均可见,可以实现二级索引页面级别的索引覆盖扫描。一般而言,此判断是满足条件的,保证了索引覆盖扫描 (index only scan)的高效性。
- 二级索引中的项,需要与聚簇索引中的可见性进行比较,保证聚簇索引中的可见项,与二级索引中的项数据一致。
疑问
- 在http://blogs.InnoDB.com/wp/2011/04/mysql-5-6-multi-threaded-purge/中, 作者提到,InnoDB存储引擎的purge操作,是通过遍历undo来实现对于标记位deleted项的回收的。如果二级索引本身标记deleted位不记录 undo,那么这个回收操作如何完成?还是说purge是通过解析redo来完成回收的?(根据下面对于purge的流程分析,此问题已解决)
Purge流程
Purge功能:
InnoDB由于要支持多版本协议,因此无论是更新,删除,都只是设置记录上的deleted bit标记位,而不是真正的删除记录。后续这些记录的真正删除,是通过Purge后台进程实现的。Purge进程定期扫描InnoDB的undo,按照先 读老undo,再读新undo的顺序,读取每条undo record。对于每一条undo record,判断其对应的记录是否可以被purge(purge进程有自己的read view,等同于进程开始时最老的活动事务之前的view,保证purge的数据,一定是不可见数据,对任何人来说),如果可以purge,则构造完整记 录(row_purge_parse_undo_rec)。然后按照先purge二级索引,最后purge聚簇索引的顺序,purge一个操作生成的旧版本完整记录。
一个完整的purge函数调用流程如下:
?123 |
row_purge_step->row_purge->trx_purge_fetch_next_rec->row_purge_parse_undo_rec ->row_purge_del_mark->row_purge_remove_sec_if_poss ->row_purge_remove_clust_if_poss
|
总结:
- purge是通过遍历undo实现的。
- purge的粒度是一条记录上的一个操作。如果一条记录被update了3次,产生3个old版本,均可purge。那么purge读取undo,对于每一个操作,都会调用一次purge。一个purge删除一个操作产生的old版本(按照操作从老到新的顺序)。
- purge按照先二级索引,最后聚簇索引的顺序进行。
- purge二级索引,通过构造出的索引项进行查找定位。不能直接针对某个二级页面进行,因为不知道记录的存放page。
- 对于二级索引设置deleted bit为不需要记录undo,因为purge是根据聚簇索引undo实现。因此二级索引deleted bit被设置为1的项,没有记录undo,仍旧可以被purge。
- purge是一个耗时的操作。二级索引的purge,需要search_path定位数据,相当于每个二级索引,都做了一次index unique scan。
- 一次delete操作,IO翻番。第一次IO是将记录的deleted bit设置为1;第二次的IO是将记录删除。
文章具体来源不详,如有知情者,请在评论中回复。

热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)

热门话题











苹果公司最新发布的iOS18、iPadOS18以及macOSSequoia系统为Photos应用增添了一项重要功能,旨在帮助用户轻松恢复因各种原因丢失或损坏的照片和视频。这项新功能在Photos应用的"工具"部分引入了一个名为"已恢复"的相册,当用户设备中存在未纳入其照片库的图片或视频时,该相册将自动显示。"已恢复"相册的出现为因数据库损坏、相机应用未正确保存至照片库或第三方应用管理照片库时照片和视频丢失提供了解决方案。用户只需简单几步

如何在PHP中使用MySQLi建立数据库连接:包含MySQLi扩展(require_once)创建连接函数(functionconnect_to_db)调用连接函数($conn=connect_to_db())执行查询($result=$conn->query())关闭连接($conn->close())

写在前面&笔者的个人理解基于图像的3D重建是一项具有挑战性的任务,涉及从一组输入图像推断目标或场景的3D形状。基于学习的方法因其直接估计3D形状的能力而受到关注。这篇综述论文的重点是最先进的3D重建技术,包括生成新颖的、看不见的视图。概述了高斯飞溅方法的最新发展,包括输入类型、模型结构、输出表示和训练策略。还讨论了尚未解决的挑战和未来的方向。鉴于该领域的快速进展以及增强3D重建方法的众多机会,对算法进行全面检查似乎至关重要。因此,本研究对高斯散射的最新进展进行了全面的概述。(大拇指往上滑

PHP中处理数据库连接报错,可以使用以下步骤:使用mysqli_connect_errno()获取错误代码。使用mysqli_connect_error()获取错误消息。通过捕获并记录这些错误信息,可以轻松识别并解决数据库连接问题,确保应用程序的顺畅运行。

本站5月5日消息,日前,网易集团公益基金会宣布为梅大高速见义勇为司机王向楠及其妻子颁发10万元奖金,向其见义勇为之举致敬。5月1日凌晨2时01分左右,梅大高速茶阳路段发生塌方灾害。发现前方高速塌方后,冷链货车司机王向楠及其妻子在发现异常后,立刻决定急转方向将长12.5米的冷藏货车横在路面,及时接力拦停四五十辆车,在危急时刻挽救他人生命。在此之前,中国职工发展基金会联合一汽解放汽车有限公司共同为其颁发正能量奖金10000元;阿里公益天天正能量为王向楠颁发奖状及10000元公益金。此外,运满满等其他

腾讯游戏官宣新一年腾讯游戏发布会将于5月28日20:00开启,从直播封面图来看除王者荣耀、元梦、英雄联盟手游外,包含新游DNF手游、塔瑞斯世界、剑灵等。

任谁都不会想到,光一个网易与暴雪复合的操作,就能搞得国内怀旧市场风云突变。各大游戏厂商纷纷发力,不得不把一年一度的“暑期档对决”提前到5月,趁着魔兽国服重开之前,收割最后一波暴雪全家桶缺席的流量。今天,就让我们来回顾下4月的怀旧市场,并展望下5月的怀旧大战吧。网易与暴雪复合成功,魔兽国服招募已经开启在4月10日10点,网易正式官宣跟暴雪以及微软达成更广泛的合作关系,并承诺会从今年夏天起,把《魔兽世界》、《炉石传说》、《暗黑破坏神》等游戏带回国服,给到玩家更好的游戏体验。但在各路舅舅党开始透露魔兽

通过Go标准库database/sql包,可以连接到MySQL、PostgreSQL或SQLite等远程数据库:创建包含数据库连接信息的连接字符串。使用sql.Open()函数打开数据库连接。执行SQL查询和插入操作等数据库操作。使用defer关闭数据库连接以释放资源。
