SQL中的DISTINCT关键字用于从查询结果中去除重复的行,是数据库查询场景里非常常用的功能,不同数据库引擎对它的实现逻辑存在一定差异,但核心思路有共通之处。

DISTINCT去重的核心原理
大多数关系型数据库的DISTINCT去重逻辑可以分为两种常见实现方式,具体选择哪种和查询的场景、数据量、是否有索引等因素相关。
排序去重方式
这是最传统的实现方式,数据库会先对需要去重的字段进行排序,排序完成后遍历排序后的结果,相邻的两行如果去重字段的值完全相同,就只保留第一行,这样就实现了去重。这种方式的优势是实现简单,劣势是排序操作本身会消耗较多的CPU和内存资源,当数据量较大时性能下降会比较明显。
哈希去重方式
随着数据库引擎的迭代,很多现代数据库支持哈希去重逻辑。数据库会为需要去重的字段建立一个哈希表,遍历每一行数据时,计算去重字段的哈希值,判断该哈希值是否已经存在于哈希表中,如果不存在就保留当前行并将哈希值存入哈希表,如果存在就直接跳过当前行。这种方式的去重效率通常比排序去重更高,尤其是在去重字段基数较大的场景下。
DISTINCT查询的性能分析维度
分析DISTINCT去重查询的性能,需要从多个维度综合判断,以下是几个核心的参考维度。
数据量与去重比例
如果要查询的数据总量很小,比如只有几百行,那么DISTINCT带来的性能损耗几乎可以忽略。如果数据量达到百万级以上,就需要关注去重后的结果集大小:如果去重后结果集占比很高,说明重复数据少,去重操作的成本相对更低;如果去重后结果集占比很低,说明大量数据都是重复的,去重操作需要扫描和对比的数据量会更大,性能损耗也会更高。
索引使用情况
如果去重的字段上存在合适的索引,数据库可以直接利用索引的有序性或者哈希结构快速完成去重,避免全表扫描。比如对user表的city字段做DISTINCT查询,如果city字段有索引,数据库可以直接遍历索引树完成去重,不需要回表查询所有行数据,性能会有明显提升。如果去重字段没有索引,数据库大概率需要全表扫描所有数据行,性能会差很多。
数据库引擎特性
不同数据库引擎对DISTINCT的优化策略不同,比如MySQL的InnoDB引擎在处理DISTINCT时,如果查询只包含索引字段,会优先使用索引覆盖扫描,减少IO消耗;PostgreSQL则会根据统计信息自动选择排序去重或者哈希去重的执行计划。可以通过各数据库的执行计划命令查看DISTINCT查询的具体执行逻辑,判断性能瓶颈所在。
DISTINCT查询的优化建议
在实际使用中,可以通过以下方式优化DISTINCT去重查询的性能。
- 尽量避免对多字段同时使用DISTINCT,多字段去重的哈希表或者排序成本会成倍增加,如果业务允许,可以先对单个核心字段去重,再关联查询其他字段。
- 给常用的去重字段建立合适的索引,优先选择基数适中、查询频率高的字段建立索引,提升去重效率。
- 控制查询的数据范围,尽量通过WHERE条件过滤掉不必要的行,减少需要参与去重的数据总量。
代码示例
以下是MySQL中查看DISTINCT查询执行计划的示例,通过执行计划可以判断索引是否被使用,以及去重采用的是什么方式。
-- 对user表的city字段做去重查询 SELECT DISTINCT city FROM user WHERE age > 18; -- 查看上述查询的执行计划 EXPLAIN SELECT DISTINCT city FROM user WHERE age > 18;
如果执行计划中type字段为index,说明使用了索引扫描完成去重;如果Extra字段出现Using temporary,说明查询使用了临时表进行排序去重,这种场景下如果数据量较大可以考虑优化索引。
注意事项
需要注意DISTINCT是对查询返回的所有字段的组合进行去重,而不是单独对某一个字段去重。比如执行SELECT DISTINCT city, age FROM user,只有当city和age两个字段的值都相同时,才会被判定为重复行,这一点在使用时很容易出现误解,需要特别注意。
不要在不需要去重的场景下滥用DISTINCT,多余的去重操作会增加不必要的性能消耗,只有在确认结果存在重复且需要去除时才使用。