mysql-analyze table导致waiting for table flush
一、背景
一次普通的analyze table操作却锁住了后续的查询
mysql> select sleep(100) from a;
analyze table a;
mysql> select * from a;
# 遇到这种情况就需要查询阻塞的sql,然后kill掉,或者也可以等待
68050436 | test | 2025-02-26 11:52:40 | select sleep(100) from a
二、源码分析
这是percona解决这个问题所涉及的改动,增加了一个skip_flush 的布尔常量
@@ -906,6 +906,9 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
}
if (table->table)
{
+ const bool skip_flush=
+ (operator_func == &handler::ha_analyze)
+ && (table->table->file->ha_table_flags() & HA_ONLINE_ANALYZE);
if (table->table->s->tmp_table)
{
/*
@@ -915,7 +918,7 @@ static bool mysql_admin_table(THD* thd, TABLE_LIST* tables,
- 原逻辑:只要需要修改表或发生致命错误,就移除表缓存
- 新逻辑:当且仅当(不需要跳过刷新 且 需要修改表)或发生致命错误时,才移除表缓存
- 保留了致命错误时的强制刷新逻辑
*/
if (open_for_modify && !open_error)
table->table->file->info(HA_STATUS_CONST);
}
- else if (open_for_modify || fatal_error)
+ else if ((!skip_flush && open_for_modify) || fatal_error)
{
tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED,
table->db, table->table_name, FALSE);
下面是当前的完整代码
// 当表对象存在时
if (table->table)
{
// 判断是否需要跳过缓存刷新(在线分析且引擎支持在线分析时跳过)
const bool skip_flush =
(operator_func == &handler::ha_analyze) &&
(table->table->file->ha_table_flags() & HA_ONLINE_ANALYZE);
// 处理临时表逻辑
if (table->table->s->tmp_table)
{
/*
如果表未成功打开,则不尝试获取状态信息
(修复Bug#47633)
*/
if (open_for_modify && !open_error)
table->table->file->info(HA_STATUS_CONST); // 获取存储引擎状态信息
}
// 非临时表且满足以下条件时
else if ((!skip_flush && open_for_modify) || fatal_error)
{
// 从表定义缓存中移除该表(非强制模式)
tdc_remove_table(thd, TDC_RT_REMOVE_UNUSED,
table->db, table->table_name, FALSE);
/*
可能有修改操作发生,因此需要
使查询缓存失效
*/
table->table = 0; // 重置表指针(用于查询缓存失效)
query_cache.invalidate(thd, table, FALSE); // 使该表的查询缓存失效
}
else
{
/*
当执行 ALTER TABLE 的分区管理操作时
(如 ANALYZE/CHECK PARTITION)
重置需要处理的分区状态
*/
if (table->table->part_info &&
lex->alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION)
{
set_all_part_state(table->table->part_info, PART_NORMAL); // 将所有分区状态设为正常
}
}
}
/* 错误处理路径:管理命令执行失败时 */
if (thd->transaction_rollback_request)
{
/*
罕见情况:存储引擎请求事务回滚
(例如发生死锁时),执行回滚操作
*/
if (trans_rollback_stmt(thd) || trans_rollback_implicit(thd))
goto err; // 跳转到错误处理
}
else
{
// 正常提交事务
if (trans_commit_stmt(thd) || trans_commit_implicit(thd))
goto err; // 提交失败则跳转错误处理
}
// 关闭线程相关的表对象
close_thread_tables(thd);
// 释放事务相关的元数据锁
thd->mdl_context.release_transactional_locks();
三、percona解释
发生这种情况的原因是,在修复之前, ANALYZE TABLE 的 工作方式如下:
- 打开表统计信息:允许并发 DML 操作(INSERT / UPDATE / DELETE / SELECT )
- 更新表统计信息:允许并发 DML 操作
- 更新完成
- 使表定义缓存中的表条目无效:禁止并发 DML 操作
4.1. 这里发生的事情是ANALYZE TABLE 将当前打开的表共享实例标记为无效。这不会影响正在运行的查询:它们将照常完成。但所有传入查询都将无法启动,直到它们可以重新打开表共享实例。并且这在所有当前正在运行的查询完成之前不会发生。 - 使查询缓存无效:禁止并发 DML 操作
最后两个操作通常很快,但如果另一个查询触及表共享实例或获取查询缓存互斥锁,则它们无法完成。反过来,它也无法允许传入查询启动。
但是ANALYZE TABLE 修改的是表统计信息,而不是表定义!实际上,它不会以任何方式影响已经运行的查询。如果查询在ANALYZE TABLE 完成更新统计信息之前启动,则它使用旧统计信息。ANALYZE TABLE不会影响表中的数据。因此,查询缓存中的旧条目仍然是正确的 。它没有更改表的定义。因此,无需将其从表定义缓存中删除。因此,我们避免了上面的操作 4 和 5。
对lp:1704195的修复(迁移至PS-2503)删除了这些额外的更新以及它们所需的锁,并使ANALYZE TABLE 始终能够在繁忙的生产环境中安全运行。
参考文章:
八怪大佬的文章
percona解决的版本
mysql官方bug记录地址
percona解决的详细信息
percona解决的版本