SQL Server 2016多内连接下COUNT(DISTINCT)无法正常工作问题求助
解决SQL Server 2016中
COUNT(DISTINCT u.uid)查询长时间运行的问题 我完全懂你的困惑——同一个查询在其他SQL Server环境跑的好好的,偏偏在2016版本里卡住不动,换成COUNT(1)却能秒出236的结果,这确实让人摸不着头脑。结合你的描述,我来拆解可能的原因和对应的解决办法:
为什么COUNT(DISTINCT)和COUNT(1)性能差这么多?
先理清楚两者的本质差异:
COUNT(1)只是统计连接后结果集的总行数,不需要任何去重操作,哪怕结果里有大量重复的uid,它只需要快速计数。COUNT(DISTINCT u.uid)需要先把结果集中所有的uid收集起来,去除重复值后再计数。如果连接后的结果集很大,或者没有合适的索引支持,这个去重过程会非常耗时。
可能的原因及排查/解决步骤
1. 执行计划低效(SQL Server 2016优化器的选择问题)
SQL Server 2016的查询优化器可能生成了一个糟糕的执行计划——比如先做全表连接,再对海量数据做去重排序,而不是先过滤再去重。
- 排查方法:在SSMS里按
Ctrl+M打开「包括实际执行计划」,然后分别运行两个查询,对比执行计划。重点看COUNT(DISTINCT)的计划里是否有Sort(排序)、Hash Match(哈希匹配)这类高开销的操作,以及是否用到了uid或uemail相关的索引。 - 解决思路:如果发现计划里没有用到合适的索引,就针对性创建(后面会讲);或者手动改写查询引导优化器选择更优的逻辑(见下文改写示例)。
2. 表统计信息过时
SQL Server的优化器依赖统计信息来估算数据分布,如果ABC表的统计信息过时,2016的优化器可能错误估计了连接后的结果集大小,从而选择了低效的执行计划。
- 解决方法:执行下面的语句更新统计信息,然后重新运行
COUNT(DISTINCT)查询:
UPDATE STATISTICS ABC WITH FULLSCAN;
3. 查询逻辑可以优化
你的原查询是先做JOIN再去重,或许可以调整逻辑,先筛选出符合条件的uid再去重计数,减少中间结果集的大小:
改写方式1:用IN替代JOIN
SELECT COUNT(DISTINCT uid) AS NOOFROWS FROM ABC WHERE uemail IN ( SELECT uemail FROM ABC GROUP BY uemail HAVING COUNT(...) -- 补全你的HAVING条件 );
改写方式2:用CTE先筛选再计数
WITH FilteredEmails AS ( SELECT uemail FROM ABC GROUP BY uemail HAVING COUNT(...) -- 补全HAVING条件 ), FilteredUsers AS ( SELECT DISTINCT u.uid FROM ABC u INNER JOIN FilteredEmails fe ON u.uemail = fe.uemail ) SELECT COUNT(uid) AS NOOFROWS FROM FilteredUsers;
这种改写可以让优化器先过滤出符合条件的uemail,再关联获取uid并去重,减少中间数据量。
4. 缺失合适的索引
如果ABC表没有针对uemail和uid的索引,连接和去重操作都会做全表扫描,性能自然差。建议创建复合索引:
CREATE NONCLUSTERED INDEX IX_ABC_uemail_uid ON ABC(uemail, uid);
这个索引可以让优化器快速找到匹配uemail的行,同时直接获取uid,不需要回表查询,大幅提升去重和计数的效率。
5. SQL Server 2016的兼容性或特定bug
极少数情况下,2016版本的优化器在处理DISTINCT+JOIN的组合时可能存在性能问题,或者数据库的兼容性级别设置影响了优化器行为。
- 检查兼容性级别:执行下面的语句查看当前数据库的兼容性级别:
SELECT name, compatibility_level FROM sys.databases WHERE name = '你的数据库名称';
如果是130(SQL Server 2016),可以尝试临时降到120(SQL Server 2014)看看是否解决:
ALTER DATABASE 你的数据库名称 SET COMPATIBILITY_LEVEL = 120;
注意:修改兼容性级别可能影响其他查询的性能,建议测试后再决定是否保留。
内容的提问来源于stack exchange,提问作者pise
相关产品推荐
相关产品推荐

