
1. DISTINCT到底是什么为什么我们天天要跟它打交道做数据库开发这几年我发现自己写SQL时用得最顺手又最容易翻车的关键词DISTINCT绝对排得上前三。它的字面意思就是“去重”但实际工作里它承担的任务远不止删掉重复行那么简单。先明确一下它的定位DISTINCT是SQL标准里用来消除查询结果中重复行的关键字。你写一句 SELECT DISTINCT column_name FROM table_name数据库就会把返回结果里完全相同的行合并成一行。这个操作在数据清洗、报表统计、接口幂等校验、日志去重这些场景里几乎是标配需求。比如你有一张用户订单表想看看一共有多少个城市产生过订单直接SELECT城市列的话会出现一大堆重复城市名加上DISTINCT就干净了。我在实际项目中接触过大量SQL慢查询优化发现很多人对DISTINCT的理解只停留在“加个关键字就行”的层面结果要么结果算错要么查询慢到没法看。这篇内容我就把自己踩过的坑、排查的思路、以及不同数据库里的行为差异一起梳理出来适合刚入门想搞清楚去重逻辑的初学者也适合写SQL写了好几年但没仔细抠过细节的开发。需要提前说明的是下面提到的很多实操细节是基于MySQL、SQL Server、PostgreSQL、Oracle这些主流关系型数据库的常见行为总结的。具体某一种数据库可能有细微差别我在对应位置会单独标注。2. DISTINCT的几种核心用法以及每个用法背后的坑2.1 单列去重最简单但最容易忽略NULL最基本的用法就是对单列去重。假设有一张员工表employee里面有department字段你想知道公司一共有几个部门写SELECT DISTINCT department FROM employee;这个写法本身没有技术含量但很多人会忽略一个问题如果department列里有NULL值DISTINCT会把NULL也当成一个“值”返回。也就是说结果里会多出一行NULL。这在统计报表里经常会引发误解因为业务方看到一行为空会以为是数据缺失或者程序有bug。我处理过一个实际案例某数据看板展示销售区域数量加的过滤条件是“区域不为空”但报表里始终多一个空行。排查半天发现是底层视图里有个UNION ALL把空字符串字符串拼了进来而DISTINCT无法区分NULL和空字符串在某些数据库里的行为还不太一样。Oracle里空字符串就是NULLMySQL里空字符串是空字符串NULL是NULL两个算不同的值。这种细节不亲自踩一遍很难记住。另一个单列去重的坑是很多人以为DISTINCT会自动排序。真的没有。DISTINCT只负责去重不负责排序。你看到有些结果好像有序那只是巧合或者数据库优化器顺手做的绝不能依赖这个顺序。要排序必须显式加ORDER BY。2.2 多列去重它到底按什么规则去重多列DISTINCT是误解重灾区。SELECT DISTINCT col1, col2这种写法去重单位是“col1和col2的组合”而不是分别对col1、col2去重。我见过有人写这个SQL是想去掉col1重复的行结果发现col1明明有重复值却还在结果里当场就懵了。举一个具体的例子SELECT DISTINCT province, city FROM orders;如果两条记录的province相同、city不同它们会被当成两行保留。只有在province和city都相同的情况下才合并。这个逻辑用集合论的话说就是按多列的笛卡尔组合判断完全重复。理解这一点非常重要因为很多人把DISTINCT当成“按某一列去重”的工具实际上它是对整行做“完全相同才合并”的处理。如果你真正想要的效果是“按某一列去重但需要保留该列对应的其他字段”那DISTINCT解决不了得用窗口函数或者GROUP BY加聚合函数。后面我会详细讲这两种替代方案。2.3 DISTINCT与ORDER BY、LIMIT的搭配细节DISTINCT配合ORDER BY用的时候有一个经典报错。在MySQL里SELECT DISTINCT name FROM users ORDER BY age逻辑上是有问题的因为SELECT的列里没有age而DISTINCT按name去重后一行name可能对应多个age数据库不知道该按哪个age排。MySQL早期版本会直接报错好一点的情况是它把age也隐式加入排序导致结果不符合预期。正确做法是排序字段要么包含在DISTINCT的列里要么用聚合表达式。比如SELECT DISTINCT name FROM users ORDER BY name; SELECT DISTINCT name, COUNT(*) FROM users GROUP BY name ORDER BY COUNT(*) DESC;后一条其实是GROUP BY的用法但它能解决很多DISTINCT做不了的事。LIMIT配DISTINCT也有讲究SELECT DISTINCT col FROM table LIMIT 10的意思是先去重再取前10行。但去重本身是全表扫描级的操作LIMIT只能最后生效不能减少去重的计算量。我排查过一个慢SQL对方以为加LIMIT能加速查询实际上DISTINCT部分依旧把全表都算了一遍。这条经验对后面做性能优化很重要。2.4 DISTINCT与聚合函数搭配COUNT(DISTINCT)最容易踩性能坑DISTINCT用得最多的场景之一就是统计去重数量。SELECT COUNT(DISTINCT user_id) FROM order_log这是用户数、设备数、订单数等核心指标的经典写法。但这个写法在大表上非常吃性能因为COUNT(DISTINCT)的实现通常是先把目标列的值做一个去重排序再统计数量。这个操作基本没办法走常规索引覆盖数据量大时就是慢查询大户。我在之前的公司遇到过一条每分钟跑一次的定时任务里面就有一个COUNT(DISTINCT)关联了三个大表每次执行要40多秒。后来优化时把这个去重计数拆到了离线预聚合表才把时间降到秒级。这个问题的根治方案往往不是调SQL参数而是改统计口径和数据架构。COUNT(DISTINCT)还有一个容易算错的地方如果你写的是COUNT(DISTINCT col1, col2)它的语义是统计col1和col2组合的去重数量不是分别统计两个列再去重相加。这个差异在报表口径核查时经常引起争议需要特别注意。3. DISTINCT、GROUP BY、窗口函数怎么选别再凭感觉写了3.1 DISTINCT和GROUP BY的等价与不等价很多老开发会告诉你SELECT DISTINCT col FROM table和SELECT col FROM table GROUP BY col结果几乎一样。确实单纯做一列去重两者的结果等价很多数据库优化器还会把DISTINCT转成GROUP BY执行。但两者有差异。GROUP BY天然支持聚合函数DISTINCT本身不能和聚合函数直接组合使用除了COUNT DISTINCT这种特例。比如你想统计每个部门的员工数只能写GROUP BY department配合COUNT(*)没法用DISTINCT实现。另一个差异是结果集的排序表现。GROUP BY在很多数据库里会按分组列排序输出DISTINCT则不一定。SQL Server和Oracle的优化器对两者的执行计划也可能不同有些场景GROUP BY因为能走哈希聚合反而更快。我个人的选型原则是只去重不统计且没有聚合需求的用DISTINCT语义更清晰需要分组统计数据或要排序的用GROUP BY需要按某列去重但保留多列信息的用窗口函数。3.2 窗口函数应对“按某列去重但要其他字段”的终极方案前面提到DISTINCT无法解决“按某列去重但需要其他字段”的问题窗口函数ROW_NUMBER()是目前最通用的解法。比如订单表里每个用户有多条订单你想取每个用户最新的一条记录。DISTINCT做不到GROUP BY也不好直接取整行窗口函数一行搞定SELECT * FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY user_id ORDER BY create_time DESC) AS rn FROM orders ) t WHERE rn 1;这个方案在MySQL 8.0、SQL Server、Oracle、PostgreSQL里都支持。它的逻辑是先按user_id分桶桶内按create_time倒序编号最后只保留编号1的行天然完成按用户去重并取最新记录。这个需求在业务里出现频率极高比DISTINCT好用太多。很多人分公司说MySQL里这张表也能用窗口函数请确认自己的MySQL版本是否8.0以上5.7及以下版本是不支持的那种环境只能用变量写法或者改造成子查询。3.3 什么时候用UNION去重会比DISTINCT更清晰UNION也会去重UNION ALL不去重。如果你有两个子查询结果需要合并并要求整体去重很多人先想到UNION。这没问题但要知道UNION内部默认做的事就是把两个结果集合并后去重所以它的语义等于DISTINCT作用在合并结果集上。不过有一点要注意UNION的去重对象是整行不是单列。如果你只想对某个ID去重而保留更多信息UNION同样做不到还是要回到窗口函数。UNION的优势在于组合逻辑更清晰比如分别统计线上和线下订单后再合并一眼就能看出业务意图。我见过一个反面案例有人为了去重写了三层嵌套子查询加多个UNION性能奇差。后来我建议他先看业务需求到底是什么结果他只要一个COUNT(DISTINCT user_id)瞬间简洁了十倍。所以工具本身没有高低关键是你到底要什么。4. 深入理解DISTINCT的底层执行逻辑才能对症优化慢SQL4.1 DINSTINCT在数据库内部大概是怎么跑的要谈优化得先知道DISTINCT背后数据库做了什么。以MySQL为例最常见的执行方式是使用临时表加索引或排序去重。具体来说优化器可能选择创建一个临时表把DISTINCT列的每一行内容插入临时表用唯一索引保证不重复最后再扫描临时表返回结果。数据量大时这个临时表会落在磁盘上性能立刻崩。有些场景优化器会选择先对数据排序再顺序扫描跳过相邻重复行。这种方案依赖于排序操作如果排序的量超过内存阈值就会产生磁盘上的外部排序。这就是为什么DISTINCT在百万级数据上都能卡顿的原因。SQL Server里的Hash Aggregate算是一个更高效的选择它把数据按哈希分区后分组去重不需要全局排序。但哈希聚合需要内存足够内存不足时同样会溢出到tempdb。所以一个DISTINCT慢不慢不只是SQL写法的问题和数据库内存配置、临时表空间都有关系。4.2 如何定位DISTINCT导致慢查询EXPLAIN先看一眼排查一条疑似DISTINCT引起的慢SQL第一步永远是看执行计划。MySQL用EXPLAIN或EXPLAIN ANALYZESQL Server用SET STATISTICS PROFILE ONOracle看执行计划的DBMS_XPLAN。在MySQL里你如果看到执行计划的Extra列出现Using temporary或者Using filesort就要高度警惕这往往意味着DISTINCT走了临时表加排序的路径。此时先确认能不能用索引覆盖整个查询如果能优化器大概率不会额外建临时表。举个实际优化例子。有一张订单流水表总量近两千万行业务需要查询今天消费过的用户ID去重后的数量原始SQLSELECT COUNT(DISTINCT user_id) FROM order_flow WHERE order_date 2024-05-20;执行计划显示全表扫描然后临时表去重耗时40多秒。我建了一个(order_date, user_id)的联合索引效果立竿见影。原因是查询条件用到了order_date定位日期而user_id从索引直接获取COUNT(DISTINCT user_id)可以在索引扫描后利用索引的有序性去重不需要额外排序。这个优化思路值得记住COUNT(DISTINCT)的列最好和WHERE条件里的等值列一起进联合索引。4.3 大数据量下替代DISTINCT的几种套路如果索引优化效果有限就要考虑换实现方案。我总结了几条在真实场景里验证过的方法第一预聚合。把COUNT(DISTINCT)的计算从查询时移到写入时用一张汇总表按天累计用户数。查询直接读预聚合结果复杂度从几千万行降到几十行。这是报表系统里最常用的手段牺牲一点实时性换查询速度非常值得。第二用GROUP BY代替DISTINCT再包一层COUNT。比如把SELECT COUNT(DISTINCT user_id)改成SELECT COUNT(*) FROM (SELECT user_id FROM order_flow GROUP BY user_id) t。在某些数据库里子查询里的GROUP BY可以走索引有序扫描加紧凑的计数比直接COUNT(DISTINCT)更快。但这个方法不是绝对的取决于优化器需要实测。第三换成HLL或者HyperLogLog这类近似去重算法。Redis、PostgreSQL的HyperLogLog扩展、ClickHouse里都有现成实现适合容忍一定误差的大规模UV统计。误差一般在1%以内但速度比精确去重快几个数量级。做用户行为分析的大数据平台基本都走这条路。4.4 并行SQL优化视角下的DISTINCT热词里提到了并行SQL优化这里也顺带聊一聊。DISTINCT本身并不是一个特别适合夸并行度的操作因为它天然有合并语义。在Oracle或SQL Server里DISTINCT操作通常会被分成多个并行扫描任务最后汇总到一个合并步骤。并行度设得太高时合并步骤会成为瓶颈资源都在等待和通信上。我自己遇到过Oracle环境里一个带DISTINCT的查询并行度从4调到8之后反而更慢。当时的结论是数据量没有大到需要那么多并行进程还占用了系统资源。所以并行参数不是越大越好得结合表大小、CPU负载、IO能力一起评估。如果一条DISTINCT慢SQL已经确认是单表大查询先试并行度小的值再逐步往上加找到拐点。5. DISTINCT的边界场景和注意事项全是高频面试题和实战坑5.1 字符串去重时的隐式转换陷阱DISTINCT作用于字符串列时如果列定义是CHAR和VARCHAR混合不同数据库对尾部空格的比较规则不同。SQL Server默认用了排序规则忽略尾部空格所以abc 和abc会被当成同一个值去重。MySQL的VARCHAR比较则不会忽略尾部空格这两个值是不同行。PostgreSQL里text和varchar的行为又略有不同。这个差异在数据迁移或者异构数据库同步时特别容易引发问题。有一次我们把SQL Server的数据同步到MySQL发现同一个用户ID因为尾部空格不同在MySQL里被统计成两个用户。当时排查了很久最终的解决方法是同步前统一做TRIM清洗。同样的道理如果存储的手机号、身份证号这类字符串有空格或全角字符DISTINCT统计结果一定会虚高。规范的做法是在数仓入仓阶段就统一清洗不要等写SQL时再到处TRIM那样SQL会复杂且容易遗漏。5.2 NULL的处理规则COUNT(DISTINCT)里NULL算不算数这个问题在SQL Server和Oracle里行为是一致的COUNT(DISTINCT col)不会统计NULL值而DISTINCT放在SELECT列表时NULL会被当作一个独立的分组显示出来。这两个行为是很多人的知识盲区。举个例子SELECT COUNT(DISTINCT email) FROM users如果email列有一行是NULL统计结果不会包含NULL但SELECT DISTINCT email FROM users的结果集里会有一行NULL。我在面试候选人的时候经常问这个点能准确说清楚的人不足三成。这个规则对数据质量监控有很大影响。比如你统计用户表中邮箱不重复且不为空的用户数正确写法不能只写COUNT(DISTINCT email)而应该写COUNT(DISTINCT NULLIF(email, ))或者加上WHERE email IS NOT NULL AND email 的条件。否则网上注册用户里大量空邮箱直接统计出来的数据会和实际有效用户数对不上。5.3 DISTINCT与JOIN组合时的膨胀问题这是一个非常隐蔽的错误。当DISTINCT和其他表JOIN时如果JOIN导致一对多关联那么去重之前的数据量会先被放大再去重。这个放大过程不仅让结果可能出错也让SQL慢得离谱。我处理过一个用户维度标签表和一个订单表JOIN的场景。原始需求是统计有订单的用户数写了SELECT COUNT(DISTINCT u.user_id) FROM users u JOIN orders o ON u.user_id o.user_id;看起来没毛病但如果orders表里每个用户有大量订单JOIN后的中间结果会有很多重复行DISTINCT再对user_id去重。问题是这个JOIN完全没有必要。直接写SELECT COUNT(DISTINCT user_id) FROM orders;结果一模一样但避免了无意义的JOIN放大。如果orders表有user_id索引后者性能提升可能超过百倍。这条经验的核心是能用单表DISTINCT解决的问题绝不引入JOIN。关联前先想想你要的数据是不是本来就在一张表里。类似的场景还有DISTINCT配合GROUP_CONCAT或STRING_AGG排序和去重规则不同容易混淆。建议在组合使用前先用小数据量手动验证一遍结果不要靠直觉。5.4 DISTINCT在子查询和视图里的可读性问题代码可维护性也是需要注意的。一个长SQL里如果到处都是DISTINCT往往说明表结构设计或数据模型有问题而不是SQL写法足够高级。真正让人舒服的SQL风格是让DISTINCT出现在它该出现的地方比如维表去重、统计口径明确的地方而不是把它当成万能胶水到处粘。在团队协作中我通常建议DISTINCT只用于查询层不要塞进复杂视图的每一层。因为视图嵌套视图时每一层都去重既影响性能也影响排错。业务报表出现问题想定位是哪一层多了去重逻辑会非常痛苦。把DISTINCT和业务语义绑定用清晰的名字或注释说明这层的去重目的比写一堆炫技SQL重要得多。5.5 与GROUP BY混用的习惯先分组再去重还是先去重再分组有人会写SELECT DISTINCT department, COUNT(*) FROM employee GROUP BY department这种写法本身是错的或者冗余的。因为已经GROUP BY department了department本来就不会重复再加DISTINCT没有任何意义还会让优化器多做无用功。养成一个好习惯分组聚合后不需要DISTINCT分组列天然唯一去重后不需要GROUP BY取了多列值还要去重这种需求请用窗口函数。这两种思路一旦混着写SQL逻辑容易混乱后期维护的人根本看不懂你本意是什么。我自己带新人时反复强调SQL不是越复杂越牛而是看别人能不能一眼看懂你要什么。6. DISTINCT导致的实际生产故障复盘一条慢SQL引发的雪崩6.1 事故背景和现象有一年我们维护一个商户后台系统每天凌晨会跑一批报表任务。某天零点刚过数据库CPU直接飙到95%以上所有写操作被拖死线上接口平均响应时间从30毫秒涨到4秒。DBA紧急喊停了一批定时任务才恢复。事后排查日志时定位到有一个月初新增的报表任务里面的一句SQL长这样SELECT DISTINCT merchant_id, order_type, COUNT(DISTINCT order_id) FROM settlement_detail WHERE settle_date BETWEEN 2024-05-01 AND 2024-05-31 GROUP BY merchant_id, order_type;这条SQL有两个问题叠加。第一个是表里数据量已经过亿全表扫描查询一个月的数据量也有几千万行。第二个是DISTINCT和GROUP BY同时滥用先把merchant_id、order_type组合去重又对order_id做COUNT(DISTINCT)优化器生成的执行计划涉及巨大的临时表和排序直接把服务器资源吃光。6.2 排查思路和验证方法我当时复现的方式是在测试环境开一个会话用EXPLAIN分析执行计划。结果Extra列出现了Using temporary和Using filesort同时扫描行数接近6000万行。这个数量级同时进入两个全局去重操作慢是必然的。接着我用二分法测试先去掉外层DISTINCT执行时间从100多秒降到60秒再把COUNT(DISTINCT order_id)改成COUNT(order_id)降到15秒最后加上合适的索引总耗时只剩3秒。通过这个对比定位出真正的瓶颈在外层DISTINCT和COUNT(DISTINCT)同时存在时产生的巨大中间结果。6.3 最终优化方案和效果最终的改法是彻底重构。因为settlement_detail表本身已经有订单唯一键order_id不会重复COUNT(DISTINCT order_id)直接可以改成COUNT(order_id)。而外层DISTINCT配合GROUP BY完全是画蛇添足直接删掉。修改后的SQLSELECT merchant_id, order_type, COUNT(order_id) FROM settlement_detail WHERE settle_date BETWEEN 2024-05-01 AND 2024-05-31 GROUP BY merchant_id, order_type;再给(settle_date, merchant_id, order_type, order_id)建了联合索引查询秒回。这次事故也给我留下一个深刻印象DISTINCT不是免费的关键字它背后可能藏着巨大的计算开销在大数据量线上环境里每一个DISTINCT都要问一句这里真的需要精确去重吗可以用业务主键替代吗可以用近似算法替代吗7. 从数据中去重到业务中定义“重复”DISTINCT的边界在哪7.1 业务上的“重复”不是一个技术问题写SQL的时候DISTINCT处理的是“行完全一致”但业务上的“重复用户”“重复订单”“重复设备”往往不是完全一致。比如同一个用户换了手机号注册了两次技术上两条记录完全不同DISTINCT永远去不掉这种“软重复”。这时候需要的是业务主键归一比如用身份证号、统一信用代码等业务唯一键来合并。我有一次做用户画像清洗维度表里同一个自然人因为邮箱和手机号不同被当成三个用户。数据团队提出用DISTINCT去重我当时就泼了冷水DISTINCT只能帮你处理精确的重复数据写入处理不了业务语义上的同一个人。最后我们引入了ID-Mapping方案把各个渠道的标识映射到一个统一的用户ID上这才真正解决了问题。7.2 数据清洗时DISTINCT扮演的角色有限热词里有一个是“清洗---sql语句去重”说明很多人把SQL去重直接等同于数据清洗。实际数据清洗的工作量大部分花在缺失值填充、格式统一、异常值剔除、业务规则校验上DISTINCT只是其中很小的一步。拿地址清洗举例同一个地址“北京市海淀区中关村大街1号”和“北京海淀区中关村大街1号”在字符串层面完全不同DISTINCT毫无办法。要先做文本正则化、同义词归一、地址标准化再去重才有意义。这一点务必要有心理预期SQL里的DISTINCT是物理去重不是智能合并。7.3 用DISTINCT做数据质量监控的合理姿势虽然DISTINCT做不了复杂清洗但它很适合做数据质量监控。比如每天对主键表跑一个COUNT和COUNT(DISTINCT id)的对比如果两者不一致说明表里有重复主键立刻告警。这是数仓里最常见的数据校验手段简单有效。再比如日志表里统计页面访问的UV用COUNT(DISTINCT device_id)。这里如果device_id字段本身有大量NULL或空字符串监控阈值就要把这些情况排除掉否则数据质量波动会让告警变得不可信。我习惯在监控SQL里加数据质量附带检查比如同时统计NULL比例和空字符串比例确保DISTINCT的结果始终反映真实业务。8. 关于DISTINCT的面试题和对话场景如何回答得透彻技术面试里DISTINCT也是一个高频点问题通常沿着从浅到深的顺序展开。我梳理了一套回答思路比背答案更有用。8.1 “DISTINCT和GROUP BY有什么区别”面试官问这个表面上考语法实际考的是你对执行计划的理解。我的回答框架是这样两者在只去重不聚合的场景结果等价但GROUP BY的设计目标是分组聚合所以它可以配合COUNT、SUM等聚合函数DISTINCT的设计目标是去除重复行不能直接在列表里配合单个分组的聚合函数使用。在部分数据库中只去重场景的优化器可能把DISTINCT重写成GROUP BY但这属于实现细节不看执行计划没法一概而论。8.2 “COUNT(DISTINCT col)为什么在大表上慢怎么优化”慢的核心在于精确去重需要保存所有不同值才能计数内存不够就要落盘。优化手段从SQL层面说先看能不能走覆盖索引不行的话看能不能改成GROUP BY子查询再不行考虑预聚合表最后可选近似算法。回答问题时如果能顺着这条链路走面试官基本就认可你的实战深度了。8.3 “DISTINCT能用到索引吗”这个问题容易答错。DISTINCT如果作用的列恰好是索引列并且查询条件可以让优化器选择索引扫描那么索引的有序性能够帮助去重不需要额外排序。但更多情况下DISTINCT会对多列组合或多个不同列操作索引帮助有限。我自己面试时会强调一点索引对DISTINCT有没有帮助抽出一条真实SQL跑EXPLAIN看执行计划比背书里的理论更可靠。8.4 “如何排查一条带DISTINCT的慢SQL”标准的排查链路是先看执行计划确认是否出现临时表、文件排序再看数据量和过滤条件是否合理索引是否被正确用到第三评估业务上能否用其他写法替代第四实在不行考虑架构层面优化。这个问题在实际工作里出现频率极高回答时如果能覆盖执行计划、索引优化、业务口径三层就会显得很有条理。9. 最后再分享几个我实际项目里养成的DISTINCT好习惯写了好几年SQL踩了不少DISTINCT的坑现在自己总结出一套使用习惯可以分享给你。第一写SQL之前先问自己“我要去重的逻辑到底是什么”。是去掉完全重复的行还是按某个业务键保留一行还是统计不重复数量这三个需求分别对应DISTINCT、窗口函数、GROUP BY或COUNT(DISTINCT)。先想清楚语义再落笔能避免大半错误。第二涉及多表JOIN时先别急着加DISTINCT。先审视JOIN本身是否会产生重复数据如果需要保留一侧的维度信息考虑按主键去重后再JOIN而不是JOIN完了再去重。这个顺序颠倒过来性能和结果都可能出问题。第三任何带DISTINCT的统计SQL上线前至少在测试库跑一遍执行计划。MySQL里看一眼Extra列有没有Using temporary或Using filesort有的话就说明这次去重可能不轻松。提前发现提前优化别等着线上任务超时被业务部门追着问。第四DISTINCT不像数学里的等号那么纯粹它受数据库实现、字段类型、NULL规则、字符排序规则影响。跨数据库迁移时一定要回归测试统计结果不能默认两边行为一致。这是我在SQL Server到MySQL迁移中付出过代价换来的经验。DISTINCT这个关键字看起来简单但真要把它用对、用快、用出价值背后是一整套关于数据模型、索引结构、执行计划、业务语义的理解。希望这篇文章能帮你在下一次遇到“重复数据”问题时多一分从容少一点手忙脚乱。