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

本文探讨了如何查询MySQL中最近5分钟更新过的表,分析了innodb_table_stats和information_schema.TABLES的区别。尽管innodb_table_stats的last_update字段在表变更后不一定会更新,但information_schema.TABLES能实时反映表的修改。然而,当table_definition_cache参数限制过多时,可能导致update_time字段变为NULL,从而影响统计。解决方案是调整table_definition_cache参数,但需注意内存使用情况。

问题1

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

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

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

时间到!

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

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

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

编辑切换为居中

添加图片注释,不超过 140 字(可选)

点击图片可放大

因此,通常查询表 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 是错的!

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

编辑切换为居中

添加图片注释,不超过 140 字(可选)

点击图片可放大

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

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

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

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

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

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

编辑切换为居中

添加图片注释,不超过 140 字(可选)

点击图片可放大

可以看到插入记录后,表 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分钟的思考时间。

编辑

添加图片注释,不超过 140 字(可选)

时间到。

这是因为表 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

编辑切换为居中

添加图片注释,不超过 140 字(可选)

而参数 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 的问题。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值