从一个案例深入剖析InnoDB隐式锁和可见性判断(1)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 从一个案例深入剖析InnoDB隐式锁和可见性判断

一、问题抛出

最近遇到一个问题,得到栈如下(5.6.25):

image.png

出现这个问题的时候只存在一个读写事务,那就是本事务。对这里的红色部分比较感兴趣,但是这里不是所有的内容都和这个问题相关,主要还是围绕可见性判断和隐式锁判定进行,算是我的思考过程。但是对Innodb认知水平有限,如有误导请谅解。使用的源码版本5.7.29。

二、read view 简述

关于read view说明的文章已经很多了,我这里简单记录一下我学习的地方。一致性读取(consistent read),根据隔离级别的不同,会在不同的时机建立read view,如下:

  • RR 事务的第一个select命令发起的时候建立read view,直到事务提交释放
  • RC 事务的每一个select都会单独建立read view

有了read view 就能够对每行数据的可见性进行判断了,下面是read view中的关键属性

  • m_up_limit_id:如果行的trx id 小于了m_up_limit_id则不可见。
  • m_low_limit_id:如果行的trx id 大于了m_low_limit_id则可见。
  • m_ids:是用于记录建立read view时刻的读写事务的vector数组,用于对于m_up_limit_id和m_low_limit_id之间的trx需要根据它来进行判定,是否处于活跃状态。
  • m_low_limit_no则用于记录建立read view时刻的最小trx no,主要用于purge线程判断清理undo使用。

如何拿到值得具体可以参见附录,而对于可见性的判断我们可以参考如下函数:

/** Check whether the changes by id are visible.
 @param[in] id transaction id to check against the view
 @param[in] name table name
 @return whether the view sees the modifications of id. */
 bool changes_visible(
  trx_id_t  id,
  const table_name_t& name) const
  MY_ATTRIBUTE((warn_unused_result))
 {
  ut_ad(id > 0);
  if (id < m_up_limit_id || id == m_creator_trx_id) { //小于 可见
   return(true);
  }
  check_trx_id_sanity(id, name);
  if (id >= m_low_limit_id) { //大于不可见
   return(false);
  } else if (m_ids.empty()) { //如果之间的 active 为空 则可见 
   return(true);
  }
  const ids_t::value_type* p = m_ids.data();
  return(!std::binary_search(p, p + m_ids.size(), id)); //否则比较本trx id 是否在这之中,如果在不可以见,反之可见
 }

三、关于可见性判断的几个问题

1、有大量的删除行,且已经提交,但是没有被purge线程清理

这种情况由于大量删除行(或者update)并且已经提交,但是由于有长时间的select语句导致read view记录的状态也比较陈旧,因此根据m_low_limit_no的判断purge线程是不能清理一些比较老旧的undo的,因此这会导致一个问题,如果这些del flag的记录会存在于逻辑记录链表内部,因此其他select扫描的时候回根据next offset扫描到,但是根据可见性判断条件这些del flag的记录trx id小于本select语句的read view 的 m_up_limit_id,因此是可见的debug如下:

387             return(view->changes_visible(trx_id, index->table->name));

(gdb) p view->changes_visible(trx_id, index->table->name)
$14 = true


但是因为已经标记为del flag因此会做跳过处理如下:

row_search_mvcc:
if (rec_get_deleted_flag(rec, comp)) {
/ The record is delete-marked: we can skip it /
...
goto next_rec;

也就是实际上在长时间read view的“保护”下,我们的undo不能清理,并且del flag不能清理还保存在block的逻辑链表中,扫描的时候会实际扫描到,只是做了跳过处理。因此会出现如下现象

image.png

这就是上面说的原因,虽然没有数据了,但是查询依旧很慢。

2、大量删除,还未提交

那么select扫描的时候会根据next offset 扫描到,但是由于read view 判断这些数据的trx id 位于 m_up_limit_id和m_low_limit_id之间,需要根据事务是否活跃(read view的m_ids,显然这里是活跃的)通过undo构建其前印象,如下判断:

lock_clust_rec_cons_read_sees
trx_id_t trx_id = row_get_rec_trx_id(rec, index, offsets);
return(view->changes_visible(trx_id, index->table->name));
3、using index也可能回表

我们知道如果执行计划使用到using index那么不会回表去取主键的数据,使用整个二级索引即可。但是这里有一种特殊情况,这里进行描述。

对于二级索引而言,因为row记录不包含trx id和undo ptr两个伪列,那么其可见性判断和前的印象构建均需要回表获取主键的记录,当然可见性判断可以先根据本二级索引page的max trx id是否小于read view的m_up_limit_id来进行第一次粗略过滤,那么可见性判断的可能性就低很多,如果通过了这个比对,那么剩余精确判断还是需要回表通过主键来比对才行,如下:

  • 对于二级索引回表操作来讲,精确的可见性判断放到了回表后的lock_clust_rec_cons_read_sees函数上,关于二级索引的回表,参考附录。
  • 对于不回表访问(using index),通过了粗略判断后(lock_sec_rec_cons_read_sees),如果遇到需要精确的可见性判断,那么也是要回表的,原因前面解释了(row记录不包含trx id和undo ptr),参考附录。

对于这个问题我们可以简单的做如下的测试,当然需要打断点才行:

测试表如下:
mysql> show create table testimp4 \G
1. row **
Table: testimp4
Create Table: CREATE TABLE `testimp4` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
`d` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `b` (`b`),
KEY `d` (`d`)
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from testimp4;
+------+------+------+------------------------------------+
| id | a | b | d |
+------+------+------+------------------------------------+
| 5 | 5 | 300 | NULL |
| 6 | 7000 | 7700 | 1124 |
| 11 | 7000 | 7700 | 1124 |
| 12 | 7000 | 7700 | 1124 |
| 13 | 2900 | 1800 | NULL |
| 14 | 2900 | 1800 | NULL |
| 1000 | 88 | 1499 | NULL |
| 4000 | 6000 | 5904 | iiiafsafasfihhhccccchhhigggofgo111 |
| 4001 | 7000 | 7700 | 1124454555 |
| 9999 | 9999 | 9999 | a |
+------+------+------+------------------------------------+
10 rows in set (0.00 sec)

对于下列语句的执行话是:

mysql> desc select b from testimp4  where b=300;
+----+-------------+----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
| 1 | SIMPLE | testimp4 | NULL | ref | b | b | 5 | const | 1 | 100.00 | Using index |
+----+-------------+----------+------------+------+---------------+------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

我们做如下语句:

T1

T2

begin;delete from testimp4 where id=5;(不提交)



select b from testimp4 where b=300;(这里是需要回表的)

这里显然T2(5 ,5 ,300 ,NULL )的这条记录已经被T1删除了,但是没有提交,T2首先判断二级索引b上这行数据所在的page其max trx id是否小于本select语句的read view的m_up_limit_id,显然这不成立,因为T1还会处于活跃状态,然后就进入了回表判断流程。栈如下:

#0  lock_clust_rec_cons_read_sees (rec=0x7fff060980a8 "\200", index=0x7ffec0499330, offsets=0x7fffe8399a70, view=0x33b1368)
at /home/mysql/soft/percona-server-5.7.29-32/storage/innobase/lock/lock0lock.cc:369
#1 0x0000000001afbca4 in Row_sel_get_clust_rec_for_mysql::operator() (this=0x7fffe839a2d0, prebuilt=0x7ffec80c97a0, sec_index=0x7ffec049a2c0, rec=0x7fff060a008c "\200",
thr=0x7ffec80c9f88, out_rec=0x7fffe839a310, offsets=0x7fffe839a2e8, offset_heap=0x7fffe839a2f0, vrow=0x0, mtr=0x7fffe8399d90)
at /home/mysql/soft/percona-server-5.7.29-32/storage/innobase/row/row0sel.cc:3763
#2 0x0000000001b00a94 in row_search_mvcc (buf=0x7ffec80c8a00 <incomplete sequence \375>, mode=PAGE_CUR_GE, prebuilt=0x7ffec80c97a0, match_mode=1, direction=0)
at /home/mysql/soft/percona-server-5.7.29-32/storage/innobase/row/row0sel.cc:6051



            </div>
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
存储 关系型数据库 MySQL
从一个案例深入剖析InnoDB隐式锁和可见性判断(4)
从一个案例深入剖析InnoDB隐式锁和可见性判断
680 0
|
NoSQL 关系型数据库 索引
从一个案例深入剖析InnoDB隐式锁和可见性判断(1)
从一个案例深入剖析InnoDB隐式锁和可见性判断
从一个案例深入剖析InnoDB隐式锁和可见性判断(1)
|
SQL 关系型数据库 MySQL
从一个案例深入剖析InnoDB隐式锁和可见性判断(2)
从一个案例深入剖析InnoDB隐式锁和可见性判断
137 0
从一个案例深入剖析InnoDB隐式锁和可见性判断(2)
|
关系型数据库 MySQL 索引
从一个案例深入剖析InnoDB隐式锁和可见性判断(3)
从一个案例深入剖析InnoDB隐式锁和可见性判断
107 0
|
3天前
|
机器学习/深度学习 人工智能 算法
解密巴黎奥运会中的阿里云AI技术
2024年巴黎奥运会圆满结束,中国代表团金牌数与美国并列第一,展现了卓越实力。阿里云作为官方云服务合作伙伴,通过先进的AI技术深度融入奥运的各项环节,实现了大规模的云上转播,超越传统卫星转播,为全球观众提供流畅、高清的观赛体验。其中,“子弹时间”回放技术在多个场馆的应用,让观众享受到了电影般的多角度精彩瞬间。此外,8K超高清直播、AI智能解说和通义APP等创新,极大地提升了赛事观赏性和互动性。能耗宝(Energy Expert)的部署则助力实现了赛事的可持续发展目标。巴黎奥运会的成功举办标志着体育赛事正式进入AI时代,开启了体育与科技融合的新篇章。
解密巴黎奥运会中的阿里云AI技术
|
11天前
|
开发框架 自然语言处理 API
基于RAG搭建企业级知识库在线问答
本文介绍如何使用搜索开发工作台快速搭建基于RAG开发链路的知识库问答应用。
7612 16
|
18天前
|
弹性计算 关系型数据库 Serverless
函数计算驱动多媒体文件处理:高效、稳定与成本优化实践
本次测评的解决方案《告别资源瓶颈,函数计算驱动多媒体文件处理》展示了如何利用阿里云函数计算高效处理多媒体文件。文档结构清晰、内容详实,适合新客户参考。方案提供了一键部署与手动部署两种方式,前者简便快捷,后者灵活性高但步骤较多。通过部署,用户可体验到基于函数计算的文件处理服务,显著提升处理效率和系统稳定性。此外,测评还对比了应用内处理文件与函数计算处理文件的不同,突出了函数计算在资源管理和成本控制方面的优势。
22675 19
|
12天前
|
SQL 分布式计算 数据库
畅捷通基于Flink的实时数仓落地实践
本文整理自畅捷通总架构师、阿里云MVP专家郑芸老师在 Flink Forward Asia 2023 中闭门会上的分享。
8207 14
畅捷通基于Flink的实时数仓落地实践
|
18天前
|
机器学习/深度学习 存储 人工智能
提升深度学习性能的利器—全面解析PAI-TorchAcc的优化技术与应用场景
在当今深度学习的快速发展中,模型训练和推理的效率变得尤为重要。为了应对计算需求不断增长的挑战,AI加速引擎应运而生。其中,PAI-TorchAcc作为一个新兴的加速引擎,旨在提升PyTorch框架下的计算性能。本文将详细介绍PAI-TorchAcc的基本概念、主要特性,并通过代码实例展示其性能优势。
17709 149
|
12天前
|
前端开发 Java Go
关于智能编码助手【通义灵码】,开发者们这么说...
现在通过体验活动首次完成通义灵码免费下载及使用的新用户,即可获得限量定制帆布包 1 个;分享体验截图到活动页面,还可参与抽奖活动,iPhone15 手机、机械键盘、智能手环等大奖等你拿!
7164 11