DISTINCT 带 WHERE 仍全表扫描?两层优化刀法拆解
DISTINCT 带 WHERE 仍全表扫描两层优化刀法拆解引言一个看似多余的 DISTINCT藏着性能陷阱几乎每个写过 SQL 的人都用过DISTINCT。它的语义很简单——去掉重复行。但简单不等于快。在一个客户的生产环境中运维团队发现这样一条 SQLSELECTDISTINCTstatus,categoryFROMt_ordersWHEREstatusACTIVEANDcategoryELECTRONICS;过滤条件已经把结果锁定到了唯一的值组合(ACTIVE, ELECTRONICS)。但执行计划显示全表扫描、排序或哈希去重一个都没少。这条查询每次执行 30ms在高并发场景下成了明显的性能瓶颈。为什么因为传统数据库的优化器看到DISTINCT就会机械地走扫描 去重的固定流程即使 WHERE 条件已经确定了目标列的值。金仓数据库在 V9R4C19 版本中对 DISTINCT 语句进行了两层深度优化把这种机械流程变成了聪明决策。本文将带你理解这两层优化的原理和效果。原理剖析两层刀法层层递进第一层刀法DISTINCT 改写为 GROUP BYSELECT DISTINCT a, b FROM t在语义上等价于SELECT a, b FROM t GROUP BY a, b。这看起来只是换了一种写法但实际意义在于GROUP BY 有更成熟的优化路径。金仓数据库将 DISTINCT 改写为 GROUP BY 后可以复用 GROUP BY 已有的优化能力键值消除如果目标列上存在唯一索引或主键优化器可以直接利用索引信息进行键值裁剪无需扫描全部数据并行执行GROUP BY 天然支持并行计算改写后可以享受并行去重的性能红利优化器规则复用GROUP BY 的优化规则在数据库中积累多年比 DISTINCT 单独优化的成熟度更高-- 原始 SQLSELECTDISTINCTa,bFROMs1;-- 优化器内部改写对用户透明SELECTa,bFROMs1GROUPBYa,b;第二层刀法LIMIT 1 替代 DISTINCT / GROUP BY这是更激进也更高效的一层优化。当目标列被常值条件完全固定时DISTINCT 的去重操作本身就是多余的——结果要么有值一行要么没值零行。考虑以下场景SELECTDISTINCTa,bFROMs1WHEREa1ANDb1;WHERE 条件已经把a和b锁死为常量(1, 1)。即使扫描到 100 条匹配的记录DISTINCT 之后的结果也只有一行(1, 1)。所以-- 等价改写SELECTa,bFROMs1WHEREa1ANDb1LIMIT1;这个改写的威力在于一旦找到第一条匹配的记录就可以立刻停止扫描。如果数据分布均匀这几乎把扫描量从全表降到了找到第一个匹配项。改写策略适用条件核心收益DISTINCT → GROUP BY通用复用 GROUP BY 的键值消除和并行能力DISTINCT → LIMIT 1目标列被常值 WHERE 条件完全固定找到第一条即可停止极致加速代码示例场景一DISTINCT 转 GROUP BY-- 创建测试表CREATETABLEs1(idINTPRIMARYKEY,aINT,bVARCHAR(20),cDATE);-- 场景查询某时间范围内不重复的 (a, b) 组合SELECTDISTINCTa,bFROMs1WHEREc2026-01-01ANDc2026-04-01;优化器内部将上述 SQL 改写为SELECTa,bFROMs1WHEREc2026-01-01ANDc2026-04-01GROUPBYa,b;改写后优化器可以利用 GROUP BY 已有的键值消除规则如果a或b上有索引直接走索引扫描避免全表扫描和哈希去重。实测效果464ms → 249ms耗时减少近一半。场景二DISTINCT 转 LIMIT 1-- 场景查询特定用户的状态结果唯一SELECTDISTINCTuser_status,vip_levelFROMt_userWHEREuser_idU10086ANDuser_statusACTIVE;由于user_status在 WHERE 中已被固定为ACTIVEvip_level虽然未被固定但user_id是主键整个结果集最多只有一行。优化器将其改写为SELECTuser_status,vip_levelFROMt_userWHEREuser_idU10086ANDuser_statusACTIVELIMIT1;实测效果30ms → 0.03ms提速 1000 倍。场景三复杂场景组合优化-- 复杂场景多条件 子查询SELECTDISTINCTt1.statusFROMt_order t1WHEREt1.order_idIN(SELECTorder_idFROMt_paymentWHEREpay_statusPAID)ANDt1.statusSHIPPED;这里t1.status被 WHERE 条件固定为SHIPPEDDISTINCT 的去重操作等价于 LIMIT 1-- 优化器改写后SELECTt1.statusFROMt_order t1WHEREt1.order_idIN(SELECTorder_idFROMt_paymentWHEREpay_statusPAID)ANDt1.statusSHIPPEDLIMIT1;实测效果12ms → 0.08ms提速 150 倍。如何验证优化是否生效使用EXPLAIN查看执行计划对比优化前后的差异-- 查看原始执行计划EXPLAIN(ANALYZE,BUFFERS)SELECTDISTINCTa,bFROMs1WHEREa1ANDb1;-- 优化后应看到 LIMIT 节点且扫描行数显著减少如果执行计划中出现了Limit节点并且在Actual Rows中只返回了一行说明优化已生效。最佳实践写 SQL 时的心态转变旧思维新思维DISTINCT 就是去重写了就好DISTINCT 可能隐藏性能问题考虑是否有更高效的写法有 WHERE 过滤DISTINCT 会快WHERE 固定了列值时DISTINCT 本质是多余的依赖数据库自动优化了解优化边界复杂场景手动改写更可靠适用场景速查你的场景建议写法原因结果确定唯一如主键查询直接去掉 DISTINCT或加 LIMIT 1去重操作多余WHERE 条件固定了所有 SELECT 列加 LIMIT 1 替代 DISTINCT找到第一个就够需要去重但不确定结果唯一性保持 DISTINCT 或改为 GROUP BYGROUP BY 有更好并行能力大表 索引列的去重查询改为 GROUP BY 利用索引避免哈希去重的内存开销需要注意的限制LIMIT 1 替代策略只在以下条件下生效目标列被 WHERE 条件中的常值完全固定——比如WHERE a 1 AND b 2不涉及聚合函数或窗口函数——这些会改变结果的基数不包含 ORDER BY 与 LIMIT 语义冲突的子句实测效果汇总优化策略原始耗时优化后耗时性能提升DISTINCT → GROUP BY464ms249ms1.86xDISTINCT → LIMIT 130ms0.03ms1000x复杂场景组合12ms0.08ms150x与同类产品的对比特性KingbaseESDM v8DISTINCT 转 GROUP BY支持支持DISTINCT 转 LIMIT 1支持不支持总结金仓数据库 V9R4C19 对 DISTINCT 语句的两层优化本质上是把机械的去重操作变成了智能的结果判断第一层DISTINCT → GROUP BY利用 GROUP BY 已有的键值消除和并行能力通用场景下减少近一半的耗时第二层DISTINCT → LIMIT 1当 WHERE 条件已确定结果唯一时用 LIMIT 1 替代整个去重流程极端场景下提速 1000 倍对于开发者和 DBA 来说这意味着两件事第一写 DISTINCT 时不必再担心明明有 WHERE 为什么还要全表去重第二了解这些优化的边界在关键查询中主动采用更高效的写法。毕竟最好的优化是你写什么数据库都能理解你的意图。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2602809.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!