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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
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
相关文章
|
NoSQL 关系型数据库 索引
从一个案例深入剖析InnoDB隐式锁和可见性判断(1)
从一个案例深入剖析InnoDB隐式锁和可见性判断
从一个案例深入剖析InnoDB隐式锁和可见性判断(1)
|
SQL 关系型数据库 MySQL
从一个案例深入剖析InnoDB隐式锁和可见性判断(2)
从一个案例深入剖析InnoDB隐式锁和可见性判断
148 0
从一个案例深入剖析InnoDB隐式锁和可见性判断(2)
|
关系型数据库 MySQL 索引
从一个案例深入剖析InnoDB隐式锁和可见性判断(3)
从一个案例深入剖析InnoDB隐式锁和可见性判断
|
存储 关系型数据库 MySQL
从一个案例深入剖析InnoDB隐式锁和可见性判断(4)
从一个案例深入剖析InnoDB隐式锁和可见性判断
|
2天前
|
调度 云计算 芯片
云超算技术跃进,阿里云牵头制定我国首个云超算国家标准
近日,由阿里云联合中国电子技术标准化研究院主导制定的首个云超算国家标准已完成报批,不久后将正式批准发布。标准规定了云超算服务涉及的云计算基础资源、资源管理、运行和调度等方面的技术要求,为云超算服务产品的设计、实现、应用和选型提供指导,为云超算在HPC应用和用户的大范围采用奠定了基础。
|
9天前
|
存储 运维 安全
云上金融量化策略回测方案与最佳实践
2024年11月29日,阿里云在上海举办金融量化策略回测Workshop,汇聚多位行业专家,围绕量化投资的最佳实践、数据隐私安全、量化策略回测方案等议题进行深入探讨。活动特别设计了动手实践环节,帮助参会者亲身体验阿里云产品功能,涵盖EHPC量化回测和Argo Workflows量化回测两大主题,旨在提升量化投研效率与安全性。
云上金融量化策略回测方案与最佳实践
|
11天前
|
人工智能 自然语言处理 前端开发
从0开始打造一款APP:前端+搭建本机服务,定制暖冬卫衣先到先得
通义灵码携手科技博主@玺哥超carry 打造全网第一个完整的、面向普通人的自然语言编程教程。完全使用 AI,再配合简单易懂的方法,只要你会打字,就能真正做出一个完整的应用。
8905 20
|
15天前
|
Cloud Native Apache 流计算
资料合集|Flink Forward Asia 2024 上海站
Apache Flink 年度技术盛会聚焦“回顾过去,展望未来”,涵盖流式湖仓、流批一体、Data+AI 等八大核心议题,近百家厂商参与,深入探讨前沿技术发展。小松鼠为大家整理了 FFA 2024 演讲 PPT ,可在线阅读和下载。
4769 12
资料合集|Flink Forward Asia 2024 上海站
|
15天前
|
自然语言处理 数据可视化 API
Qwen系列模型+GraphRAG/LightRAG/Kotaemon从0开始构建中医方剂大模型知识图谱问答
本文详细记录了作者在短时间内尝试构建中医药知识图谱的过程,涵盖了GraphRAG、LightRAG和Kotaemon三种图RAG架构的对比与应用。通过实际操作,作者不仅展示了如何利用这些工具构建知识图谱,还指出了每种工具的优势和局限性。尽管初步构建的知识图谱在数据处理、实体识别和关系抽取等方面存在不足,但为后续的优化和改进提供了宝贵的经验和方向。此外,文章强调了知识图谱构建不仅仅是技术问题,还需要深入整合领域知识和满足用户需求,体现了跨学科合作的重要性。
|
23天前
|
人工智能 自动驾驶 大数据
预告 | 阿里云邀您参加2024中国生成式AI大会上海站,马上报名
大会以“智能跃进 创造无限”为主题,设置主会场峰会、分会场研讨会及展览区,聚焦大模型、AI Infra等热点议题。阿里云智算集群产品解决方案负责人丛培岩将出席并发表《高性能智算集群设计思考与实践》主题演讲。观众报名现已开放。