关于MySQL count(distinct) 逻辑的另一个bug

背景

         上一篇博文(链接)介绍了count distinct的一个bug。解决完以后发现客户的SQL语句仍然返回错误结果(0), 再查原因,发现了另外一个bug。也就是说,这个SQL语句触发了两个bug -_-

 

这里只说第二个,将问题简化后复现如下,影响已知的所有版本 。

 


drop table if exists tb;

set tmp_table_size=1024;

create table tb(id int auto_increment primary key, v varchar(32))engine=myisam charset=gbk;

insert into tb(v) values("aaa");

insert into tb(v) (select v from tb);

insert into tb(v) (select v from tb);

insert into tb(v) (select v from tb);

insert into tb(v) (select v from tb);

insert into tb(v) (select v from tb);

insert into tb(v) (select v from tb);

 

update tb set v=concat(v, id);

select count(distinct case when id<=64 then id end) from tb;

  返回64,正确

select count(distinct case when id<=63 then id end) from tb;

   返回0

上述中update语句的目的是将所有的v值设为各不相同。

 

与上个bug类似,5.5+的版本直接复现;5.1版本需要修改的是max_heap_table_size参数,而由于max_heap_table_size的最小值限制不能设置为1024,需要的测试数据量大些,但原理类似。

 

原因分析

         Count(distinct case when xxx then f end)的语义就是计算字段f的去重总数,计算流程细节参看前一篇。这里直接给出tmp_table_size不够大时的流程,便于说明此问题。

  

           流程:

1、  构造一个unique 集合A1, 将满足条件的结果插入A1中(计算了case when之后的值)

2、  插入item过程中若大小超过tmp_table_size,则将A1暂时写到文件中,再构造集合A2

3、  重复步骤2直到所有的item插入完成

因此若item很多则可能重复生成多个集合A1~An。

4、  对A1~An作合并操作。由于只是每个集合A保证unique,因此需要做类似归并排序的操作(实际上不需要排序,只是扫一遍)

5、  合并加和操作本来只需要去重和去掉NULL值即可,但为了复用代码,对于每个item,重新计算了一次结果的合法性,也就是,再判断一次case when是否正确。

6、  不幸的是,计算结果合法性的这些case when,其实是共同的一个:最后一行。

 

因此最后的结果是正确值还是0,就取决于最后一行的case when的结果。

案例分析

以上面这个case为例。由于使用主键,最后的一行必然是id=64的那一行。这样在合并的时候,若条件是id<=64 这些值都被认为符合条件可以合并。而最后一个语句的情况,最后一行id<=64不成立

 

作为验证可以看一下这个case


CREATE TABLE `tb2` (   `id` int(11) NOT NULL ,   `v` varchar(32) DEFAULT NULL ) ENGINE=MyISAM  DEFAULT CHARSET=gbk;

insert into tb2 (select * from tb order by id desc);

select count(distinct case when id<=63 then id end) from tb2;

返回63,正确

   可以看到,其实tb2和tb1的数据内容是一样的,只是tb2没有索引且数据倒置插入,因此查询的最后一行的id是1,满足id<=63, 结果记入就正确了。

 

解决方法

         调高tmp_table_size也是一种直接的方法,但是不治本,因为只要满足条件的行数足够多,就会出现这个问题。

 

         当然本质上这是一个bug。

         代码上,对于已经走到合并操作的这个逻辑,其实前面在构造各个集合A1~An的时候,已经验证过条件合法,其实在合并的时候,可以直接做去重操作即可。

时间: 2024-10-03 20:08:56

关于MySQL count(distinct) 逻辑的另一个bug的相关文章

Mysql count(*),DISTINCT效率研究

突然发现mysql教程对于count(*)的不同处理会造成不同的结果 比如执行 select count(*) from tablename 即使对于千万级别的数据mysql也能非常迅速的返回结果 而对于 select count(*) from tablename where-.. mysql的查询时间开始攀升 仔细查阅累下手册,发现当没有where语句对于整个mysql的表进行count运算的时候 myisam类型的表中保存有总的行数,而当添加有where限定语句的时候mysql需要对整个表

pdo,mysql 中binlog日志记录的一个 bug

最近发现数据库同步总是出问题,最诡异的时,主从数据库写入的数据不一样,我勒个去.程浩同学看了半天终于找到原因,原来是PDO的一个大坑,加上binlog的一个大坑. 首先声明,这篇文章有很强的攻击性,如果你利用这里面写的东西攻击,所造成的一切后果,自负!        起因:       2010/12/15 我的领导,突然要求我们开始折腾一下机器.主要的目的是,没做备份的,做一下备份,单个的数据库做主从,线上的机器要做一个能快速恢复的热备份.经过检查发现机器若干台需要整理,于是开始一一处理,其他

MySQL关于timestamp和mysqldump的一个“bug”

复现 来源于一个同事在做数据转储碰到的的问题,简化如下: 1.建表 drop table if exists tb; CREATE TABLE tb (   c timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ) ENGINE=InnoDB DEFAULT CHARSET=gbk; insert into tb values(now()); select * from tb;   返回 mysql> select * from tb; +----

mysql数据库UPDATE语句一个bug分析

这个我认为的bug,反馈给MySQL官方,但是MySQL官方认为这并不是一个bug,并给出了解释,我认为这个解释是合理的,但是不可避免的是这条语句实在太危险了. 问题描述 示例表结构与表数据: # 表结构 mysql> show create table t; +-------+--------------------------------------------------------------------------------------------------------------

MySQL内核月报 2014.08-MySQL· 捉虫动态·Count(Distinct) ERROR

背景 MySQL现行版本中存在一个count(distinct)语句返回结果错误的bug,表现为,实际结果存在值,但是用count(distinct)统计后返回的是0. 原因分析 Count(distinct f)的语义就是计算字段f的去重总数,计算流程大致如下: 流程一: 1. 构造一个unique集合A1(用tree实现) 2. 对每个值都试图插入集合A1中 3. 若和A1中现有item重复则直接跳过,不重复则插入并+1 4. 完成后计算集合中元素个数. 细心的同学会看到上面的语句中有一个s

MySQL中distinct和count(*)的使用方法比较_Mysql

首先对于MySQL的DISTINCT的关键字的一些用法: 1.在count 不重复的记录的时候能用到,比如SELECT COUNT( DISTINCT id ) FROM tablename:就是计算talbebname表中id不同的记录有多少条. 2,在需要返回记录不同的id的具体值的时候可以用,比如SELECT DISTINCT id FROM tablename:返回talbebname表中不同的id的具体的值. 3.上面的情况2对于需要返回mysql表中2列以上的结果时会有歧义,比如SE

mysql中distinct去除重复值方法

,而无法返回其它字段,这个问题让我困扰了很久,用distinct不能解决的话,我只有用二重循环查询来解决,而 这样对于一个数据量非常大的站来说,无疑是会直接影响到效率的.所以我花了很多时间来研究这个问题 mysql的DISTINCT的关键字有很多你想不到的用处 1.在count 不重复的记录的时候能用到 比如SELECT COUNT( DISTINCT id ) FROM tablename; 就是计算talbebname表中id不同的记录有多少条 2,在需要返回记录不同的id的具体值的时候可以

MySQL中distinct语句的基本原理及其与group by的比较_Mysql

DISTINCT 实际上和 GROUP BY 操作的实现非常相似,只不过是在 GROUP BY 之后的每组中只取出一条记录而已.所以,DISTINCT 的实现和 GROUP BY 的实现也基本差不多,没有太大的区别.同样可以通过松散索引扫描或者是紧凑索引扫描来实现,当然,在无法仅仅使用索引即能完成 DISTINCT 的时候,MySQL 只能通过临时表来完成.但是,和 GROUP BY 有一点差别的是,DISTINCT 并不需要进行排序.也就是说,在仅仅只是 DISTINCT 操作的 Query

mysql count详解及函数实例代码

mysql count详解 count函数是用来统计表中或数组中记录的一个函数,下面我来介绍在mysql中count函数用法. count(*) 它返回检索行的数目, 不论其是否包含 NULL值. SELECT 从一个表中检索,而不检索其它的列,并且没有 WHERE子句时, COUNT(*)被优化到最快的返回速度. 例如: mysql> SELECT COUNT(*) FROM student; COUNT(DISTINCT 字段)这个优化仅适用于 MyISAM表, 原因是这些表类型会储存一个函