关于MySQL count(distinct) 逻辑的一个bug【转】
本文来自: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【转】的更多相关文章
- mysql count distinct 统计结果去重
1.使用distinct去重(适合查询整张表的总数)有多个学校+教师投稿,需要统计出作者的总数select count(author) as total from files每个作者都投稿很多,这里有 ...
- MySQL 5.7.13 的一个BUG
mysql今天从5.6切到5.7,在测试环境中,日志是全部打印的,发现打了一个警告: Incorrect string value: '\xD6\xD0\xB9\xFA\xB1\xEA...' for ...
- MySQL关于exists的一个bug
今天碰到一个很奇怪的问题,关于exists的, 第一个语句如下: SELECT ) FROM APPLY t WHERE EXISTS ( SELECT r.APPLY_ID FROM RECORD ...
- Mysql中count(*),DISTINCT的使用方法和效率研究
在处理一个大数据量数据库的时候 突然发现mysql对于count(*)的不同处理会造成不同的结果 比如执行 SELECT count(*) FROM tablename 即使对于千万级别的数据mysq ...
- 记录Window系统下myeclipes连接linux下mysql所出现的一个bug
记录myeclipes远程连接mysql所出现的一个bug 今天在玩框架hibernate时,出现一个非常费解的bug,话不多说,先看bug Access denied for user 'root' ...
- mysql查询不重复的行内容,不重复的记录数.count,distinct
有这么一个表 记录了id, p_id, p_name , p_content , p_time 1 343 aaa aaaaaa 2012-09-01 2 ...
- php查询mysql时,报超出内存错误(select count(distinct))时
学时服务器查询教练所带人数时,使用select count(distinct(u_STRNO))时报超出内存错误.后参考“mysqld-nt: Out of memory解决方法”http://jin ...
- MySQL游标循环取出空值的BUG
早上同事要我写个MySQL去除重复数据的SQL,想起来上次写过一篇MySQL去除重复数据的博客,使用导入导出加唯一索引实现的,但是那种方式对业务影响较大,所以重新写一个存储过程来删重复数据,这一写就写 ...
- 由一个bug引发的SQLite缓存一致性探索
问题 我们在生产环境中使用SQLite时中发现建表报“table xxx already exists”错误,但DB文件中并没有该表.后面才发现这个是SQLite在实现过程中的一个bug,而这个bug ...
随机推荐
- bootstrapValidator.js 做表单验证
有这样的一个场景,我们在提交form表单的时候 可能要做一些验证,比如判断是不是为空,电话的格式验证,邮箱的格式验证等等,手写起来也是可以得. 但是今天我介绍一个bootstrap插件简化开发.就是b ...
- html页面 代码 编写的 一些 基本素养 约定 知识点
hmtl代码书写也要养成一段一段的 区块代码, 每个区块代码 进行 html的 功能注释 自由文字的获得: (lorem ipsum: 乱数假文, 哑元文字) lorem ipsum: lipsum等 ...
- strtr函数的用法
http://php.net/manual/en/function.strtr.php <?php $trans = array("h" => "-" ...
- 2015年12月01日 GitHub入门学习(二)手把手教你Git安装
序:Mac与Linux中,Mac都预装了Git,各版本的Linux也都提供了Git的软件包.下面手把手教你Windows下的安装. 一.Git Windows GUI 下载地址 msysgit htt ...
- WP8.1下 Cortana语音命令 VCD文件 设计
Windows Phone8.1下的Cortana,可以通过语音的方式,打开.设置应用,进行页面跳转.执行任务. 我们先要创建VCD(VoiceCommand.xml)文件 <?xml vers ...
- R笔记1
gsub format > measurements<-c('3.95*3.99*2.43mm','3*3*5mm','2*2*2mm') > measurements [1] &q ...
- 点击验证码刷新(tp3.1)--超简单
省略js点击刷新验证码,虽然看不懂 <img src='http://localhost/app/index.php/Index/verify/' onclick='this.src=this ...
- 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 ...
- hdu.5212.Code(莫比乌斯反演 && 埃氏筛)
Code Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Total Submi ...
- 在使用开源library的PullToRefreshView中
下拉刷新几乎是每个应用都会有的功能,且大部分用的都是开源项目,下载地址:下拉刷新.如何在页面刚打开的时候自动触发下拉刷新的呢? 只需要一句代码,在PullToRefreshAdapterView Ba ...