distinct是对整行去重而非单列去重,其作用范围是所有选定字段的组合;若需按某列逻辑去重并取对应行,应使用group by配合聚合函数或窗口函数。

SELECT DISTINCT col1, col2 是联合去重,不是分别去重
很多人写 SELECT DISTINCT code, cdate, ctotal FROM tt 时,以为“code 去重了,cdate 和 ctotal 就自动跟着选一条”,其实不是。MySQL 对整行判断:只有 code、cdate、ctotal 三者组合完全一致,才算重复。只要其中任意一列不同,就保留整行。
常见错误现象:
- 表里有两行
(A, '2026-01-01', 100)和(A, '2026-01-02', 200),SELECT DISTINCT code, cdate, ctotal会返回两行 —— 因为组合不同 - 误写成
SELECT cdate, ctotal, DISTINCT code,直接报错:SQL error [1064]: You have an error in your SQL syntax...
正确姿势只有一种:DISTINCT 必须放在所有字段最前面,且不能加括号(SELECT DISTINCT(name) 是无效语法,只是碰巧不报错,但语义和 SELECT DISTINCT name 完全一样)。
想按某列“逻辑去重”并带出其他字段?DISTINCT 不行,得换方案
比如你真正想要的是:“每个 code 只取一条,顺便带上它对应的 cdate 和 ctotal”。这已经超出 DISTINCT 的能力范围 —— 它不承诺留哪条,也不支持指定优先级。
这时有三个可行路径:
- 用
GROUP BY code+ 聚合函数,例如SELECT code, MAX(cdate), ANY_VALUE(ctotal) FROM tt GROUP BY code(ANY_VALUE()是 MySQL 5.7+ 提供的明确语义,替代模糊的非聚合列引用) - 关闭
only_full_group_by模式后写SELECT code, cdate, ctotal FROM tt GROUP BY code,但结果不可控(MySQL 不保证返回哪一行) - MySQL 8.0+ 推荐用窗口函数:
SELECT code, cdate, ctotal FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY code ORDER BY id DESC) rn FROM tt) t WHERE rn = 1,可精确控制留最新/最小/最大那条
DISTINCT 多列查询性能差?先看执行计划,再建索引
EXPLAIN SELECT DISTINCT col1, col2 FROM t 如果出现 Using temporary,说明 MySQL 正在内存或磁盘建临时表去重,数据量一大就慢。
优化关键点:
- 给
(col1, col2)建联合索引:CREATE INDEX idx_col1_col2 ON t(col1, col2),能显著减少临时表使用 - 如果查询还包含其他字段(如
SELECT DISTINCT col1, col2, col3),索引必须覆盖全部列,否则仍需回表 - 别在
TEXT或超长VARCHAR字段上用DISTINCT—— 索引失效,还可能触发磁盘临时表
COUNT(DISTINCT col) 和 SELECT DISTINCT col 的用途完全不同
这是最容易混淆的一对操作:
-
COUNT(DISTINCT login_date)返回一个数字:用户一共登录了多少天 -
SELECT DISTINCT login_date返回多行数据:列出所有登录过的日期(具体值) -
COUNT(DISTINCT ...)会忽略NULL;而SELECT DISTINCT col中所有NULL被视为相同,只返回一行NULL
如果你只需要数量,别写 SELECT DISTINCT 再用程序计数 —— COUNT(DISTINCT) 是数据库层直接算好返回的,更快更省网络开销。
真正难的从来不是语法,而是搞清自己到底要“取唯一值”还是“按某字段归并选一条”。前者用 DISTINCT,后者就得跳出这个关键字想方案。











