mysql 设置断点返回_MySQL的DDL与DCL乱序BUG源码剖析

背景

DDL与DCL一直是工作中高频的手工操作,但两者之间的相互影响导致的BUG,笔者还是第一次见。请看下图:

7d01183247ada29f13eb2f2c7b157aa6.png

说明:在一个完全为空的MySQL实例上,如上红色框框中的SQL语句,第一句为创建用户,第二句为针对某表sakila.actor授权,第三句为创建表sakila.actor。细心的同学很快会发现,如果把该Binlog放到从库去执行,肯定会失败,因为授权语句先执行,但是要授权的表还不存在。排除sql_mode之类的原因,主从的参数一摸一样,怎么会这样?

初步分析

根据笔者前面文章关于提交的分析,自然的反应就是create table语句与grant privileges语句乱序了。为了验证笔者的猜测,在mysqld.trace文件中搜索该线程的debug_sync信号同步点,找到了提交前的信号同步点create_table_like_before_binlog,源码位置如下所示,

4c5b4558991e18db1b59f89364a2e168.png

结合mysqld.trace的日志与源码位置,可以确认该点位于语句提交前。

测试过程如下:

该测试通过使用MySQL官方测试工具debug_sync完成线程同步,具体可查看源码中大量的官方测试案例。
// MySQL config: debug
// MySQL config: debug_sync_timeout = 120
// Session 1
mysql> set debug_sync = 'reset';
Query OK, 0 rows affected (0.00 sec)

mysql> set debug_sync='create_table_like_before_binlog WAIT_FOR go';
Query OK, 0 rows affected (0.00 sec)

mysql> create table if not exists private.drop_if_exists_10 like private.drop_if_exists_1;
// ... Hangs.
// Session 2
mysql> show processlistG
...
*************************** 3. row ***************************
     Id: 7
   User: root
   Host: localhost
     db: NULL
Command: Query
   Time: 3
  State: debug sync point: create_table_like_before_binlog
   Info: create table if not exists private.drop_if_exists_10 like private.drop_if_exists_1
3 rows in set (0.00 sec)

mysql> grant select on private.drop_if_exists_10 to 'acl_user'@'localhost';
Query OK, 0 rows affected, 1 warning (0.01 sec)
// ... Grant successfully.

如上所示,线程1卡在提交前,而线程却授权成功!到此,BUG已确认属实。进一步,将对源码剖析,分析原因。

源码剖析

  • grant语句源码剖析

为了分析授权过程,找到函数mysql_table_grant设置断点,寻找过程本来不再陈述。有兴趣的同学可以根据grant语句的报错eng "Table '%-.192s.%-.192s' doesn't exist",找到相关的函数mysql_table_grant。调试到如下步骤,

d31cd7020ec1334dffc4a023d4ebd773.png

如上所示,从上往下,第一个红色框框为授权失败的逻辑流程,它直接跳出了授权逻辑,返回了错误。下面的展示窗口,展示了内部把库名和表名变成绝对路径下库表名/data/mysql/3306/data/private/drop_if_exists_10.frm的过程。此处的逻辑很简单,通过C函数access检查文件是否存在,如果不存在,就报错退出,否则就继续下去,执行授权。

要验证此处逻辑,很简单,只要授权之前touch一个对应的frm文件就行。如下,

以下两条语句分别为创建文件前与创建文件后,创建文件命令为 touch /data/mysql/3306/data/private/drop_if_exists_12.frm
mysql> grant select on private.drop_if_exists_12 to 'acl_user'@'localhost'; 
ERROR 1146 (42S02): Table 'private.drop_if_exists_12' doesn't exist
mysql> grant select on private.drop_if_exists_12 to 'acl_user'@'localhost';
Query OK, 0 rows affected, 1 warning (2.80 sec)
  • create语句源码剖析

为了分析create语句执行过程,可以从mysqld.trace日志该线程的日志中快速通过grep -i enter_stage以及grep debug_sync的方式快速熟悉流程,并找到关键函数create_table_impl设置断点进行调试。关键代码如下,

create_table_impl // 创建表主函数
>>if (check_engine(thd, db, table_name, create_info)) // 检查存储引擎是否支持该类型的表
>>prepare_error= mysql_prepare_create_table(thd, db, error_table_name, // 对字段进行检查并准备创建
>>	        create_info, alter_info,
>>	        internal_tmp_table,
>>	        &db_options, file,
>>	        key_info, key_count,
>>	        select_field_count);
>>THD_STAGE_INFO(thd, stage_creating_table); // 显示线程状态“creating table”
>>if (rea_create_table(thd, path, db, table_name, // 真正创建表
	                   create_info, alter_info->create_list,
	                   *key_count, *key_info, file, no_ha_table))
>>>>if (mysql_create_frm(thd, frm_name, db, table_name, create_info, // 创建.frm文件
	                     create_fields, keys, key_info, file))
>>>>if (file->ha_create_handler_files(path, NULL, CHF_CREATE_FLAG, // 创建存储引擎层数据库描述符
	                                  create_info))
>>>>if (!no_ha_table && // 创建聚簇索引,即InnoDB层创建.ibd文件
	    ha_create_table(thd, path, db, table_name, create_info, 0))

经过以上流程的梳理,整理的乱序分析如下图所示,

72db1e3c4f139f75b4feba925472ed93.png

结论

DDL的非原子性导致了与DCL的乱序,而MySQL 8.0已经废弃了.frm文件,此BUG将不复存在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值