干这行的人,多多少少都有点"DISTINCT 强迫症"。写查询的时候,只要心里对结果会不会重复没底,手就先把 DISTINCT 敲上去了。它确实是最常见的去重办法,简单、稳妥,看着也没什么副作用。
但问题就出在这个"看着没什么副作用"上。
实际业务系统里,有大量场景其实是带着明确过滤条件的——可只要语句里挂了 DISTINCT,数据库就还是老老实实地去全表扫描、排序,或者哈希去重。尤其是那种目标列已经能被筛选条件固定下来、或者表上本来就有主键的情况,这一套去重流程跑下来纯属浪费。我见过不少线上慢 SQL,扒到最后发现根子就在这儿。数据量小的时候你根本察觉不到,等业务量起来了,这种冗余的全表扫描和排序就成了实打实的性能瓶颈,接口一卡,排查半天还以为是别的地方出了问题。
电科金仓 KingbaseES(KES)在内核里专门收拾了这个问题。思路其实不复杂,归纳起来就两条路:一是用 GROUP BY 去替代 DISTINCT 来完成去重,借 GROUP BY 自带的键值消除和并行能力把活儿干得更轻;二是在目标列已经被固定成常量的时候,干脆用 LIMIT 1 把 DISTINCT 和 GROUP BY 都顶替掉。
难就难在"敢不敢改"和"该不该改"
听起来好像很顺,但这事真要落地,有两道坎绕不过去。
第一道是语义安全。这种优化本质上是在动 SQL 本身,很多 SQL 结构稍微改一下,结果语义就变了。所以改写之前必须有一套严格的约束兜底,确认前后完全等价才敢动手——不然为了快那么几毫秒把结果改错了,那是要出大事的。
第二道是判断目标列到底有没有被固定住。这个常量值,可能是 WHERE 条件直接给定的,可能是从 INNER JOIN 的等值条件里间接推出来的,也可能是好几个谓词之间互相传递攒出来的,甚至目标列原本就是个常量。要在这么多种情况下都准确认出"哦,这个结果其实唯一,可以用 LIMIT 1",对优化器的推理能力要求是很高的,普通优化器根本覆盖不全。
两层优化,一层比一层狠
先看最基础的传统做法。比如 select distinct a, b from s1;,数据库会先把 s1 整张表扫一遍,结果排个序,再去重。数据量一大,这开销就很可观,而且它压根没主动用上数据库里那些现成的优化能力,属于"会干活但不会偷懒"。
KES 把优化拆成了两层。
第一层,把 DISTINCT 改写成 GROUP BY。 这个能力通过 GUC 参数控制开关,符合条件的语句会自动转。好处在于,原本要对 a、b 整组字段去重,转成 GROUP BY 之后可以变成对主键 a 的键值去重,实现键值裁剪;对带主键的表尤其管用,能砍掉一堆冗余操作。转成 GROUP BY 还有个隐藏福利——能接上数据库已有的并行能力,把活儿分到多个线程去跑。
实测最能说明问题。还是 select distinct a, b from s1 这条,没开优化前跑了 464ms,开启自动改写、转成 GROUP BY 并用上并行之后,直接降到 249ms,差不多快了一倍。
第二层,用 LIMIT 1 直接顶替。 这层是真正拉开差距的地方。当目标列能被常量固定时,结果集顶天了就一条数据,那这趟去重根本没必要完整跑完。
举个例子:select distinct a, b from s1 where a=1 and b=1;。优化器一看,a、b 的值都被 WHERE 钉死了,结果必然唯一,于是改写成 select a, b from s1 where a=1 and b=1 limit 1;。整个查询从"全表扫完再排序去重"变成了"找到一条满足的就立刻返回",把全表里扫描大量重复值那段冗余彻底省掉了。这一改,查询时间从 30ms 掉到 0.03ms。
复杂一点的多表关联也吃得下。比如:
select s1.a, s2.b from s1 inner join s2 on s1.a=s2.b and s1.a=5 group by s1.a, s2.b;
优化前的执行路径是:先对 s1、s2 按条件筛出所有等于 5 的数据,连接,再对连接结果分组去重,总共 12ms。优化后呢,连接完之后只需要输出一条就够了,分组去重那步直接免了,总耗时 0.08ms。
说白了,LIMIT 1 这层干的事就一句话:把"全表扫完才拿结果"改成"扫到符合条件的就当场输出",能少扫的表绝不多扫。
跟友商比一比
光自己说好不算数,拿同样的语句和达梦 DM 比了一下。
测试用例是这条带常量约束的去重语句:explain select distinct a, b from distinct_1 where a=1 and b=1;。结果 DMv8 的执行计划里,DISTINCT 算子还稳稳地待在那儿——它没认出来这个查询的结果其实已经唯一了,照样按标准 DISTINCT 流程走,不支持往 LIMIT 1 上做这种逻辑改写。
这一对比,差异就出来了:很多国产库的优化器还停留在"算代价、挑方案"这个阶段,能识别的是执行开销,识别不了常量固定、谓词传递这类藏在语义里的隐性约束。而 KES 已经能在内核层面做这种深层的逻辑推导和自动改写了。
最后说两句
平时碰到的 DISTINCT 慢查询,老实讲,大多不是这个关键字本身有毛病,而是优化器不会推理、只会按既定流程死磕去重。KES 这套东西的价值就在于,它把这种"该偷的懒"在内核层替你偷了——你写 SQL 不用刻意去抠要不要加 DISTINCT,数据库自己会判断、自己会改写,业务代码一行都不用动。
对我们做开发和运维的来说,这意味着排查慢 SQL 时少了一类常见坑,日常调优的工作量也能省下不少。眼下信创替换在加速、高并发业务越来越多,有这么一层内核自带的智能优化兜着,拿来扛企业级的核心业务,是够用且让人踏实的。