摘要:为了保障华为云GaussDB产品的可靠性,每一款产品发布前都要通过多轮严苛的测试用例。

说明:本文中的MySQL,如果不做特殊说明,指的是开源社区版MySQL。

华为云数据库新版本在发布之前,会面临一系列严苛的测试规则,除了要求通过MySQL的所有测试用例之外,还需要通过由华为百万级更丰富、更贴近用户业务场景的测试用例构筑的测试防护网,以此充分验证新版本是否满足用户经典场景的稳定性。

正是在这样严苛的验证过程中,我们发现了MySQL的一个潜在Bug。

Bug描述

测试环境:

基于相同的测试用例、数据集,分别测试MySQL 8.0.22, MySQL 8.0.26,与华为云GaussDB(for MySQL)的返回结果。

测试语句:

select  subq_0.c2 as c0
from (select  ref_6.C_STATE as c0, case when ref_6.C_PHONE is not NULL then ref_5.C_ID else ref_5.C_ID endas c1, floor(ref_3.c_id) as c2from sqltester.t0_hash_partition_p1_view as ref_0right join sqltester.t4 as ref_1on (EXISTS (select  ref_1.c_middle as c0from sqltester.t1 as ref_2where ((false) and ((true) or (true))) or (false)))inner join sqltester.t0_range_key_subpartition_sub_view as ref_3on (EXISTS (select  ref_0.c_credit as c0, ref_1.c_street_1 as c1, ref_4.c_credit_lim as c2, ref_3.c_credit as c3from sqltester.t0_hash_partition_p1 as ref_4where true))left join sqltester.t10 as ref_5inner join sqltester.t11 as ref_6on (true)on (((pi() is not NULL)) and (false))where (((ref_5.C_D_ID is not NULL) or (ref_3.c_middle is not NULL)) )) as subq_0
where (EXISTS (select  subq_0.c0 as c0, pi() as c1, ref_11.c_street_1 as c2, ref_11.c_discount as c3, pi() as c4from sqltester.t0_partition_sub_view_mixed_001 as ref_11))
group by 1
order by 1;

返回结果:

如下图所示,MySQL 8.0.22、MySQL 8.0.26与华为云GaussDB(for MySQL)的返回结果不一致,也就是说产生了Bug,如下图红色部分。

Bug分析

首先确定哪一个执行结果是正确的。当前这个语句执行的execution plan是Hash Join,而MySQL8.0里面引入了Hash Join,由此推论开源版本可能存在问题。接下来我们从MySQL成熟版本以及非MySQL数据库两个方面来进行验证。

验证过程:

  • 使用相对成熟的版本MySQL 5.6进行验证,返回结果与GaussDB(for MySQL)相同,但与MySQL 8.0不同。
  • 使用PostgreSQL进行验证,执行结果与MySQL 5.6、GaussDB(for MySQL)相同,但与MySQL 8.0及更高版本不同。

由此可以确定:MySQL 8.0以及更高版本存在问题。

那么,是什么原因引起了这一Bug呢?

1、首先精简查询,以方便后面分析。经过多次验证,将查询简化如下:

SELECT count(*)
FROM(SELECT 1FROM sqltester.t4 AS ref_1INNER JOIN sqltester.t4 AS ref_3 ON (EXISTS(SELECT 1FROM sqltester.t4 AS ref_4WHERE TRUE ))LEFT JOIN sqltester.t10 AS ref_5 ON (FALSE)WHERE (((ref_5.C_D_ID IS NOT NULL)OR (ref_3.c_middle IS NOT NULL)))) AS subq_0执行计划如下:
-> Aggregate: count(0) (cost=2.75 rows=0)-> Filter: ((ref_5.C_D_ID is not null) or (ref_3.c_middle is null)) (cost=2.75 rows=0)-> Inner hash join (no condition) (cost=2.75 rows=0)-> Index scan on ref_3 using ndx_c_middle (cost=0.13 rows=50)-> Hash-> Inner hash join (no condition) (cost=1.50 rows=0)-> Index scan on ref_1 using ndx_c_id (cost=6.25 rows=50)-> Hash-> Left hash join (no condition) (cost=0.25 rows=0)-> Limit: 1 row(s) (cost=312.50 rows=1)-> Index scan on ref_4 using ndx_c_id (cost=312.50 rows=50)-> Hash-> Zero rows (Impossible filter) (cost=0.00..0.00 rows=0)

从上面的执行计划可以看出,ref_5被优化器进行了优化,转换成了Zero rows,而且ref_5是Left Hash Join的内表。作为Left Join的内表,如果内表没有匹配条件的记录(这里已经是Impossible条件了,也就是说连接条件始终是False),则需要内表生成NULL行来和外表进行外表连接。

2、在MySQL 8.0.22版本上执行问题查询,语句和执行结果如下:

SELECT count(*)
FROM(SELECT 1FROM sqltester.t4 AS ref_1INNER JOIN sqltester.t4 AS ref_3 ON (EXISTS(SELECT 1FROM sqltester.t4 AS ref_4WHERE TRUE ))LEFT JOIN sqltester.t10 AS ref_5 ON (FALSE)WHERE (((ref_5.C_D_ID IS NOT NULL) or(ref_3.c_middle IS NOT NULL))))AS subq_0;
++
| count(*) |
++
|     2500 |
++
1 row in set (0.00 sec)

3、对问题查询进行修改:去掉Where条件里面的另外一个条件(ref_3.c_middle is NULL)

现在Where条件只包含了(ref_5.C_D_ID IS NOT NULL)一个条件,要求当前查询过滤掉所有ref_5没有匹配的连接记录。

则SQL语句和执行结果如下:

SELECT count(*)
FROM(SELECT 1FROM sqltester.t4 AS ref_1INNER JOIN sqltester.t4 AS ref_3 ON (EXISTS(SELECT 1FROM sqltester.t4 AS ref_4WHERE TRUE ))LEFT JOIN sqltester.t10 AS ref_5 ON (FALSE)WHERE (((ref_5.C_D_ID IS NOT NULL))))assubq_0;++
| count(*) |
++
|     2500 |
++
1 row in set (0.01 sec)

对比修改前后的语句和执行结果可以看出:执行结果与条件(ref_3.c_middle is NULL)没有关系,只与(ref_5.C_D_ID IS NOT NULL)这个条件有关。正常情况下对ref_5表来说,因为是Impossible条件,所以ref_5被优化成了Zero rows。那么如果只剩(ref_5.C_D_ID IS NOT NULL)这个条件,正常的结果应该是空集(count返回0)。但现在开源版本的结果集却不是,这再次说明了开源版本出现了问题。

对于Left Join来说,如果Join条件不匹配,内表需要设置为NULL行来连接外表。而这里执行计划使用的是Zero rows,也就是说MySQL 8.0使用的是ZeroRowsIterator来执行的。执行器需要调用ZeroRowsIterator::SetNullRowFlag来设置Null flag。

4、通过gdb来查看设置是否正确:

Breakpoint 1, ZeroRowsIterator::SetNullRowFlag (this=0x7f92a413d510, is_null_row=false)at /mywork/mysql-sql/sql/basic_row_iterators.h:398
398            assert(m_child_iterator != nullptr);
(gdb) n
399            m_child_iterator->SetNullRowFlag(is_null_row);
(gdb) s
std::unique_ptr<RowIterator, Destroy_only<RowIterator> >::operator-> (this=0x7f92a413d520)at /opt/simon/taurus/mysql-root/src/tools/gcc-9.3.0/include/c++/9.3.0/bits/unique_ptr.h:355
355                return get();
(gdb) fin
Run till exit from #0 std::unique_ptr<RowIterator, Destroy_only<RowIterator> >::operator-> (this=0x7f92a413d520)at /opt/simon/taurus/mysql-root/src/tools/gcc-9.3.0/include/c++/9.3.0/bits/unique_ptr.h:355
ZeroRowsIterator::SetNullRowFlag (this=0x7f92a413d510,is_null_row=false)at /home/simon/mywork/mysql-sql/sql/basic_row_iterators.h:399
399            m_child_iterator->SetNullRowFlag(is_null_row);
Value returned is $1 = (RowIterator *) 0x7f92a413d4d0
(gdb) s
TableRowIterator::SetNullRowFlag (this=0x7f92a413d4d0,is_null_row=false)at /home/simon/mywork/mysql-sql/sql/records.cc:229
229          if (is_null_row) {
(gdb) n
232            m_table->reset_null_row();
(gdb)
234        }

从上面的gdb来看,断点处利用ZeroRowsIterator::SetNullRowFlag将表的Null flag设置为了False。后面的gdb信息也证明了这一点。

可以确定,导致此Bug的原因是:ZeroRowsIterator::SetNullRowFlag设置为False这里是不正确的。因为如果把ZeroRowsIterator::SetNullRowFlag设置为False,那就会导致内表为Zero Rows的Left Join生成内表非NULL的结果集。

如何解决

既然上面的Bug分析已经非常清楚了,那么修复起来也就比较简单了。只需要将ZeroRowsIterator::SetNullRowFlag始终设置为True就可以了。因为ZeroRowIterator只能产生两种结果,一种是空集,另一种就是作为外连接的内表产生NULL行。

对MySQL-8.0.26进行修复后,执行结果如下:

从返回的结果可以看出查询结果正确,也就是说问题得到了修复。

为了保障华为云GaussDB产品的可靠性,每一款产品发布前都要通过多轮严苛的测试用例。在发现问题后,华为云数据库团队以缜密的思路去逐步确定问题、分析问题,并第一时间修复Bug,解决问题,以确保客户的数据安全和业务结果的准确性。

华为云数据库团队荟聚了业内50%以上的数据库内核专家,以专业技术实时保障客户业务安全,助力企业业务安全上云!

华为云开年采购季盛大开幕!点击此处,快来0门槛抽奖!

点击关注,第一时间了解华为云新鲜技术~

如何从头到脚彻底解决一个MySQL Bug相关推荐

  1. 【从头到脚】撸一个多人视频聊天 — 前端 WebRTC 实战(一)

    前言 [ 从头到脚 ]会作为一个系列文章来发布,它包括但不限于 WebRTC 多人视频,预计会有: WebRTC 实战(一):也就是本期,主要是基础讲解以及一对一的本地对等连接,网络对等连接. Web ...

  2. Vchat — 从头到脚,撸一个社交聊天系统(vue + node + mongodb)

    前言 项目开始是因为工作需要一个聊天室功能,但是因为某些原因最终选用的是基于xmpp协议的Strophe.js写的.于是就想用node自己写一套,本来只是想简单的写个聊天页面,但是写完了又不满意,所以 ...

  3. 织梦mysql占用资源_解决一个 MySQL 服务器进程 CPU 占用 100%的技术笔记

    早上帮朋友一台服务器解决了Mysql cpu 占用 100%的问题.稍整理了一下: 朋友主机(Windows 2003 + IIS + PHP + MYSQL )近来 MySQL 服务进程 (mysq ...

  4. 解决一个MySQL登录报ERROR 2002 (HY000)错的问题

    一.背景 今天上午使用mysql-5.7.21-el7-x86_64.tar.gz包安装MySQL的时候报错如下:ERROR 2002 (HY000): Can't connect to local ...

  5. 从头到脚撸一个多人视频聊天 — WebRTC 实战(一)

    作者:江三疯,知乎.掘金账号同名,点击阅读原文查看作者 github. 前言 [ 从头到脚 ]会作为一个系列文章来发布,它包括但不限于 WebRTC 多人视频,预计会有: WebRTC 实战(一):也 ...

  6. 实践:《从头到脚撸一个多人视频聊天 — 前端 WebRTC 实战(一)》

    2019独角兽企业重金招聘Python工程师标准>>> 请先阅读原文,链接:从头到脚撸一个多人视频聊天 - 前端 WebRTC 实战(一),本文只涉及实践过程中的问题 1.video ...

  7. 4天4夜渡劫成功,解决10月1项目上线遇到的一个Mysql大坑,导致项目无法正常访问

    经历4天4夜解决10月1项目上线遇到的一个Mysql大坑,导致项目无法正常访问 一.问题重现 二.排查问题 三.解决问题 四.关于Mysql这两个参数的作用以及解释 五.总结 标题是不是惊讶到你了,但 ...

  8. mysql的从头到脚优化之服务器参数的调优

    一. 说到mysql的调优,有许多的点可以让我们去做,因此梳理下,一些调优的策略,今天只是总结下服务器参数的调优  其实说到,参数的调优,我的理解就是无非两点: 如果是Innodb的数据库,innod ...

  9. 从头到脚说单测——谈有效的单元测试(上篇)

    一. 为单元测试"正名" 我曾经认为,单元测试面向的是一个函数.任何走出一个函数的测试,都不是单元测试. 其实,对"单元"的定义取决于自己.如果你正在使用函数式 ...

最新文章

  1. lamp安装指南(转)
  2. 解决阿里云postfix无法发送邮件问题
  3. 形容等待时间长的句子_雅思听力该如何准确辨别句子结构?学会结构精听,雅思8分轻松get!...
  4. 关系数据库SQL之可编程性函数(用户自定义函数)
  5. java 贪吃蛇 控制台_控制台游戏贪吃蛇Java版
  6. FileWriter和FileReader解析
  7. VMware安装Linux(CentOS7)
  8. Android和.NET通用的AES算法
  9. 【codevs1576】最长严格上升子序列
  10. Python中文编程
  11. delphi 2010 mysql_DELPHI 10.2 TOKYO搭建LINUX MYSQL开发环境
  12. Spring mvc mybatis cxf Add CGLIB to your classpath. 错误
  13. 如何用latex高效写毕业论文(超简明+实用经验分享)
  14. Firefox火狐浏览器配置about:config优化加速
  15. 解决webpack : 无法加载文件 C:\Users\XXX\AppData\Roaming\npm\webpack.ps1因为在此系统上禁止运行脚本
  16. 将python脚本打包为exe可执行文件
  17. 中英双语多语言外贸企业网站源码系统 - HanCMS - 安装部署教程
  18. c运行库之 c语言标准库、glibc、 gcc库
  19. 计算机微课论文参考文献,计算机基础微课教学研究
  20. Linux中sysstat服务,Linux Sysstat性能收集的调整方法

热门文章

  1. 业务模型管理平台 开源_在可靠的开源模型上建立业务
  2. es6 Atomics对象
  3. 迷你世界电锯机器人_迷你世界:石像机器人升级版,制作如此简单!
  4. php把视频剪辑成15秒一段,如何快速分割视频 一个视频或一个电影截取变成几份的功能 一段段截取 太累了...
  5. java程序设计_十分钟说课—Java程序设计(耿祥义)
  6. 网络操作系统第1章习题
  7. urllib、requests库整理
  8. MySql8.0.11的安装与使用
  9. 大量删除MySQL中的数据
  10. c#使用椭圆签名算法制作软件序列号