关于MySQL count(distinct) 逻辑的另一个bug_
?
http://dinglin.iteye.com/blog/1982176
?
背景
???????? 上一篇博文(鏈接)介紹了count distinct的一個(gè)bug。解決完以后發(fā)現(xiàn)客戶的SQL語句仍然返回錯(cuò)誤結(jié)果(0), 再查原因,發(fā)現(xiàn)了另外一個(gè)bug。也就是說,這個(gè)SQL語句觸發(fā)了兩個(gè)bug -_-
?
這里只說第二個(gè),將問題簡(jiǎn)化后復(fù)現(xiàn)如下,影響已知的所有版本 。
?
| 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值設(shè)為各不相同。
?
與上個(gè)bug類似,5.5+的版本直接復(fù)現(xiàn);5.1版本需要修改的是max_heap_table_size參數(shù),而由于max_heap_table_size的最小值限制不能設(shè)置為1024,需要的測(cè)試數(shù)據(jù)量大些,但原理類似。
?
原因分析
???????? Count(distinct case when xxx then f end)的語義就是計(jì)算字段f的去重總數(shù),計(jì)算流程細(xì)節(jié)參看前一篇。這里直接給出tmp_table_size不夠大時(shí)的流程,便于說明此問題。
??
???????? ? 流程:
1、? 構(gòu)造一個(gè)unique 集合A1, 將滿足條件的結(jié)果插入A1中(計(jì)算了case when之后的值)
2、? 插入item過程中若大小超過tmp_table_size,則將A1暫時(shí)寫到文件中,再構(gòu)造集合A2
3、? 重復(fù)步驟2直到所有的item插入完成
因此若item很多則可能重復(fù)生成多個(gè)集合A1~An。
4、? 對(duì)A1~An作合并操作。由于只是每個(gè)集合A保證unique,因此需要做類似歸并排序的操作(實(shí)際上不需要排序,只是掃一遍)
5、? 合并加和操作本來只需要去重和去掉NULL值即可,但為了復(fù)用代碼,對(duì)于每個(gè)item,重新計(jì)算了一次結(jié)果的合法性,也就是,再判斷一次case when是否正確。
6、? 不幸的是,計(jì)算結(jié)果合法性的這些case when,其實(shí)是共同的一個(gè):最后一行。
?
因此最后的結(jié)果是正確值還是0,就取決于最后一行的case when的結(jié)果。
案例分析
以上面這個(gè)case為例。由于使用主鍵,最后的一行必然是id=64的那一行。這樣在合并的時(shí)候,若條件是id<=64 這些值都被認(rèn)為符合條件可以合并。而最后一個(gè)語句的情況,最后一行id<=64不成立
?
作為驗(yàn)證可以看一下這個(gè)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,正確 |
?? 可以看到,其實(shí)tb2和tb1的數(shù)據(jù)內(nèi)容是一樣的,只是tb2沒有索引且數(shù)據(jù)倒置插入,因此查詢的最后一行的id是1,滿足id<=63, 結(jié)果記入就正確了。
?
解決方法
???????? 調(diào)高tmp_table_size也是一種直接的方法,但是不治本,因?yàn)橹灰獫M足條件的行數(shù)足夠多,就會(huì)出現(xiàn)這個(gè)問題。
?
???????? 當(dāng)然本質(zhì)上這是一個(gè)bug。
???????? 代碼上,對(duì)于已經(jīng)走到合并操作的這個(gè)邏輯,其實(shí)前面在構(gòu)造各個(gè)集合A1~An的時(shí)候,已經(jīng)驗(yàn)證過條件合法,其實(shí)在合并的時(shí)候,可以直接做去重操作即可。
轉(zhuǎn)載于:https://www.cnblogs.com/kaka100/p/3447173.html
總結(jié)
以上是生活随笔為你收集整理的关于MySQL count(distinct) 逻辑的另一个bug_的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hibernate配置文件,省的到处找了
- 下一篇: MySQL中INSERT IGNORE