MySQL · 捉虫动态 · event_scheduler 慢日志记错

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
简介: 问题背景最近遇到了 event_scheduler 在记录慢日志时的一个 bug,在这里分享给大家。为了方便描述问题,先构造一个简单的 event,如下:delimiter //create event event1 on schedule every 5 second starts now() ends date_add(now(), interval 1 hour)do beg

问题背景

最近遇到了 event_scheduler 在记录慢日志时的一个 bug,在这里分享给大家。

为了方便描述问题,先构造一个简单的 event,如下:

delimiter //
create event event1 on schedule every 5 second starts now() ends date_add(now(), interval 1 hour)
do begin
select sleep(1);
select * from t1;
select sleep(2);
end //
delimiter ;

其中的 t1 表中,有 2 条记录。

同时打开 event_scheduer 和 slow_log,并把慢日志的时间设置为 1s。

set global event_scheduler = on;
set global slow_query_log = on;
set global long_query_time = 1;
set global log_output = 'TABLE';

待 event 执行段时间后,查询 slow_log 会看到如下的结果:

+---------------------+------------+------------+-----------+-----------+---------------+------+----------------+-----------+-----------+------------------+-----------+
| start_time          | user_host  | query_time | lock_time | rows_sent | rows_examined | db   | last_insert_id | insert_id | server_id | sql_text         | thread_id |
+---------------------+------------+------------+-----------+-----------+---------------+------+----------------+-----------+-----------+------------------+-----------+
| 2017-01-14 16:15:33 | root[root] | 00:00:01   | 00:00:00  |         1 |             0 | test |              0 |         0 |         1 | select sleep(1)  |         4 |
| 2017-01-14 16:15:33 | root[root] | 00:00:01   | 00:00:01  |         3 |             2 | test |              0 |         0 |         1 | select * from t1 |         4 |
| 2017-01-14 16:15:35 | root[root] | 00:00:03   | 00:00:01  |         4 |             0 | test |              0 |         0 |         1 | select sleep(2)  |         4 |
| 2017-01-14 16:15:38 | root[root] | 00:00:01   | 00:00:00  |         1 |             0 | test |              0 |         0 |         1 | select sleep(1)  |         5 |
| 2017-01-14 16:15:38 | root[root] | 00:00:01   | 00:00:01  |         3 |             2 | test |              0 |         0 |         1 | select * from t1 |         5 |
| 2017-01-14 16:15:40 | root[root] | 00:00:03   | 00:00:01  |         4 |             0 | test |              0 |         0 |         1 | select sleep(2)  |         5 |
+---------------------+------------+------------+-----------+-----------+---------------+------+----------------+-----------+-----------+------------------+-----------+

可以看到,slow_log 中的 select * from t1select sleep(2) 相关记录是有问题的:

  1. select * from t1 不应该被记为慢日志,同时其中的 lock_time(应该为0) 和 rows_sent(应该为2) 都是错的;
  2. select sleep(2) 的 query_time(应该为2),lock_time(应该为0),和 rows_sent(应该为1) 也都是错的。

rows_sent 记错比较好确认,query_time 和 lock_time 记错我们可以从 start_time 和 thread_id 对照确认,另外select sleep(2) 是没有拿锁的,不应该有等锁的时间。

问题分析

为了搞清楚这个问题,我们需要了解 slow_log 是怎么记的。

slow_log 是在语句执行完后记录的,因为加锁时间和返回记录数这些信息,在执行之后才知道,general_log 记录是在语句解析完执行前。

slow_log 是否记录判读的逻辑在 log_slow_applicable() 中:

a) 是否没有用到索引,并且 log_queries_not_using_indexes 打开(这种情况下还可能触发 throttle 导致不记录)
b) 被标记为慢 SQL(thd->server_status & SERVER_QUERY_WAS_SLOW)

我们这里只关心 b) 这种 case。

在 SQL 执行结束时,会先调用 update_server_status() 来判断是否是慢 SQL,逻辑如下:

void update_server_status()
{
  ulonglong end_utime_of_query= current_utime();
  if (end_utime_of_query > utime_after_lock + variables.long_query_time)
    server_status|= SERVER_QUERY_WAS_SLOW;
}

utime_after_lock 表示的是拿到锁的时间点,server 层通过 THD::set_time_after_lock() 设置,引擎层(目前只有 InnoDB 支持)如果有锁请求等待时间的话,会累加到这个变量上,通过 thd_storage_lock_wait() 函数。

因此一个 SQL 因为等锁而导致执行时间长的话,是不会记入慢 SQL 的。

select * from t1 语句执行结束,调用 update_server_status() 时,根据执行时间判断是不满足慢 SQL 的,但是因为 event 在执行前 server_status 没有重置,后面调用 log_slow_applicable() 时,SERVER_QUERY_WAS_SLOW 这个标志位还在,因此最终记到 slow_log 里了。

因此一个 event 在执行中,其中只要有一个语句是慢 SQL,那么后面所有的都会被记成慢SQL。

而其中时间记错的原因也是这样,每次执行语句前,start_utime 没有重置,而 utime_after_lock 会在执行 select * from t1 时拿锁被更新。

query_time 和 lock_time 的计算逻辑如下(LOGGER::slow_log_print()):

 if (thd->start_utime)
 {
   query_utime= (current_utime - thd->start_utime);
   lock_utime=  (thd->utime_after_lock - thd->start_utime);
 }

因此 event 中语句的 query_time 一直是增加的,lock_time 也不是 0。

需要注意的是,start_time 记录的并不是语句开始执行的时间,而是记入 slow_log 时的时间。

rows_sent 也是因为没有重置,一直都是累加的,而 rows_examined 会在 JOIN::exec() 中被重置,因此记的是对的。

问题影响和解决

出现这种问题的前提是:

  1. 用了 MySQL 的 event_scheduler,并且 event 有多个 SQL 语句;
  2. 其中有一个 SQL 是慢SQL。

这个 bug 目前最新的 5.6.35/5.7.17 都受影响,官方已经确认,详见 bug#84450

知道原因后,fix 也就比较简单了,在 event 中每个 SQL 语句执行前,把 server_status, start_utime, m_sent_rows_count 重置掉就好了。

正常的用户 SQL 的执行逻辑就是这么干的,在 mysql_parse() 里会调用 THD::reset_for_next_command(),但是 event 执行过程中并没有调用这个函数。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
11天前
|
存储 SQL 关系型数据库
MySQL日志详解——日志分类、二进制日志bin log、回滚日志undo log、重做日志redo log
MySQL日志详解——日志分类、二进制日志bin log、回滚日志undo log、重做日志redo log、原理、写入过程;binlog与redolog区别、update语句的执行流程、两阶段提交、主从复制、三种日志的使用场景;查询日志、慢查询日志、错误日志等其他几类日志
MySQL日志详解——日志分类、二进制日志bin log、回滚日志undo log、重做日志redo log
|
9天前
|
SQL 缓存 关系型数据库
MySQL原理简介—7.redo日志的底层原理
本文介绍了MySQL中redo日志和undo日志的主要内容: 1. redo日志的意义:确保事务提交后数据不丢失,通过记录修改操作并在系统宕机后重做日志恢复数据。 2. redo日志文件构成:记录表空间号、数据页号、偏移量及修改内容。 3. redo日志写入机制:redo日志先写入Redo Log Buffer,再批量刷入磁盘文件,减少随机写以提高性能。 4. Redo Log Buffer解析:描述Redo Log Buffer的内存结构及刷盘时机,如事务提交、Buffer过半或后台线程定时刷新。 5. undo日志原理:用于事务回滚,记录插入、删除和更新前的数据状态,确保事务可完整回滚。
|
21天前
|
监控 Oracle 关系型数据库
Mysql、Oracle审计日志的开启
通过上述步骤,可以在 MySQL 和 Oracle 数据库中启用和配置审计日志。这些日志对于监控数据库操作、提高安全性和满足合规性要求非常重要。确保正确配置审计参数和策略,定期查看和分析审计日志,有助于及时发现并处理潜在的安全问题。
40 11
|
1月前
|
SQL 关系型数据库 MySQL
MySQL事务日志-Undo Log工作原理分析
事务的持久性是交由Redo Log来保证,原子性则是交由Undo Log来保证。如果事务中的SQL执行到一半出现错误,需要把前面已经执行过的SQL撤销以达到原子性的目的,这个过程也叫做"回滚",所以Undo Log也叫回滚日志。
MySQL事务日志-Undo Log工作原理分析
|
2月前
|
SQL 存储 关系型数据库
Mysql并发控制和日志
通过深入理解和应用 MySQL 的并发控制和日志管理技术,您可以显著提升数据库系统的效率和稳定性。
172 10
|
2月前
|
安全 关系型数据库 MySQL
MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!
《MySQL崩溃保险箱:探秘Redo/Undo日志确保数据库安全无忧!》介绍了MySQL中的三种关键日志:二进制日志(Binary Log)、重做日志(Redo Log)和撤销日志(Undo Log)。这些日志确保了数据库的ACID特性,即原子性、一致性、隔离性和持久性。Redo Log记录数据页的物理修改,保证事务持久性;Undo Log记录事务的逆操作,支持回滚和多版本并发控制(MVCC)。文章还详细对比了InnoDB和MyISAM存储引擎在事务支持、锁定机制、并发性等方面的差异,强调了InnoDB在高并发和事务处理中的优势。通过这些机制,MySQL能够在事务执行、崩溃和恢复过程中保持
123 3
|
2月前
|
SQL 存储 缓存
MySQL进阶突击系列(02)一条更新SQL执行过程 | 讲透undoLog、redoLog、binLog日志三宝
本文详细介绍了MySQL中update SQL执行过程涉及的undoLog、redoLog和binLog三种日志的作用及其工作原理,包括它们如何确保数据的一致性和完整性,以及在事务提交过程中各自的角色。同时,文章还探讨了这些日志在故障恢复中的重要性,强调了合理配置相关参数对于提高系统稳定性的必要性。
|
25天前
|
关系型数据库 MySQL 数据库连接
数据库连接工具连接mysql提示:“Host ‘172.23.0.1‘ is not allowed to connect to this MySQL server“
docker-compose部署mysql8服务后,连接时提示不允许连接问题解决
|
11天前
|
关系型数据库 MySQL 数据库
Docker Compose V2 安装常用数据库MySQL+Mongo
以上内容涵盖了使用 Docker Compose 安装和管理 MySQL 和 MongoDB 的详细步骤,希望对您有所帮助。
88 42
|
2天前
|
关系型数据库 MySQL 网络安全
如何排查和解决PHP连接数据库MYSQL失败写锁的问题
通过本文的介绍,您可以系统地了解如何排查和解决PHP连接MySQL数据库失败及写锁问题。通过检查配置、确保服务启动、调整防火墙设置和用户权限,以及识别和解决长时间运行的事务和死锁问题,可以有效地保障应用的稳定运行。
41 25

相关产品

  • 云数据库 RDS MySQL 版
  • 推荐镜像

    更多