数据库诊断不了的,腾讯大神来“诊断”
腾讯云数据库
共 7510字,需浏览 16分钟
· 2021-03-27
| 作者 王文安,腾讯CSIG数据库专项的数据库工程师,主要负责腾讯云数据库 MySQL 的相关的工作,热爱技术,欢迎留言进行交流。
这时候该怎么处理呢?
先上结论,如果这种现象已经发生了,可以尝试 kill 掉“最早的”那些慢查询。
即如果 tb1 上有慢查询,且进行了 analyze 后遇到了问题,找一下 tb1 上在 analyze 之前已经开始执行,但是没结束的慢查询,然后全部 kill 掉。
接下来我们来分析下这么做的原因是什么?
一、问题还原
先来构造一下场景:
CREATE TABLE `stu` (
`id` int(11) NOT NULL,
`name` varchar(16) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`),
KEY `idx_age` (`age`),
KEY `idx_n_a` (`name`,`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
INSERT INTO `stu` VALUES (9,'adam',25),(7,'carlos',25),(1,'dave',19),(5,'sam',22),(3,'tom',22),(11,'zoe',29);
这时候来伪造一个长时间执行的慢查询:
mysql> select sleep(3600) from stu;
然后在其他的 session 模拟 analyze 和 select 的操作:
这时候会发现这个 limit 1 的语句也会被阻塞,而且也不会触发innodb_lock_wait_timeout。mysql> analyze table stu;
+----------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+----------+---------+----------+----------+
| test.stu | analyze | status | OK |
+----------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> select * from stu limit 1;
如果在其他的 session 看 processlist,会发现如下等待事件:
mysql> show processlist;
+-----+------+-----------------+--------------------+---------+------+-------------------------+-----------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-----+------+-----------------+--------------------+---------+------+-------------------------+-----------------------------+
| 457 | root | 127.0.0.1:48650 | sbtest | Sleep | 4860 | | NULL |
| 458 | root | 127.0.0.1:48652 | sbtest | Sleep | 4851 | | NULL |
| 473 | root | 127.0.0.1:49512 | performance_schema | Sleep | 4834 | | NULL |
| 477 | root | 127.0.0.1:52364 | test | Query | 26 | User sleep | select sleep(3600) from stu |
| 478 | root | 127.0.0.1:53124 | test | Query | 10 | Waiting for table flush | select * from stu limit 1 |
| 479 | root | 127.0.0.1:53944 | sbtest | Query | 0 | starting | show processlist |
| 480 | root | 127.0.0.1:53946 | sbtest | Sleep | 958 | | NULL |
+-----+------+-----------------+--------------------+---------+------+-------------------------+-----------------------------+
7 rows in set (0.00 sec)
mysql>
二、原因分析
那么问题已经明了,且等待事件也清楚地指向了Waiting for table flush,那么从这个地方入手,看看原因是什么。先看看官方文档的解释:
Waiting for table flush The thread is executing FLUSH TABLES and is waiting for all threads to close their tables, or the thread got a notification that the underlying structure for a table has changed and it needs to reopen the table to get the new structure. However, to reopen the table, it must wait until all other threads have closed the table in question.This notification takes place if another thread has used FLUSH TABLES or one of the following statements on the table in question: FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE, or OPTIMIZE TABLE.
可以看到这个状态出现的原因已经写得很清楚了:因为这个表的结构已经“改变”,所以新线程在打开 table 的时候需要等其他的线程先关闭。
那么再看一下analyze到底干了什么,引用官方文档的内容:
ANALYZE TABLE removes the table from the table definition cache, which requires a flush lock. If there are long running statements or transactions still using the table, subsequent statements and transactions must wait for those operations to finish before the flush lock is released. Because ANALYZE TABLE itself typically finishes quickly, it may not be apparent that delayed transactions or statements involving the same table are due to the remaining flush lock. ...... ANALYZE TABLE clears table statistics from the INFORMATION_SCHEMA.INNODB_SYS_TABLESTATS table and sets the STATS_INITIALIZED column to Uninitialized. Statistics are collected again the next time the table is accessed.
通过描述可以看到analyze会尝试获取 flush 锁,同时重新做数据取样的操作其实是由下一个select发起的。
那么问题变成了:实际阻塞的时候,是在重新做数据取样时,还是在等待其他线程关闭 table?
首先要了解一下 MySQL 的两个东西:table_defination 和 table_open_cache。
简单来说,一个 Client 想 open table 的时候,会先尝试从 cache 里面拿,如果这个表有“新版本”,或者 cache 没有的时候,就会从 table_defination 去 copy 一份最新的数据。
因此,在上文构造的环境里面,扫一下堆栈的信息,看看发生了什么,去掉干扰信息,找到 select 语句的信息:
futex_abstimed_wait_cancelable,
__pthread_cond_wait_common,
__pthread_cond_timedwait,
MDL_wait::timed_wait,
TABLE_SHARE::wait_for_old_version,
open_table,
open_tables,
open_tables_for_query,
::??,
mysql_execute_command,
mysql_parse,
dispatch_command,
do_command,
handle_connection,
pfs_spawn_thread,
start_thread,clone
很明显的看到语句处于等待状态,而且是wait for old version,看起来有点奇怪,那么看看这个open_table 函数在干嘛:
open_tables()
{
...
if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))
{
if (share->has_old_version()) // 如果存在 old_version
{
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
MDL_deadlock_handler mdl_deadlock_handler(ot_ctx);
bool wait_result;
...
wait_result= tdc_wait_for_old_version(thd, table_list->db,
table_list->table_name,
ot_ctx->get_timeout(),
deadlock_weight);
thd->pop_internal_handler();
...
if (thd->open_tables && thd->open_tables->s->version != share->version)
//如果存在不同的version,那么需要释放掉所有该表的cache,然后reopen
{
release_table_share(share);
mysql_mutex_unlock(&LOCK_open);
(void)ot_ctx->request_backoff_action(Open_table_context::OT_REOPEN_TABLES,
NULL);
DBUG_RETURN(TRUE);
}
}
......
tdc_wait_for_old_version(THD *thd, const char *db, const char *table_name,
ulong wait_timeout, uint deadlock_weight)
{
TABLE_SHARE *share;
bool res= FALSE;
mysql_mutex_lock(&LOCK_open);
if ((share= get_cached_table_share(thd, db, table_name)) &&
share->has_old_version())
//在这里获取表并进行表的version判断,如果old_version一直存在的话,进入if代码
{
struct timespec abstime;
set_timespec(&abstime, wait_timeout);
res= share->wait_for_old_version(thd, &abstime, deadlock_weight);
}
mysql_mutex_unlock(&LOCK_open);
return res;
}
可以看到 open_table 发现有 old_version 存在的时候,会调用 tdc_wait_for_old_version,如果这个表的 old_version 一直存在,则会一直等待。所以这个 select 语句其实一直处于等待状态,等待 old_version 的表 cache 被释放。
而这个 version,在 MySQL 中用来标记 table_defination 的版本,这个 version 更新了,则代表这个表的结构“发生了变化”,所有该表的 cache 都是失效的,不能再继续使用。这个变量在 MySQL 中是refresh_version。
所以可以判断出,analyze table 递增了这个 refresh_version,虽然代码注释中写明了目前仅在 flush_table 的时候才会变更,不过测试环境中也只有 analyze 这个操作,联系 analyze 操作会尝试获取 flush 锁,所以可能 analyze 在实现的时候也利用了 flush 的机制吧。
PS:实际上如果后来执行的不是 select,而是继续对这个表进行 analyze 的话,也会被阻塞。
最后考虑到这个 old_version 的问题特点,拓展一下可能会遇到这个问题的场景:
1.analyze;2.flush table 可能会遇到,因为也会递增 refresh_version;3.flush table with read lock 也会遇到,因为也是 flush 操作;4.涉及到 table_defination 变化的,比如说 DDL。
关于专栏
《腾讯云数据库专家服务》是由腾讯云数据库技术服务团队维护的社区专栏,涵盖了各类数据库的实际案例,最佳实践,版本特性等内容。目前专栏文章仍在持续丰富中,欢迎在文章末尾留言互动,给出宝贵的建议。
- End -
更多精彩
评论
真高!比亚迪员工爆料比亚迪在越南的薪资水平:基本工资480万,全勤奖35万,交通补助20万,餐补110万,每周6天,每天10小时
上一篇:某大公司为逼迫员工离职,竟然把他的工位安排到厕所旁,没想到他直接开始记录领导的如厕时间,还发到公司大群...对此,你怎么看?--完--PS:欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。全文完,感谢你的耐心阅读。如果你还想看到我的文章,请一定给本
开发者全社区
0
太敢穿了!透视纱裙!性感火辣的身材
绝了呀今天的厂花:吴宣仪1995年1月26日,吴宣仪出生于海南省海口市,中国内地流行乐女歌手、影视演员。2016年2月,吴宣仪随宇宙少女发行首张迷你专辑正式出道。2018年4月,她参加《创造101》综艺选秀,获得第二名,成功加入火箭少女101组合。吴宣仪的颜值一直备受称赞,她的五官立体精致,皮肤白皙
逆锋起笔
0
某大公司为逼迫员工离职,竟然把他的工位安排到厕所旁,没想到他直接开始记录领导的如厕时间,还发到公司大群...
上一篇:字节的跳动职级与薪资(2024年)我们与公司间的合作,宛如两艘船只在茫茫大海上相互依靠,共同抵御风浪,携手驶向成功的彼岸。然而,当航向开始产生分歧,或是波涛汹涌的风浪改变了我们的初衷,我们或许应当冷静地选择和平分手,而非在风雨中硬撑。最近,一位网友的遭遇引起了广大职场人的关注和热议。这位网友
开发者全社区
0
金融研究 | 使用Python测量关键审计事项的「信息含量」
Tips: 公众号推送后内容只能更改一次,且只能改20字符。如果内容出问题,或者想更新内容, 只能重复推送。为了更好的阅读体验,建议阅读本文博客版, 链接地址https://textdata.cn/blog/2023-01-13-information-content-of-critical-aud
大邓和他的Python
0
我看阿里的年终奖总算发了!
到4月底了,这两天看朋友圈,发现阿里的年终奖终于发了,问了问老同学,也从网上检索了不少信息,基本搞清楚了阿里今年的年终奖情况。近来来阿里一些集团对绩效等级做了较大的调整,以前的旧绩效系统中,绩效分为3.25、3.5、3.75、4和5五个等级,其中4和5是较高绩效等级,较少见。而且之前3.5绩效内部划
公子龙
0
CVPR 2024|大视觉模型的开山之作!无需任何语言数据即可打造大视觉模型
↑ 点击蓝字 关注极市平台作者丨科技猛兽编辑丨极市平台极市导读 本文提出一种序列建模 (sequential modeling) 的方法,不使用任何语言数据,训练大视觉模型。>>加入极市CV技术交流群,走在计算机视觉的最前沿本文目录1 序列建模打造大视觉模型(来自 U
极市平台
1
金融研究(更新) | 使用Python构建关键审计事项的「信息含量」
Tips: 公众号推送后内容只能更改一次,且只能改20字符。如果内容出问题,或者想更新内容, 只能重复推送。为了更好的阅读体验,建议阅读本文博客版, 链接地址https://textdata.cn/blog/2023-01-13-information-content-of-critical-aud
大邓和他的Python
0
字节的跳动职级与薪资(2024年)
上一篇:阿里公布年终奖,P7, 3.5+,22W年终奖,还有35W长期现金激励,真香字节跳动自2012年3月成立以来,已经迅速成长为一个全球性的科技公司。其产品和服务已经遍布全球150多个国家与地区,并且支持超过75种不同的语言。在字节跳动的官方网站上,列出了一系列引人注目的产品和服务,包括但不限于
开发者全社区
0