平时写SQL,DISTINCT绝对是我们最顺手的去重工具。不管是查下拉选项、做统计去重、还是多表关联怕重复,随手敲个 DISTINCT 几乎成了肌肉记忆。
但只要数据量稍微一大,你一定遇过这种崩溃场景:一条加了 DISTINCT 的查询,平时秒出,一到高峰期直接卡成PPT。全表扫描、排序、临时文件疯狂占用资源,CPU 和 I/O 直接拉满,业务接口超时、页面卡死全来了。
我见过太多开发和 DBA,为了优化 DISTINCT 熬到半夜:加索引、改 SQL、拆逻辑,折腾半天效果还一般。
今天就基于KingbaseES,把数据库内核级的 DISTINCT 自动优化讲明白。不用你改业务代码,不用手动调 SQL,数据库自己就能把“笨去重”变成“极速查询”。
先聊一下:DISTINCT 为什么这么慢?
我们先别搞复杂原理,用大白话说清楚:数据库执行 DISTINCT,真的很“死脑筋”。
比如你写:
selectdistincta,bfromuser_table;数据库拿到这条 SQL,它的内心活动是这样的:
- 先把整张表的数据全部读出来,一条都不能漏;
- 把 a 和 b 两个字段拼在一起,全部排序(数据越多排得越慢);
- 排完再挨着对比,把重复的扔掉,最后给你返回结果。
这一套流程,小表无所谓,百万、千万级别的表直接扛不住:
- 排序特别吃内存,内存不够就写到磁盘,磁盘 I/O 直接爆炸;
- 就算你表里满足条件的只有一条数据,它也得把全表扫完再去重;
- 多表关联再加 DISTINCT,慢得更是没眼看。
最气人的是什么?很多时候,这个去重根本没必要做!
比如你写:
selectdistincta,bfromuser_tablewherea=1andb=1;WHERE 条件都把 a 和 b 固定死了,结果肯定只有一条,还去什么重?但传统数据库就是反应不过来,照样老老实实全表扫描+排序,纯纯浪费性能。
这就是我们日常最头疼的问题:明明结果唯一,数据库非要做全套去重。
这套流程啊,小表还好说,但是一旦到了百万、千万级别的大表,那就完全招架不住了:
- 排序特别耗内存,一旦内存不够用,就只能往磁盘上写,这样一来磁盘 I/O 就直接爆了;
- 即使你的表里符合条件的只有一条记录,它也得把整个表扫一遍再去重;
- 要是再多个表关联起来还加上 DISTINCT,那速度慢得简直让人看不下去。
最让人火大的是啥?很多时候这个去重根本就是多余的!
比如说你这样写查询:
selectdistincta,bfromuser_tablewherea=1andb=1;WHERE 条件已经把 a 和 b 都固定死了,结果肯定只有一条,还去什么重啊?但是传统的数据库就是转不过弯来,还是老老实实地全表扫描加排序,纯粹是在浪费资源。
这就是我们日常工作中最头疼的问题之一:明明结果唯一,数据库却非得做全套的去重操作。
咱们平时搞的那些优化啊,说白了就是治标不治本。遇到 DISTINCT 慢的时候,我们通常就这几招:
- 给要去重的那个字段加个索引:确实能快点儿,但一碰到复杂的查询或者多表关联,基本上就凉凉了。
- 手动把 DISTINCT 改成 GROUP BY:改起来挺麻烦的,还容易出错,结果不对劲儿。
- 把数据拉到代码里去重:这下更慢了,高并发直接就挂了。
这些方法都有个共同的问题:只是让“去重”稍微快一点,而不是让数据库根本不用去重。
根本原因就在于:传统的数据库优化器只会算成本,不会动脑子。它能算出来哪种方式去重更划算,但它没法推导出这条查询的结果本来就没什么重复的。
内核级优化来了:数据库自己“开窍”,自动改SQL
现在的企业级数据库,已经在内核里做了智能自动优化。简单说:数据库自己识别哪些 DISTINCT 是多余的,偷偷帮你把 SQL 改成最高效的写法,你完全无感知。
核心就两招,简简单单给你讲透:
第一招:DISTINCT 自动换成 GROUP BY,能并行、能裁剪
不是让你手动改,是数据库内核自己转。
比如:
selectdistincta,bfroms1;优化器直接等价改成:
selecta,bfroms1groupbya,b;别看只是换了个关键字,内核执行完全不一样:
- 如果有主键,GROUP BY 可以直接按主键裁剪,不用全量排序;
- GROUP BY 支持并行执行,多线程一起跑,速度直接翻倍;
- 避开了 DISTINCT 自带的隐式排序,内存占用小很多。
实测很直观:原来 464ms,优化完 249ms,快了将近一倍。
第二招:结果唯一时,直接用 LIMIT 1,找到一条就返回
这是最爽的优化,专门解决“条件固定、结果唯一”的场景。
当 WHERE 条件、多表关联条件,把查询的字段都锁成固定值时,优化器立马判断:最多只有一条结果,去重完全多余。
直接给你改成 LIMIT 1:
-- 优化前:全表扫+分组去重selecta,bfroms1wherea=1andb=1groupbya,b;-- 优化后:找到一条直接返回selecta,bfroms1wherea=1andb=1limit1;性能提升有多夸张?
原来 30ms,优化完只有 0.03ms,快了 1000 倍都不止!
多表关联也一样好用:
selects1.a,s2.bfroms1innerjoins2ons1.a=s2.bands1.a=5groupbys1.a,s2.b;优化器通过关联条件,能推导出 s2.b 也等于 5,两个表的字段都固定了,直接改成 LIMIT 1,从 12ms 降到 0.08ms。
说白了:不用扫全表,不用去重,找到一条立马返回,剩下的工作量全免。
底层原理:数据库怎么知道结果唯一?
你可能会好奇:数据库又不是人,它怎么知道“结果只有一条”?
核心靠两个技术,我用最简单的话解释:
- 常量传递
WHERE 里写了 a=1,数据库就把这个“1”直接传到查询的字段里,认定查询结果里的 a 永远是 1,不可能有别的值。 - 谓词传递
多表关联时,比如 s1.a = s2.b,又有 s1.a=5,数据库自动推导出 s2.b=5,跨表也能锁定常量。
数据库会先把你的 SQL 拆成逻辑树,把条件、关联、查询字段全都梳理清楚,然后一步步推导:
- 这些字段是不是都固定了?
- 结果是不是最多一条?
- 去重是不是多余的?
确认没问题,才会帮你改写 SQL,绝对保证和原来的结果一模一样,不会出错。
传统数据库 VS 智能优化数据库:差距真的很大
我给你用最直白的方式对比一下,你就明白了:
- 传统数据库
看到 DISTINCT → 只能排序/哈希去重 → 不管结果是否唯一,都要全量执行 - 智能优化数据库
看到 DISTINCT → 先分析条件能不能推导出结果唯一 → 能唯一就直接 LIMIT 1 → 不能唯一就转 GROUP BY 并行加速
简单总结:
- 传统优化器:只会老老实实干活,不懂偷懒
- 智能优化器:先想能不能少干活,再用最高效的方式干
我也拿实际场景对比过其他数据库,同样的 SQL:
selectdistincta,bfromdistinct_1wherea=1andb=1;有些数据库还是老老实实走 DISTINCT 去重,全表扫描一样不少。
而支持内核智能优化的数据库,直接变成 LIMIT 1,执行计划里连去重的步骤都没了。
这些场景下,优化效果特别明显:
- 比如单表查询时,WHERE 条件是固定的,还用了 DISTINCT 或 GROUP BY;
- 再比如多表内连接,关联字段被常量固定了,结果自然就唯一;
- 还有下拉框和枚举值的查询,条件固定,返回的结果集很小;
- 以及高并发的短查询,这样可以避免全表扫描,接口响应速度直接飞起来。
最棒的是用起来超级简单,根本不用改业务代码,只需要在数据库里开个开关就行了:
-- 开启 DISTINCT 转 GROUP BYsetdistinct_convert=on;开了这个开关后,所有 SQL 都会自动优化,DBA 和开发人员都能轻松很多。