distinct真正去重发生在查询结果的整行数据层面,即仅当select列表中所有字段值完全相同时才合并;它不是按单列逻辑去重,无法保证取“最新”或“最高”记录,也不支持聚合计算,本质是结果集过滤而非分组控制。

MySQL DISTINCT 什么时候真正去重
DISTINCT 只对查询结果的**整行数据**做去重,不是按某一个字段单独去重。也就是说,只要两行记录在 SELECT 列表中所有字段的值完全相同,才会被合并成一条。
常见误解是以为写 SELECT DISTINCT name FROM users 就能“挑出不重复的 name”,其实它确实能做到;但一旦加了其他字段,比如 SELECT DISTINCT name, email FROM users,那只有 name 和 email 都相同的行才去重——哪怕 name 重复但 email 不同,也会全部保留。
- 想按单字段逻辑去重(如“每个 name 取最新一条”),
DISTINCT无能为力,得用GROUP BY+ 聚合函数或窗口函数 -
DISTINCT在多字段组合时容易误判“为什么没去重”,本质是字段组合唯一性比预期高 - 如果字段含
NULL,MySQL 把所有NULL视为相等,所以多行name = 'a', email = NULL会被合并
DISTINCT 和 GROUP BY 的关键区别
DISTINCT 是简化语法糖,底层常被优化成 GROUP BY;但它不支持聚合计算,也不能指定“保留哪条”。而 GROUP BY 明确分组并允许搭配 MAX()、MIN()、ANY_VALUE() 等控制取值逻辑。
例如:要取每个 category 下 price 最高的商品名,不能靠 DISTINCT:
SELECT DISTINCT category, MAX(price) FROM products GROUP BY category;
这个写法看似用了 DISTINCT,实际起作用的是 GROUP BY —— DISTINCT 在这里多余且误导。正确写法是直接 GROUP BY,或者用窗口函数:
- 用
GROUP BY:必须把非聚合字段全写进GROUP BY,否则 MySQL 8.0+ 会报错ERROR 1055 - 用
ROW_NUMBER():更灵活,可精确控制“取最新/最贵/首条” -
DISTINCT无法表达“每个分组只取一行,且该行满足某条件”这类需求
DISTINCT 性能开销在哪
DISTINCT 本质是排序 + 去重,或哈希去重,取决于执行计划。字段越多、数据量越大,内存和 CPU 消耗越明显。
- 对大表执行
SELECT DISTINCT *极其危险,可能触发临时表写磁盘,甚至 OOM - 有索引的字段参与
DISTINCT时,MySQL 可能利用索引避免排序,但仅限于索引前缀匹配整个SELECT列表顺序 - 加
LIMIT不一定能缓解压力——去重必须做完才能截断,不像普通查询可提前终止 - 替代思路:先用
WHERE缩小结果集,再DISTINCT;或用EXISTS/JOIN改写逻辑
容易忽略的 NULL 和类型隐式转换问题
DISTINCT 对 NULL 的处理很统一(全视为相同),但和其他操作混用时容易出意外。比如字段是 VARCHAR,但值里混着空字符串 '' 和 NULL,它们在 DISTINCT 中不等价,会算作两条。
更隐蔽的是类型转换:如果 SELECT DISTINCT a, b 中 a 是 INT,b 是 VARCHAR,而 b 存了数字字符串如 '123',MySQL 不会自动转成整数比较——123 和 '123' 是不同行。
- 显式转换可用
CAST(b AS SIGNED)统一类型,但要注意失败时转成 0 或警告 - 字符串比较受字符集和 collation 影响,比如
'A'和'a'在utf8mb4_0900_as_cs下不等,在utf8mb4_general_ci下可能等 - 用
IS NULL单独过滤,别依赖DISTINCT合并NULL行来“清理”数据
DISTINCT 关键字那么简单,尤其当你要的不是“所有字段组合唯一”,而是“按某个维度取代表行”时,它根本不是解法。











