sql server多表join基数估计偏差源于selinger模型假设列独立且分布均匀,无法处理数据倾斜;需检查并补充联合统计信息、升级兼容级别至150启用新版ce、强制更新统计,并在严重失准时采用物化中间结果或应用层拆分。

为什么多表JOIN的基数估计总是偏小或偏大
SQL Server 2019 的查询优化器默认仍基于 Selinger 模型做多表连接基数估计,它强行假设各列独立、连接键值均匀分布。一旦真实数据存在倾斜(比如 CustomerID 在订单表中大量重复,而客户表里只有少量活跃客户),WHERE 条件 + 多层 INNER JOIN 就会叠加误差——两表估计偏差 30%,三表后可能放大到 5× 甚至 10×。这不是 bug,是模型本身的局限。
检查当前统计信息是否覆盖了 JOIN 列和过滤列
基数不准的第一诱因,往往是关键列上压根没统计信息,或者只在单列建了统计,却没覆盖组合条件。比如查询是 ON a.id = b.a_id AND b.status = 'active',但 b.a_id 和 b.status 各自都有统计,合起来却没有。
- 用
DBCC SHOW_STATISTICS('Orders', 'IX_Orders_CustomerID')查看直方图步数(Steps)是否 ≥ 200;低于 100 时,对倾斜数据基本失效 - 运行
SELECT name, auto_created, user_created FROM sys.stats WHERE object_id = OBJECT_ID('Orders'),确认CustomerID和Status是否有联合统计(名字含多个列名,如_WA_Sys__CustomerID_Status_...) - 没有联合统计?别依赖 AUTO_CREATE_STATISTICS —— 它只建单列统计。手动补:
CREATE STATISTICS stats_Orders_CustID_Status ON Orders(CustomerID, Status) WITH FULLSCAN
强制更新统计信息并禁用过时的 CE 版本
SQL Server 2019 默认兼容级别可能是 140(对应 SQL Server 2017),它用的是旧版基数估计器(CE v140),对多表 JOIN 更保守、更易低估。即使统计信息全新,CE 模型本身也拉胯。
- 先查当前兼容级别:
SELECT compatibility_level FROM sys.databases WHERE name = DB_NAME() - 升级到 150:
ALTER DATABASE CURRENT SET COMPATIBILITY_LEVEL = 150(启用新版 CE,对连接键相关性有一定建模能力) - 再全量更新:
EXEC sp_updatestats或对大表单独跑UPDATE STATISTICS Orders WITH FULLSCAN, COLUMNS(注意:FULLSCAN比SAMPLE准,但锁表时间长) - 避免
sp_updatestats跳过已“足够新”的统计——它可能漏掉刚插入的倾斜数据,务必加WITH RESAMPLE
当统计+CE都救不了时,用查询提示或物理拆分
如果执行计划里已经出现哈希匹配(Hash Match)但预估行数是 1,实际扫了 50 万行,说明优化器彻底失焦。这时硬调参数比等模型修复更快。
- 用
OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'))临时切回旧 CE 对比——有时旧版对特定倾斜模式反而更稳 - 对关键中间结果,用
SELECT ... INTO #tmp_orders FROM Orders WHERE Status = 'active'先物化,再 JOIN;#tmp_orders会自动建统计,且行数精准 - 慎用
OPTION (HASH JOIN)或OPTION (LOOP JOIN)—— 它们不改基数估计,只换连接算法;但如果估计值错得离谱,换算法反而让错误固化
真正难啃的是跨 4 张以上表、带非等值 JOIN 或复杂 CASE 过滤的场景——这时候统计信息和 CE 都不是瓶颈,是模型表达能力到了极限。必须把逻辑拆到应用层或用视图预聚合,别指望一条 SQL 自动变聪明。











