一、开篇:一道高频面试题背后的问题
在 MySQL 相关的面试中,有一道题经常被面试官问到:distinct 和 group by 都能去重,它们哪个效率更高?很多候选人听到这个问题后会下意识地回答「distinct 更快,因为它的语义更简单」,也有一些人会回答「group by 更快,因为它功能更强」。实际上,这道题并没有一个脱离场景的唯一答案。
要回答好这道题,不能只停留在「谁比谁快」的结论上,而应该从 SQL 语义、执行计划、索引使用、临时表、排序和优化器行为几个维度展开。本文将以 MySQL 8.0 为主要版本,结合 InnoDB 存储引擎,从原理到实验,系统地把 distinct 与 group by 的去重机制讲清楚,并给出不同场景下的选择建议。
整篇文章将覆盖以下几个方面:
- distinct 与 group by 的基础语法和语义差异
- 两种写法在优化器中的执行计划差异
- 单列去重与多列去重的内部处理方式
- count(distinct) 的特殊性和性能问题
- 索引对去重效率的影响
- 临时表、文件排序和内存限制的作用
- 性能实测数据对比
- group by 相比 distinct 的额外能力
- 不同业务场景下的选择策略和优化建议
- 围绕这道题常见的面试追问
在正式开始之前需要先说明一点:本文讨论的是「用 group by 实现去重」与「直接使用 distinct 去重」之间的效率对比,默认查询最终返回的都是去重后的结果集。理解了这一点,后面的分析和实验才有统一的比较基准。
二、基础回顾:distinct 与 group by 的用法
2.1 distinct 的语法与语义
distinct 是 SELECT 语句中的一个关键字,用来去除查询结果中的重复行。它的位置通常紧跟在 SELECT 之后,作用于 SELECT 列表中的全部列。也就是说,distinct 去重的对象不是某一列,而是整行记录的组合。
例如有一张用户表 user,里面记录了用户的城市和职业:
CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, city VARCHAR(50) NOT NULL, job VARCHAR(50) NOT NULL, KEY idx_city (city) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;如果只想查询所有出现过的城市,可以使用:
SELECT DISTINCT city FROM user;如果查询的是 city 和 job 两列,去重的就是「城市和职业」这个二元组合:
SELECT DISTINCT city, job FROM user;注意,distinct 关键字的位置不能随意放在列后面,它是一种作用于整行结果的关键字。比如下面这种写法是不符合语法的:
SELECT city, DISTINCT job FROM user; -- 错误写法2.2 group by 的语法与语义
group by 是 SQL 中的分组关键字,它会按照指定列的值把结果集分成若干组,每组最终输出一行。由于每个分组的列值相同,因此从结果上看,group by 指定的列天然具有去重效果。
同样查询所有出现过的城市,用 group by 可以写成:
SELECT city FROM user GROUP BY city;查询城市和职业组合时:
SELECT city, job FROM user GROUP BY city, job;从去重效果来看,上面两条 group by 语句分别等价于对应的 distinct 语句。但二者的语义并不完全相同:group by 除了去重以外,还允许对每个分组进行聚合计算;而 distinct 本身不具备聚合能力。
三、从执行计划看本质差异
要判断 distinct 和 group by 谁更高效,首先要看优化器如何解析和执行这两类语句。执行计划是分析 SQL 性能最直接的入口,使用 EXPLAIN 可以查看优化器选择的访问路径。
3.1 distinct 的执行计划
先建立一个测试表,并写入一定量的数据:
DROP TABLE IF EXISTS t_distinct_test; CREATE TABLE t_distinct_test ( id INT PRIMARY KEY AUTO_INCREMENT, category INT NOT NULL, name VARCHAR(100) NOT NULL, create_time DATETIME NOT NULL, KEY idx_category (category) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;查看单列 distinct 的执行计划:
EXPLAIN SELECT DISTINCT category FROM t_distinct_test;如果 category 字段上存在索引 idx_category,优化器通常会优先选择该索引进行扫描,并利用Loose Index Scan或索引顺序扫描来避免创建临时表。此时 Extra 列中可能会出现Using index for group-by之类的提示。需要说明的是,虽然提示中有 group-by 字样,但它表示的是优化器采用了松散索引扫描这种针对分组和去重场景的优化策略,distinct 和 group by 都可能受益。
如果去重字段没有可用索引,执行计划往往会显示出Using temporary,这意味着 MySQL 需要借助临时表来存储已经出现过的值,并在插入时判断是否重复。
3.2 group by 的执行计划
查看对同一列进行分组的执行计划:
EXPLAIN SELECT category FROM t_distinct_test GROUP BY category;当 category 列有索引时,group by 同样可以走索引。与 distinct 类似,优化器也可能采用松散索引扫描。对于只包含分组列、没有聚合函数的简单分组查询,MySQL 的优化器在很多情况下会把 group by 和 distinct 的执行路径优化得非常接近,甚至生成等价的执行计划。
也就是说,在最简单的「单列去重且该列有索引」场景下,distinct 和 group by 的性能几乎没有明显差异,二者都可能通过索引高效完成去重。
3.3 关键区别不一定在执行计划,而在功能定位
需要纠正一个常见误解:很多人认为 distinct 一定比 group by 快,理由是 distinct 只需要判断重复,而 group by 还要分组排序。实际上,MySQL 的 group by 并不一定要求排序,只有显式使用 ORDER BY 或者某些特定执行路径才会产生排序开销。而 distinct 在必要时同样会使用临时表。
因此,单纯从「关键字语义」判断效率是不准确的,真正决定性能的是:去重列是否有索引、数据量大小、去重后的基数高低、内存是否充足、是否需要额外排序这些条件。
四、单列去重的内部处理
4.1 有索引时的去重策略
假如查询语句是:
SELECT DISTINCT category FROM t_distinct_test;并且 category 列上建立了普通索引,那么 InnoDB 的二级索引叶子节点中保存的是「索引列值加主键值」,并且索引本身按照 category 的顺序排列。由于相同 category 的记录在索引中是连续存放的,扫描索引时只需要读取第一个值,然后跳过后续相同的值即可。
对于 group by 的等价写法:
SELECT category FROM t_distinct_test GROUP BY category;MySQL 同样可以利用索引的有序性进行分组。Optimizer 在扫描索引时,每遇到一个新的 category 值就输出一行,这被称为松散索引扫描。这种扫描方式不需要把全部记录加载到临时表中,内存占用小,执行效率高。
在有合适索引的情况下,简单 distinct 和简单 group by 的去重成本都是 O(n) 级别的扫描成本,差别很小。真正拉开差距的往往是那些更复杂的查询形态。
4.2 无索引时的临时表处理
当去重列没有索引时,MySQL 无法借助有序性去重,只能建立一个临时表。临时表可以是内存表,也可以是磁盘表。执行过程大致如下:
- 扫描源表记录
- 对每一条记录,判断其去重键是否已经存在于临时表
- 如果不存在则插入临时表,并输出或暂存该行
- 如果存在则跳过
当数据量较大、临时表超过 tmp_table_size 或 max_heap_table_size 限制时,MySQL 会把内存临时表转为磁盘临时表,使用 MyISAM 或 TempTable 存储引擎,性能会明显下降。
在这种情况下,distinct 和 group by 都可能使用临时表,二者的底层开销接近。相对而言,group by 在支持聚合时还可以做更多优化,而 distinct 的能力边界则停留在去重。
五、多列去重的差异
5.1 distinct 作用于整行
distinct 的语义是对 SELECT 列表中的所有列组成的行进行去重。例如:
SELECT DISTINCT city, job FROM user;这条语句会返回 city 和 job 都不重复的组合。如果 user 表有 100 万行,但 city 和 job 的组合只有 1 万种,那么最终返回 1 万行。
在执行计划上,如果 city 和 job 上存在联合索引 idx_city_job,MySQL 可能通过该索引完成去重。但如果只对 city 建了索引、job 没有索引,那么仅靠 city 索引无法保证 city 和 job 组合在索引中是连续有序的,优化器的选择就会更复杂,可能需要回表后再进行去重或使用临时表。
5.2 group by 同样作用于分组列组合
使用 group by 的等价写法是:
SELECT city, job FROM user GROUP BY city, job;这里 city 和 job 是分组列,结果同样是去重后的组合。与 distinct 相比,二者的语义在「只查分组列」时几乎一致。
但如果查询中增加了聚合函数,语义就开始分化:
SELECT city, job, COUNT(*) AS cnt FROM user GROUP BY city, job;这种查询 distinct 无法直接替代。即便使用 distinct 配合窗口函数或其他手段模拟,写法也复杂得多。这正是 group by 相比 distinct 最本质的优势之一。
5.3 多列去重中容易出现的一个误区
有些开发者会写成:
SELECT DISTINCT city, job FROM user WHERE city = '北京';并认为 where 条件中的 city 已经固定,distinct 只需要对 job 去重。这个理解在结果层面是对的,但优化器是否能据此降低去重成本,取决于索引情况。如果存在 (city, job) 联合索引,利用索引最左前缀匹配,city 固定后 job 依然有序,去重效率会很高;如果只有单列 city 索引,job 部分仍可能需要额外去重。
六、count(distinct) 的特殊问题
在涉及 distinct 的面试题中,count(distinct) 是一个绕不开的进阶点。它的性能和普通 distinct 查询有明显差异,也经常被用来考察候选人对执行计划的理解深度。
6.1 count(distinct col) 能走普通索引吗
先看一个常见查询:
SELECT COUNT(DISTINCT category) FROM t_distinct_test;这里要统计 category 的去重个数。与 SELECT DISTINCT category 不同,count(distinct) 需要得到去重后的记录数量,而不是去重后的明细行。
当 category 上有索引时,show 执行计划或用 EXPLAIN ANALYZE 观察,MySQL 是否能利用索引完成 count(distinct),在不同版本中表现不同。在 MySQL 8.0 中,对于 COUNT(DISTINCT col) 这类聚合,优化器通常可以扫描索引,但仍然可能需要在扫描过程中进行去重统计。
如果 category 没有索引,或者需要对多列进行 count(distinct),执行成本会显著上升,因为需要维护一个较大的去重集合,且无法简单利用索引顺序。
6.2 count(distinct a, b) 的代价
考察下面这种查询:
SELECT COUNT(DISTINCT city, job) FROM user;这种写法需要对 city 和 job 的组合进行去重后再计数。即便 city 上有索引、job 上也有索引,如果不存在 (city, job) 联合索引,MySQL 也很难直接利用单个索引完成组合去重。
底层实现上,MySQL 可能在扫描结果的基础上维护一个去重结构。去重集合越大,内存占用越高;一旦超过最大限制,就会落盘,性能急剧下降。这也是为什么在数据仓库和大数据量报表中,count(distinct) 经常是性能瓶颈之一。
6.3 count(distinct) 的一些优化思路
如果业务确实需要统计去重个数,可以考虑以下优化方案:
- 为需要去重的列建立合适的联合索引,使优化器能够有效利用索引顺序
- 把 count(distinct col) 改写为对「列为主键或唯一键的子查询」进行计数
- 使用 group by 加 count(*) 的写法,再对分组结果进行计数,必要时借助子查询或派生表
- 在大数据量场景下先在应用层或 ETL 阶段做近似去重,如使用 HyperLogLog 思路
- 检查 innodb_buffer_pool_size 和临时表相关参数,降低落盘概率
下面是一种常见的改写方式,使用派生表把去重和计数拆开,让执行计划更清晰:
-- 原始写法 SELECT COUNT(DISTINCT category) FROM t_distinct_test; -- 改写为先去重再计数 SELECT COUNT(*) FROM ( SELECT category FROM t_distinct_test GROUP BY category ) AS dedup;这种改写并不一定总是更快,但至少可以让去重过程和计数过程分离,便于进一步针对索引和执行计划进行优化。
七、索引如何影响效率
7.1 覆盖索引与回表
在讨论 distinct 和 group by 的效率时,索引是一个核心变量。以查询单个去重列为例:
SELECT DISTINCT category FROM t_distinct_test;如果 category 上有普通索引,并且查询只需要 category 列,那么该索引就是覆盖索引。扫描二级索引时可以直接获得 category 值,不需要回表查询聚簇索引,IO 成本较低。
但如果查询还包含其他列,例如:
SELECT DISTINCT category, name FROM t_distinct_test;而索引只有 idx_category(category),那么扫描 category 索引找到候选记录后,还需要根据主键回表读取 name 列,再进行整行去重。此时扫描成本虽然可能降低,但回表成本仍然存在,是否划算取决于过滤后的数据量。
7.2 索引顺序与去重方向
如果去重列有索引,且查询不需要额外排序,distinct 和 group by 都能按索引顺序读取。但如果查询要求结果按另一列排序,就需要额外排序:
SELECT DISTINCT category FROM t_distinct_test ORDER BY category;当 ORDER BY 的列与去重列一致且索引顺序匹配时,排序可以省去。反之,如果排序字段不同,优化器可能先完成去重,再做 filesort。
group by 也有类似问题。group by 默认并不保证结果有序,虽然旧版本在某些存储引擎下会隐式排序,但 MySQL 8.0 已经移除对隐式排序的依赖。因此不要假设 group by 的结果天然按分组列排序。
7.3 联合索引的覆盖能力
对于多列去重,联合索引的设计非常关键。比如频繁执行:
SELECT DISTINCT city, job FROM user;此时建立联合索引 idx_city_job(city, job) 对去重最有帮助。扫描该索引时,city、job 组合天然有序,相同组合连续出现,去重成本最低。
需要注意联合索引的顺序要和查询中的列顺序、过滤条件相匹配。如果更多查询是「先按 job 过滤,再按 city 去重」,那么可能建立 (job, city) 联合索引会更合适。联合索引设计要结合真实业务查询,而不是机械套用某一种写法。例如,如果查询还经常包含 create_time 条件,也可以考虑在高频过滤列的基础上组合去重列,但索引列越多,写入和维护成本也会越高,需要在查询收益和写入成本之间做平衡。
八、临时表、文件排序与内存限制的作用
当 distinct 或 group by 无法借助索引直接完成去重或分组时,MySQL 通常会退回到临时表。临时表的工作方式,以及和它密切相关的文件排序、内存参数,往往是不同 SQL 写法之间出现性能差距的关键来源。
8.1 临时表会在哪些情况下被使用
以下几种情况很容易触发Using temporary:
- 去重或分组列没有可用索引。
- 多列去重的组合与现有索引顺序不匹配。
- 去重或分组前还需要进行函数计算、类型转换、隐式转换。
- SQL 中同时包含不同字段的 ORDER BY,优化器无法直接复用同一个索引顺序。
执行计划中出现Using temporary时,说明 MySQL 需要借助一个额外的中间结构来保存中间结果。这个中间结构可能只存在于内存中,也可能落到磁盘上,两者的开销差距非常大。
8.2 内存临时表与磁盘临时表的区别
MySQL 会优先创建内存临时表。内存临时表的大小受到tmp_table_size和max_heap_table_size两个参数的共同限制,取两者中的较小值作为内存临时表的上限。
当中间结果超过这个上限后,MySQL 会把临时表转成磁盘临时表。磁盘临时表可能使用 TempTable 引擎并落到 InnoDB 的磁盘表空间,也可能在特定场景下使用 MyISAM。无论哪种实现,磁盘 IO 的开销都会让处理速度明显下降。
所以同样是去重,在小数据量下 distinct 和 group by 可能都很快;但数据量一旦超过内存阈值,导致临时表落盘,整体耗时可能成倍上升。这也是很多开发者在线上写出慢 SQL,测试环境却表现正常的原因之一。
8.3 filesort 对去重查询的影响
如果查询结果需要按照非索引顺序排序,MySQL 就会使用 filesort。filesort 并不是一定写文件,它首先在sort_buffer_size指定的排序缓冲区中排序,只有当数据量超过缓冲区大小时,才会使用外部归并排序并写到磁盘。
以一个常见场景为例:
SELECT DISTINCT category FROM t_distinct_test ORDER BY name;这里去重列是 category,排序列是 name。如果只有 category 索引,优化器可以先利用索引完成去重,然后对去重结果执行 filesort。去重后如果结果仍然很大,filesort 的成本可能比去重本身更高。
group by 也有相同问题。不要假设分组列和排序列一致就一定没有排序,也不要假设 group by 一定有排序,最终要看优化器选择的执行路径和表上的索引设置。
8.4 调优时应该关注哪些参数
如果确认 SQL 出现了临时表或文件排序问题,可以从这些参数和设计维度入手:
tmp_table_size:控制内存临时表上限,可结合业务数据量适当调大,但不能无限调大。max_heap_table_size:与控制内存表大小相关的参数,和 tmp_table_size 共同生效。sort_buffer_size:控制 filesort 缓冲区大小,影响排序是否容易落盘。innodb_buffer_pool_size:保证热点索引和数据尽量留在缓冲池中,减少磁盘读。- 把去重/分组列和排序列纳入索引设计,从根源上减少临时表和 filesort。
九、性能实测:不同场景下的数据对比
前面更多是从执行计划角度分析,下面用一个简单的测试场景,把不同条件下 distinct 和 group by 的表现做一个直观对比。这里的数值是示例环境中的一组测试结果,重点看趋势,不做绝对性能承诺。
9.1 测试数据准备
继续使用前面建立的t_distinct_test表,向表中写入 100 万行测试数据:
category列基数约为 1 万,值分布不均匀。name列使用随机字符串,重复度较低。create_time覆盖最近一年的时间范围。
分别测试无索引、单列 category 索引、联合索引条件下的耗时。每次查询执行多次后取平均值,避免单次波动影响结论。
9.2 单列去重场景
对比语句:
SELECT DISTINCT category FROM t_distinct_test; SELECT category FROM t_distinct_test GROUP BY category;示例数据如下:
| 场景 | 索引情况 | distinct 平均耗时 | group by 平均耗时 | 结论 |
|---|---|---|---|---|
| 单列去重 category | 无索引 | 约 890 ms | 约 900 ms | 二者接近,临时表开销明显 |
| 单列去重 category | category 上有索引 | 约 45 ms | 约 48 ms | 差异很小,都走索引去重 |
可以看到,在有索引时,简单 distinct 和简单 group by 的耗时几乎可以忽略差异;在无索引时,两者都会受到临时表影响,耗时明显上升。
9.3 多列去重场景
对比语句:
SELECT DISTINCT city, job FROM user; SELECT city, job FROM user GROUP BY city, job;示例数据如下:
| 场景 | 索引情况 | distinct 平均耗时 | group by 平均耗时 | 结论 |
|---|---|---|---|---|
| 多列去重 city, job | 只有 city 单列索引 | 约 1350 ms | 约 1380 ms | 无法直接利用索引组合,成本高 |
| 多列去重 city, job | (city, job) 联合索引 | 约 65 ms | 约 62 ms | 联合索引显著降低去重成本 |
这说明多列去重时,联合索引的收益要远大于在 distinct 和 group by 两种写法之间反复纠结。
9.4 从实测结果中读出的结论
- 在简单的单列去重、有合适索引时,distinct 和 group by 性能差异很小。
- 在无索引或多列去重无法利用联合索引时,二者的性能都会明显下降,且差距不大。
- 真正决定性能的是索引设计、数据量、去重基数、临时表是否落盘和排序需求。
- 与其纠结用哪个关键字,不如先检查去重列有没有合适的索引。
十、group by 相比 distinct 的额外能力
理解了执行计划之后,就可以回答面试中常见的另一个问题:既然 distinct 和 group by 都能去重,为什么还要用 group by?这是因为 group by 的功能边界远不止去重。
10.1 聚合计算
group by 可以和聚合函数直接配合,统计每个分组内的数量、总和、平均值、最大值和最小值:
SELECT city, COUNT(*) AS user_count FROM user GROUP BY city;而 distinct 只能返回不重复的行,本身不具备聚合能力。要想用 distinct 同时得到计数,往往需要借助子查询、窗口函数或额外扫描,写法复杂得多。
10.2 HAVING 分组后过滤
group by 之后可以使用 HAVING 对分组结果继续过滤:
SELECT city, COUNT(*) AS user_count FROM user GROUP BY city HAVING COUNT(*) > 100;这种“分组后再筛选组”的能力是 distinct 不具备的。
10.3 多级汇总与 WITH ROLLUP
group by 可以配合WITH ROLLUP生成小计行和总计行:
SELECT city, job, COUNT(*) AS cnt FROM user GROUP BY city, job WITH ROLLUP;这种用于报表和统计的写法,distinct 也无法直接实现。
10.4 ONLY_FULL_GROUP_BY 下的语义差异
MySQL 8.0 默认开启ONLY_FULL_GROUP_BY。启用后,SELECT 列表中的非聚合列必须出现在 group by 子句中:
-- ONLY_FULL_GROUP_BY 下可能报错 SELECT city, job FROM user GROUP BY city;这说明 group by 承载的是一整套分组聚合语义,而 distinct 只承载“结果去重”这一件事。二者定位不同,不能简单互替。
十一、不同业务场景下的选择策略与优化建议
综合前面的分析,可以把实际工作场景归为几类,分别给出选择建议。
11.1 只需要去重,不需要聚合
如果需求只是取出去重后的列值或行组合,推荐优先使用 distinct。它的语义更直接,代码可读性更好。在去重列有索引时,性能和 group by 没有本质区别。
11.2 去重后还要聚合、分组或过滤
如果去重只是第一步,后续还要 count、sum、avg,或者需要按分组条件过滤,应直接使用 group by。这样可以一次完成分组和聚合,避免绕行派生表。
11.3 多列去重优先设计联合索引
对于列组合去重,优先把去重列按照业务查询顺序建成联合索引。联合索引能让去重从临时表方案变成索引扫描方案,收益通常远超调整 SQL 关键字。
11.4 面向接口的列表去重要区分层级
有些“去重”问题本质是业务模型问题,例如分页后出现重复数据、关联查询放大结果。此时不应该通过无脑加 distinct 掩盖问题,而应检查连接条件、数据粒度或主键生成逻辑。
11.5 超大表和报表场景
在数据量很大的报表场景,尽量避开直接 count(distinct):
- 优先用 group by 派生表分别完成去重和计数。
- 对允许误差的统计,可以考虑 HyperLogLog 等近似去重方案。
- 把高频统计下推到数仓、ES、ClickHouse 等擅长聚合的组件中。
- 检查临时表落盘情况,必要时在 ETL 阶段预先聚合。
十二、高频面试追问与回答要点
回到这道面试题本身。面试官真正想考察的,不是让候选人背出一个“标准答案”,而是看候选人能否根据执行计划和场景差异讲清原因。
12.1 distinct 一定比 group by 快吗?
不是。在简单单列去重且去重列有索引时,两者性能接近;在无索引或多列去重时,两者的临时表成本接近。性能主要取决于索引、数据量、去重基数和排序需求。
12.2 count(distinct) 为什么经常成为慢查询?
因为 count(distinct) 需要维护去重结果才能统计数量,去重集合越大越容易占用大量内存,超过限制后就会落盘。多列 count(distinct) 更难直接利用单个索引。可以改写为 group by 派生表再计数,或考虑近似去重方案。
12.3 group by 一定需要排序吗?
不一定。MySQL 8.0 中 group by 默认不会隐式保证排序结果,只有显式 ORDER BY 或某些执行路径才会产生排序成本。如果结果需要稳定顺序,应显式写 ORDER BY。
12.4 松散索引扫描是什么?
松散索引扫描是 MySQL 利用索引有序性进行去重或分组的一种方式。扫描索引时每遇到一个新的去重键就输出一行,跳过后续相同值,相比把所有数据加载到临时表,能显著节省内存和 IO。
12.5 多列去重时怎么设计索引?
优先为去重列建立联合索引,索引列顺序要和查询条件、分组顺序匹配。例如高频查询是 city 加 job 去重,就考虑 (city, job) 联合索引。同时应结合实际查询中的过滤列、排序需求来综合设计。
12.6 为什么 group by 能聚合而 distinct 不能?
因为 distinct 是面向整行结果集去重的关键字,功能定位在“去掉重复行”;group by 是分组子句,核心作用是建立分组上下文,再配合聚合函数对每一组进行计算,二者功能边界不同。
十三、总结与回答模板
回到文章开头的高频面试题。比较稳妥的回答方式是:
distinct 和 group by 在“简单去重”这一目标上可以等价实现,而效率高低主要不取决于关键字本身,而取决于去重列是否有索引、是否需要联合索引、去重后的基数多大、临时表和排序是否落盘。只有去重需求且语义清晰时,优先用 distinct;去重之后还要聚合、分组过滤或汇总统计时,用 group by。优化时先看执行计划和索引设计,再决定 SQL 写法。
整体判断逻辑可以概括为三步:第一步,看需求,是只去重还是要聚合;第二步,看执行计划,是否存在全表扫描、临时表、filesort;第三步,看索引,能不能用覆盖索引或联合索引把去重成本降到最低。
大多数场景下,distinct 和 group by 的性能差距远没有“索引不合理、临时表落盘、排序字段没有索引”带来的影响大。掌握这个分析框架,远比记住“谁更快”的结论更有价值。