本文来自:http://dinglin.iteye.com/blog/1976026#comments

背景

客户报告了一个count(distinct)语句返回结果错误,实际结果存在值,但是用count(distinct)统计后返回的是0。将问题简化后复现如下,影响已知的所有版本。

这里的 set tmp_table_size=1024; 一定是在插入前设置,这样下面的操作就是按照这个大小进行的,最终出现错误的结果,解决办法:

1,开始前设置足够大的tmp_table_size(推荐);

2,设置成1024,在不修改tmp_table_size 的前提下,设置 set sql_big_tables=1。

测试:

drop table if exists tb;

set tmp_table_size=1024;
#set sql_big_tables=1;
create table tb(id int auto_increment primary key, v varchar(32)) 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); 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 v) from tb; 返回0

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

原因分析
      Count(distinct f)的语义就是计算字段f的去重总数,计算流程大致如下:

流程一:

1、  构造一个unique集合A1(用tree实现)

2、  对每个值都试图插入集合A1中

3、  若和A1中现有item重复则直接跳过,不重复则插入并+1

4、  完成后计算集合中元素个数。

细心的同学会看到上面的语句中有一个set tmp_table_size的过程,集合A1并不能无限扩大,大小上限为tmp_table_size。若超过则上述流程变为

流程二:

1、  构造一个unique 集合A1

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

3、  重复步骤2直到所有的item插入完成。因此若item很多则可能重复生成多个集合A1~An。

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

5、  因此合并操作需要一个临时内存,长度为n,单元大小为key_length (key大小)。这个临时内存,用的也是tmp_table_size定义的大小。实际上在合并过程中还需要长为key_length的预留空间作临时内存保存。因此需要的空间为 (n+1)*key_length。

6、  在进行合并前会判断tmp_table_size >=(n+1)*key_length, 不满足则直接放弃合并。其结果就是返回为0。

案例分析

以上面这个case为例。字段v的单key大小为65  (65 = 32*2+1) 加上tree节点字占空间24字节共89字节。单个集合只能放11个item (1024/89), 因此n为 24 (24>=256/11), 在合并时需要 (24+1)*65= 1625字节的临时空间,大于1024,放弃合并。

Sql_big_tables

         实际上在最初处理这个问题时,俊达同学发现社区也有人讨论这个bug,并且指出在set sql_big_tables=on的时候,执行count(distinct)就能正确返回结果。原因就是在sql_big_tables=on的情况下,构造集合的方式是直接生成一个临时表,全部插入后直接计算临时表的大小作为结果,整个过程与tmp_table_size无关。

解决方法

      运维上,set sql_big_tables是一个方法,不过会影响性能。调高tmp_table_size算是正招。当然本质上这是一个bug。代码上,对于已经走到合并操作的这个逻辑,如果tmp_table_size不够,应该直接申请新的临时空间用于合并,完成后释放。虽然会造成临时征用内存,不过以现有的逻辑来看,临时征用的内存已经不少了-_-

另外一种时间换空间的方法,就是作多次合并。 相比之下第一种改造比较简单安全,提交的patch用第一种思路完成。后面看看社区有没有别的方案。

关于MySQL count(distinct) 逻辑的一个bug【转】的更多相关文章

  1. mysql count distinct 统计结果去重

    1.使用distinct去重(适合查询整张表的总数)有多个学校+教师投稿,需要统计出作者的总数select count(author) as total from files每个作者都投稿很多,这里有 ...

  2. MySQL 5.7.13 的一个BUG

    mysql今天从5.6切到5.7,在测试环境中,日志是全部打印的,发现打了一个警告: Incorrect string value: '\xD6\xD0\xB9\xFA\xB1\xEA...' for ...

  3. MySQL关于exists的一个bug

    今天碰到一个很奇怪的问题,关于exists的, 第一个语句如下: SELECT ) FROM APPLY t WHERE EXISTS ( SELECT r.APPLY_ID FROM RECORD ...

  4. Mysql中count(*),DISTINCT的使用方法和效率研究

    在处理一个大数据量数据库的时候 突然发现mysql对于count(*)的不同处理会造成不同的结果 比如执行 SELECT count(*) FROM tablename 即使对于千万级别的数据mysq ...

  5. 记录Window系统下myeclipes连接linux下mysql所出现的一个bug

    记录myeclipes远程连接mysql所出现的一个bug 今天在玩框架hibernate时,出现一个非常费解的bug,话不多说,先看bug Access denied for user 'root' ...

  6. mysql查询不重复的行内容,不重复的记录数.count,distinct

    有这么一个表 记录了id, p_id, p_name , p_content , p_time 1  343        aaa            aaaaaa   2012-09-01 2   ...

  7. php查询mysql时,报超出内存错误(select count(distinct))时

    学时服务器查询教练所带人数时,使用select count(distinct(u_STRNO))时报超出内存错误.后参考“mysqld-nt: Out of memory解决方法”http://jin ...

  8. MySQL游标循环取出空值的BUG

    早上同事要我写个MySQL去除重复数据的SQL,想起来上次写过一篇MySQL去除重复数据的博客,使用导入导出加唯一索引实现的,但是那种方式对业务影响较大,所以重新写一个存储过程来删重复数据,这一写就写 ...

  9. 由一个bug引发的SQLite缓存一致性探索

    问题 我们在生产环境中使用SQLite时中发现建表报“table xxx already exists”错误,但DB文件中并没有该表.后面才发现这个是SQLite在实现过程中的一个bug,而这个bug ...

随机推荐

  1. bootstrapValidator.js 做表单验证

    有这样的一个场景,我们在提交form表单的时候 可能要做一些验证,比如判断是不是为空,电话的格式验证,邮箱的格式验证等等,手写起来也是可以得. 但是今天我介绍一个bootstrap插件简化开发.就是b ...

  2. html页面 代码 编写的 一些 基本素养 约定 知识点

    hmtl代码书写也要养成一段一段的 区块代码, 每个区块代码 进行 html的 功能注释 自由文字的获得: (lorem ipsum: 乱数假文, 哑元文字) lorem ipsum: lipsum等 ...

  3. strtr函数的用法

    http://php.net/manual/en/function.strtr.php <?php $trans = array("h" => "-" ...

  4. 2015年12月01日 GitHub入门学习(二)手把手教你Git安装

    序:Mac与Linux中,Mac都预装了Git,各版本的Linux也都提供了Git的软件包.下面手把手教你Windows下的安装. 一.Git Windows GUI 下载地址 msysgit htt ...

  5. WP8.1下 Cortana语音命令 VCD文件 设计

    Windows Phone8.1下的Cortana,可以通过语音的方式,打开.设置应用,进行页面跳转.执行任务. 我们先要创建VCD(VoiceCommand.xml)文件 <?xml vers ...

  6. R笔记1

    gsub format > measurements<-c('3.95*3.99*2.43mm','3*3*5mm','2*2*2mm') > measurements [1] &q ...

  7. 点击验证码刷新(tp3.1)--超简单

    省略js点击刷新验证码,虽然看不懂 <img src='http://localhost/app/index.php/Index/verify/'  onclick='this.src=this ...

  8. CF #305 (Div. 2) C. Mike and Frog(扩展欧几里得&&当然暴力is also no problem)

    C. Mike and Frog time limit per test 1 second memory limit per test 256 megabytes input standard inp ...

  9. hdu.5212.Code(莫比乌斯反演 && 埃氏筛)

    Code Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submi ...

  10. 在使用开源library的PullToRefreshView中

    下拉刷新几乎是每个应用都会有的功能,且大部分用的都是开源项目,下载地址:下拉刷新.如何在页面刚打开的时候自动触发下拉刷新的呢? 只需要一句代码,在PullToRefreshAdapterView Ba ...