当前位置: 首页 > news >正文

这个 MySQL 问题困扰了我一个月,现在终于把他解决了

最近在追《东八区的先生们》,真是一部不错的青春剧。

可以比肩姜老师心目中另两部的神剧《奋斗》和《蜗居》。

不过《东八区的先生们》豆瓣评分仅2.1分,创评分新低。

只能感叹主演张翰观众缘不佳......不能多说,说多了可能会得罪某种可怕势力。

在追剧的这一个月中,团队小伙伴线上遇到了一个“吊诡”的问题,很值得深思和举一反三,故今天拿来和各位同学分享。

1

问题1

首先,姜老师先来问 一个问题: 如何获得 MySQL 数据库中最近5分钟更新过的表 ?

别看这个问题简单,然而大部分同学并不一定能答上来。

给同学们3分钟的思考时间。

时间到!

这个问题的本质是涉及到对于 MySQL 元数据字典表的了解。

关于 MySQL 的元数据字典表有两张,一张是mysql数据库下的表 innodb_table_stats,另一张是 information_schema 数据库下的表 TABLES。

有经验的 DBA 知道元数据字典表 innodb_table_stats 是 InnoDB 存储引擎,而元数据字典表 TABLES 在 5.7 版本中是 Memory 引擎。

点击图片可放大

因此,通常查询表 innodb_table_stats 的速度会快很多,并且该表也有 last_update 字段,可用于查询最近5分钟发生修改的表(假设我们只需知道 InnoDB 的表),如:

SELECT * FROM innodb_table_stats
WHERE last_update > DATE_SUB(NOW(), INTERVAL 5 MINUTES)
AND last_update =< NOW()

然而,很可惜,查询元数据字典表 innodb_table_stats 是错的!

很简单,看下面这个简单的例子:

点击图片可放大

可以看到对表 t 进行变更后,元数据字典表中的 last_update 值并没有发生变化。

这是因为对于元数据字典表 innodb_table_stats 的更新是有条件的,只有当表中的超过 10% 的记录发生变更时,才会触发更新!

另外,虽然文档中说可以通过命令 FLUSH TABLE tbl_name 触发手动重新统计表的元数据信息,但是根据姜老师的测试发现,貌似也没有触发。

当然,这不是关键,因为我们要获取的最近 5 分钟内发生变更过的数据,即通过 1 条 SQL 取得所有发生变更的表名。

所以,我们的目标不得不转向 information_schema 数据库下的表 TABLES。

虽然该表的存储引擎为 Memroy ,但他是实时更新的。如:

点击图片可放大

可以看到插入记录后,表 t 的最后修改时间从15:17:59 变为了 17:24:04,是最新变更的时间。

所以,查询最近5分钟发生变化的表,可以通过下面的命令:

SELECT * FROM information_schema.TABLES
WHERE (update_time > DATE_SUB(NOW(), INTERVAL 5 MINUTES)
    AND update_time =< NOW())

2

问题2

接着的问题是,为什么表 TABLES 可以实时更新?实时更新的代价不是会很大么?

再给同学们3分钟的思考时间。

时间到。

这是因为表 innodb_table_stats 需要持久化到磁盘,每次表变更就更新元数据字典表的话,会导致开销增大。

而表 TABLES 是元数据字典表内存数据结构的一种映射,并通过 Memroy 引擎绑定将最后的数据显示出来而已。

InnoDB 存储引擎中对于表的元数据字典结构定义为 dict_table_t,其大致定义如下所示:

struct dict_table_t {
  table_name_t    name;
  time_t          update_time;
  ...
}

3

问题3

然而,在使用表 TABLES 的过程中,我们发现在某台 MySQL 实例上发生了一个”诡异“的现象:

表 TABLES 中某些记录的 update_time 字段会诡异地从非 NULL 值更新为 NULL 值

上述现象导致统计 5 分钟内发生变更表的遗漏,最终产生了另一个服务的错误。

但是,小伙伴通过源码阅读发现这个现象是一个正常的现象!

因为 InnoDB 存储引擎层面存储表的元数据字典信息 dict_table_t 在内存中其实一个 LRU 的数据结构:

struct dict_sys_t{
  UT_LIST_BASE_NODE_T(dict_table_t) table_LRU;
  ...
}


struct dict_table_t {
  table_name_t    name;
  UT_LIST_NODE_T(dict_table_t)    table_LRU;
  time_t          update_time;
  ...
}

而 InnoDB 存储引擎的 Master 后台线程会定期进行扫描,确保这个 LRU 链表中元字典数据表的数量不要超过参数  table_definition_cache 

而参数 table_definition_cache 设置的值为 4000,这意味着若 5 分钟内有超过 4000 张表发生过打开,又或有超过 4000 张表发生修改,那么其中某些表就会被从 LRU 链表中移除。

待下次读取表的元数据字典信息时, 会重新分配和初始化表的元数据字典对象,而这时 update_time 就会显示为 NULL,从而导致统计出错。

分析完原因后,要解决上述问题就很简单了,只需要调大参数  table_definition_cache 即可。

但参数调大后,意味着 MySQL 的内存使用率会增大。

虽然 dict_table_t 结构本身只占用不到 700 字节,但这个结构中还有列名信息,索引元数据字典信息等:

struct dict_table_t {
  table_name_t    name;
  UT_LIST_NODE_T(dict_table_t)    table_LRU;
  time_t          update_time;
  const char*     col_names;
  UT_LIST_BASE_NODE_T(dict_index_t)  indexes;
  ...
}

如果表的列很多很长,表上的索引数量也较多,那么数据库实例占用的内存会更大。

所以同学们线上一定要预留足够的内存空间,否则可能会产生 OOM 的问题。

以上。

4

今日思考题

1. 元数据字典信息中表的数量会超过参数 table_definition_cache 的设置么?为什么?

2. 参数 table_definition_cache 应该设置到多大才合理?是否有监控可以统计到被刷出 LRU 列表的次数?

3. 表 information_schema.TABLES 底层实现上,MySQL 8.0 和 5.7 有何不同?

4. 在 MySQL 8.0 版本中使用表 information_schema.TABLES ,需要注意哪个参数?否则可能会导致查询不及时呢?

5. 表最后更新的时间 last_update 是在 InnoDB 源码哪个函数中被修改的?

相关文章:

  • SOD酶活性测定丨Abbkine超氧化物歧化酶(SOD)活性检测试剂盒
  • 参数解释安捷伦86142B光学分析仪
  • 利用回调函数在driver中收集覆盖率
  • ch05 pointer
  • Java:Kubernetes原生Java与Quarkus
  • 15天深度复习JavaWeb的详细笔记(十二)——综合案例
  • AD生成Gerber及CAM350检查
  • Python数据分析:折线图和散点图的绘制
  • 【Vue2基础】Vue项目搭建及组件使用
  • 艾美捷衣霉素Tunicamycin 化学性质及引用文献
  • 【小样本分割】Self-Support Few-Shot Semantic Segmentation
  • ch01变量和数据结构
  • 五分钟学会一门编程语言?
  • 【Python数据分析 - 11】:DataFrame索引操作(pandas篇)
  • [Vue]数据代理
  • JS 中的深拷贝与浅拷贝
  • canvas 绘制双线技巧
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • CentOS7简单部署NFS
  • C学习-枚举(九)
  • Effective Java 笔记(一)
  • express如何解决request entity too large问题
  • idea + plantuml 画流程图
  • jQuery(一)
  • MySQL主从复制读写分离及奇怪的问题
  • Python - 闭包Closure
  • python docx文档转html页面
  • Python 基础起步 (十) 什么叫函数?
  • Redash本地开发环境搭建
  • select2 取值 遍历 设置默认值
  • tensorflow学习笔记3——MNIST应用篇
  • 对象管理器(defineProperty)学习笔记
  • 飞驰在Mesos的涡轮引擎上
  • 搞机器学习要哪些技能
  • 前端_面试
  • 深入浅出Node.js
  • 收藏好这篇,别再只说“数据劫持”了
  • 自定义函数
  • 机器人开始自主学习,是人类福祉,还是定时炸弹? ...
  • 组复制官方翻译九、Group Replication Technical Details
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​七周四次课(5月9日)iptables filter表案例、iptables nat表应用
  • ###C语言程序设计-----C语言学习(6)#
  • #pragma pack(1)
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (ZT) 理解系统底层的概念是多么重要(by趋势科技邹飞)
  • (超简单)构建高可用网络应用:使用Nginx进行负载均衡与健康检查
  • (附源码)计算机毕业设计SSM疫情居家隔离服务系统
  • (转)Sql Server 保留几位小数的两种做法
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .NET Micro Framework初体验
  • .NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试