写 DISTINCT 时觉得理所当然,执行计划却告诉你------它在笨拙地扫描整张表,哪怕 WHERE 已经把答案锁死了。

引子:一个"本该瞬间完成"的慢查询
生产环境告警,运维团队定位到这条 SQL:
sql
SELECT DISTINCT status, category
FROM t_orders
WHERE status = 'ACTIVE' AND category = 'ELECTRONICS';
WHERE 条件已经把结果框死在了 (ACTIVE, ELECTRONICS) 这一个组合上。但执行计划毫不留情:全表扫描、排序或哈希去重,流程一个不落。30ms 的耗时,在高并发下成了明显的瓶颈。
为什么?因为传统优化器看到 DISTINCT 就条件反射------走"扫描 → 去重"的标准套路,完全不管 WHERE 条件已经把目标列的值钉死了。
金仓数据库 V9R4C19 对 DISTINCT 做了两层深度优化,把这套"机械套路"升级成了"智能判断"。本文拆解这两层刀法怎么出招、效果如何。
一、核心原理:两层刀法,由浅入深
第一刀:DISTINCT 换皮 GROUP BY
SELECT DISTINCT a, b FROM t 语义上等价于 SELECT a, b FROM t GROUP BY a, b。表面是换种写法,实质是借道更成熟的优化体系。
金仓把 DISTINCT 改写为 GROUP BY 后,直接继承 GROUP BY 的优化家底:
- 键值消除:目标列上有唯一索引或主键?优化器直接拿索引信息做裁剪,不用碰全表
- 并行加速:GROUP BY 天然支持并行计算,改写后能蹭上并行去重的红利
- 规则复用:GROUP BY 的优化规则在数据库里打磨多年,比 DISTINCT 单打独斗成熟得多
sql
-- 原始 SQL
SELECT DISTINCT a, b FROM s1;
-- 优化器内部自动改写
SELECT a, b FROM s1 GROUP BY a, b;

第二刀:LIMIT 1 直接掀桌
这是更激进的一刀。当 WHERE 条件用常量把目标列彻底焊死时,DISTINCT 的去重本身就是多余的------结果要么有一行,要么空集。
看场景:
sql
SELECT DISTINCT a, b FROM s1 WHERE a = 1 AND b = 1;
WHERE 已经把 a 和 b 锁成 (1, 1)。哪怕扫到 100 条匹配记录,DISTINCT 完也只有一行 (1, 1)。所以:
sql
-- 等价改写
SELECT a, b FROM s1 WHERE a = 1 AND b = 1 LIMIT 1;
威力在于:找到第一条匹配记录就能收工。数据分布均匀的话,扫描量从"全表"暴跌到"碰第一个匹配项"。
| 改写策略 | 适用场景 | 核心收益 |
|---|---|---|
| DISTINCT → GROUP BY | 通用场景 | 蹭 GROUP BY 的键值消除和并行能力 |
| DISTINCT → LIMIT 1 | 目标列被 WHERE 常量完全固定 | 找到第一条即停,极致加速 |

二、实战验证:从毫秒到微秒
场景一:DISTINCT 转 GROUP BY
sql
-- 测试表
CREATE TABLE s1 (
id INT PRIMARY KEY,
a INT,
b VARCHAR(20),
c DATE
);
-- 查某时间段内不重复的 (a, b) 组合
SELECT DISTINCT a, b
FROM s1
WHERE c >= '2026-01-01' AND c < '2026-04-01';
优化器内部改写为:
sql
SELECT a, b
FROM s1
WHERE c >= '2026-01-01' AND c < '2026-04-01'
GROUP BY a, b;
改写后,若 a 或 b 上有索引,直接走索引扫描,跳过全表扫描和哈希去重。
实测:464ms → 249ms,耗时砍半。
场景二:DISTINCT 转 LIMIT 1
sql
-- 查特定用户状态(结果唯一)
SELECT DISTINCT user_status, vip_level
FROM t_user
WHERE user_id = 'U10086' AND user_status = 'ACTIVE';
user_status 被 WHERE 钉死为 'ACTIVE',user_id 是主键,结果集最多一行。优化器直接改写为:
sql
SELECT user_status, vip_level
FROM t_user
WHERE user_id = 'U10086' AND user_status = 'ACTIVE'
LIMIT 1;
实测 :30ms → 0.03ms,提速 1000 倍 。 
场景三:复杂场景组合拳
sql
-- 多条件 + 子查询
SELECT DISTINCT t1.status
FROM t_order t1
WHERE t1.order_id IN (
SELECT order_id FROM t_payment WHERE pay_status = 'PAID'
)
AND t1.status = 'SHIPPED';
t1.status 被 WHERE 固定为 'SHIPPED',DISTINCT 等价于 LIMIT 1:
sql
-- 改写后
SELECT t1.status
FROM t_order t1
WHERE t1.order_id IN (
SELECT order_id FROM t_payment WHERE pay_status = 'PAID'
)
AND t1.status = 'SHIPPED'
LIMIT 1;
实测:12ms → 0.08ms,提速 150 倍。
三、验证优化是否生效
用 EXPLAIN 看执行计划,对比前后差异:
sql
EXPLAIN (ANALYZE, BUFFERS)
SELECT DISTINCT a, b FROM s1 WHERE a = 1 AND b = 1;
优化生效的标志:执行计划中出现 Limit 节点,且 Actual Rows 只有一行。

四、最佳实践:写 SQL 时换个脑子
| 旧思维 | 新思维 |
|---|---|
| DISTINCT 就是去重,写了就完事 | DISTINCT 可能藏性能雷,想想有没有更高效的招 |
| 有 WHERE 过滤,DISTINCT 应该快 | WHERE 把列值钉死时,DISTINCT 纯属多余 |
| 全靠数据库自动优化 | 摸清优化边界,复杂场景手动改写更稳 |
场景速查表
| 你的场景 | 推荐写法 | 原因 |
|---|---|---|
| 结果确定唯一(如主键查询) | 去掉 DISTINCT,或加 LIMIT 1 | 去重操作多余 |
| WHERE 常量固定了所有 SELECT 列 | LIMIT 1 替代 DISTINCT | 找到第一个就够 |
| 需要去重但不确定结果唯一性 | 保持 DISTINCT 或改 GROUP BY | GROUP BY 并行能力更强 |
| 大表 + 索引列去重 | GROUP BY + 索引 | 避开哈希去重的内存开销 |
LIMIT 1 替代的红线
以下情况不适用:
- 目标列没被 WHERE 常量完全固定 ------比如
WHERE a > 1这种范围条件 - 涉及聚合或窗口函数------这些会改变结果基数
- 包含与 LIMIT 语义冲突的 ORDER BY------排序后再 LIMIT 会扭曲结果
五、实测数据与竞品对标
| 优化策略 | 原始耗时 | 优化后耗时 | 提升倍数 |
|---|---|---|---|
| DISTINCT → GROUP BY | 464ms | 249ms | 1.86× |
| DISTINCT → LIMIT 1 | 30ms | 0.03ms | 1000× |
| 复杂场景组合 | 12ms | 0.08ms | 150× |
| 特性 | KingbaseES | DM v8 |
|---|---|---|
| DISTINCT 转 GROUP BY | 支持 | 支持 |
| DISTINCT 转 LIMIT 1 | 支持 | 不支持 |

结语
金仓数据库 V9R4C19 对 DISTINCT 的两层优化,本质是把"无脑去重"升级为"结果预判":
- 第一刀(DISTINCT → GROUP BY):借道 GROUP BY 的成熟优化体系,通用场景耗时减半
- 第二刀(DISTINCT → LIMIT 1):WHERE 把结果焊死时,用 LIMIT 1 直接掀掉去重流程,极端场景提速 1000 倍
对开发者和 DBA 的意义:第一,写 DISTINCT 不用再担心"明明 WHERE 已经锁死了为什么还要全表去重";第二,摸清这些优化的边界,在关键查询里主动选择更高效的写法。
毕竟,最理想的优化是------你写什么,数据库都能读懂你的真实意图。