看执行计划→ 查找Table Scan、Clustered Index Scan大表、Key Lookup书签查找多、Spool假脱机SSMS 工具栏点显示估计执行计划或 CtrlL把鼠标悬停在每个运算符上看Actual Number of RowsvsEstimated Number of Rows执行计划读法执行计划读法从右到左、从上到下。读懂执行计划的技巧1. 看最右侧操作那是起点2. 看开销占比 → 最耗时的节点就是瓶颈如果一个节点占90%其他地方就不用看了优化它。如果多个节点均匀分布可能整体性能没问题对于90% 的节点必须悬浮查看关键就在那几行数字里。你这步悬浮已经暴露了最核心的证据指标值含义要读取的预计行数5,465,660全表 546 万行全部扫了一遍过滤后返回行数891但只要 891 行筛选谓词Effective_IsEffective1ANDMainItemCategoryCodeNA04060204101过滤条件写在 Predicate 里关键信号546万 vs 891 行差了6000 倍。而且谓词出现在Predicate扫描后过滤而不是Seek Predicate扫描前定位说明没有索引支持这两个条件的查找。需要重点看的几个属性按优先级要读取的行数 vs 返回的行数— 差越大问题越严重Predicate谓语— 扫描完成后才过滤说明缺索引Predicate谓语就是过滤条件换成 SQL 里的WHERE。Seek Predicate查找谓语— 如果有说明索引用得对Ordered—False表示无序扫描True表示有序读取排序需要存储类型— RowStore / ColumnStore确认是否用对了存储模式结论建议在CBO_ItemMasterGuangDu上建索引解决当前慢查询核心就是让CBO_ItemMasterGuangDu不再扫 546 万行而是按条件直接定位到那 891 行。你这个执行计划里的过滤条件是G.Effective_IsEffective 1 G.MainItemCategoryCode NA04060204101还关联了G.Org Base_Organization.ID所以建议建一个复合索引CREATE NONCLUSTERED INDEX IX_CBO_ItemMasterGuangDu_Category_Effective_Org ON dbo.CBO_ItemMasterGuangDu ( MainItemCategoryCode, Effective_IsEffective, Org ) INCLUDE ( Id, Code, Name, DescFlexField_PrivateDescSeg4, DescFlexField_PrivateDescSeg5 );建完后重新执行查询看执行计划是否从Clustered Index Scan变成Index Seek同时看悬浮信息里要读取的预计行数5465660是否降到接近返回行数891另外两个注意点不要直接复制 SSMS 缺失索引提示的名字它会生成很长、很丑的名字建议用上面这种有意义的索引名。不要SELECT *只查实际需要的列。SELECT *会让 SQL Server 觉得索引不够覆盖可能又回去扫聚集索引。如果这个查询就是包装机里查光度档案的核心查询这个索引基本就是当前慢点的第一优先级处理。优化后对比这份新执行计划说明索引已经被用上了之前的 546 万行全表扫描问题基本解决了。现在读法是Base_Organization Index Seek 33% ↓ Top 0% CBO_ItemMasterGuangDu Index Seek 58% ↓ Nested Loops Inner Join 9% ↓ SELECT核心变化是这一条CBO_ItemMasterGuangDuClustered Index Scan 99% 变成 CBO_ItemMasterGuangDuIndex Seek 58%这说明 SQL Server 现在不再全表扫CBO_ItemMasterGuangDu而是通过非聚集索引直接定位数据。这是正确方向。但你看到Index Seek还有 58%不代表它仍然有大问题。执行计划里的百分比是当前查询内部的相对占比节点含义Base_Organization Index Seek 33%查组织表也用了索引CBO_ItemMasterGuangDu Index Seek 58%查光度表也用了索引是当前主要工作量Nested Loops 9%两边结果做连接因为这个查询只剩几个节点了光度表查找自然占比最高。你要判断是否还慢不能只看 58%要悬浮看这些值要看什么判断标准Actual Number of Rows/ 实际行数返回多少行Number of Rows Read/ 实际读取行数读取行数是否接近返回行数Seek Predicate过滤条件是否出现在这里Predicate如果还有很多条件在这里说明仍有扫描后过滤Key Lookup是否出现如果出现且次数多说明索引没覆盖列理想状态大概是Index Seek Seek Predicate: MainItemCategoryCode A04060204101 Effective_IsEffective 1 Org xxx Rows Read ≈ Rows Returned如果悬浮后发现读取 900 行返回 891 行那就已经很好。如果发现读取 100000 行返回 891 行那说明索引虽然用了但列顺序或包含列还不够理想需要继续调索引。这是悬浮显示的结果这个结果已经很理想了。关键证据有三条项当前值说明运算符Index Seek已经不是全表扫描命中索引IX_CBO_ItemMasterGuangDu_Category_Effective_Org用到了你为这个查询建的索引查找谓词3 个联合索引键都命中MainItemCategoryCode Effective_IsEffective Org全部用于定位最重要的是这里Seek Predicates: MainItemCategoryCode NA04060204101 Effective_IsEffective 1 Org Base_Organization.ID这说明 SQL Server 不是先读一堆数据再过滤而是直接按这三个条件去索引里找。再看行数要读取的预计行数213.108 估计返回行数213.108读取行数 ≈ 返回行数说明没有明显浪费读取。你现在看到58%不需要担心因为它只是当前这个很小查询里的相对比例。真实开销只有估计运算符开销0.0057386之前是48.5317粗略看单这个节点估计成本从48.5降到0.0057差不多是8000 多倍级别的下降。结论这个索引对当前查询是有效的当前慢点已经解决。下一步如果还感觉慢要看客户端、网络、返回数据量、或者其他 SQL。看缺失索引提示→ 执行计划里绿色文字 Missing Index 直接复制创建看表连接顺序→ SQL Server 会自动优化但大表放左边、小表放右边也有帮助Hash Match→ 通常意味着表大且无索引需要建索引Nested Loops→ 适合小表驱动大表但如果外层是大表则慢Merge Join→ 适合两个有序的大数据集检查 WHERE 条件是否 SARGable❌WHERE YEAR(Col) 2026→ 不走索引✅WHERE Col 2026-01-01 AND Col 2027-01-01❌WHERE LEFT(Code, 3) ABC✅WHERE Code LIKE ABC%检查隐式转换→ 执行计划找黄色的CONVERT_IMPLICIT警告例如WHERE VarcharCol 123→ SQL Server 要把全表每行转类型再比较统计信息是否过期→DBCC SHOW_STATISTICS(表名, 索引名)看Rows Sampled如果采样比例很低或Updated日期太久远 →UPDATE STATISTICS 表名检查数据量级→SELECT COUNT(*)每个参与表确认哪张是大表SET STATISTICS IO ON看logical reads大量读取说明索引没用到或扫描太多总结慢查询主要是数据库层面的优化吗不完全是。慢查询首先要从数据库层面查因为数据库通常是最大头但最终要按链路拆。常见来源大概分 5 层层级常见问题怎么判断数据库执行缺索引、全表扫描、统计信息过期、排序/哈希溢出看执行计划、STATISTICS IO/TIMESQL 写法SELECT *、函数包列、隐式转换、子查询重复执行、返回太多列看 SQL 本身和执行计划数据量一次查太多行、分页不合理、无条件查询大表看返回行数、网络传输量应用层循环查库、N1 查询、DataTable 处理慢、UI 一次加载太多看代码、日志耗时网络/服务器跨公网数据库、带宽低、SQL Server 资源紧张看实际耗时 vs 数据库执行耗时你刚才这个案例属于数据库层面CBO_ItemMasterGuangDu原来扫 546 万行建索引后变成Index Seek这就是典型数据库索引优化。但包装机项目还可能有应用层慢点比如foreach (...) { DBHelper.GetListBycInvCCode(...); // 每个分类查一次 }这种就算单条 SQL 很快循环 100 次也会慢。判断方法很简单SET STATISTICS IO ON; SET STATISTICS TIME ON; -- 你的SQL如果 SQL Server 显示执行只花几十毫秒但程序界面卡几秒那问题多半在应用层、网络传输或 UI 渲染。
SQLServer多表关联慢查询排查思路
看执行计划→ 查找Table Scan、Clustered Index Scan大表、Key Lookup书签查找多、Spool假脱机SSMS 工具栏点显示估计执行计划或 CtrlL把鼠标悬停在每个运算符上看Actual Number of RowsvsEstimated Number of Rows执行计划读法执行计划读法从右到左、从上到下。读懂执行计划的技巧1. 看最右侧操作那是起点2. 看开销占比 → 最耗时的节点就是瓶颈如果一个节点占90%其他地方就不用看了优化它。如果多个节点均匀分布可能整体性能没问题对于90% 的节点必须悬浮查看关键就在那几行数字里。你这步悬浮已经暴露了最核心的证据指标值含义要读取的预计行数5,465,660全表 546 万行全部扫了一遍过滤后返回行数891但只要 891 行筛选谓词Effective_IsEffective1ANDMainItemCategoryCodeNA04060204101过滤条件写在 Predicate 里关键信号546万 vs 891 行差了6000 倍。而且谓词出现在Predicate扫描后过滤而不是Seek Predicate扫描前定位说明没有索引支持这两个条件的查找。需要重点看的几个属性按优先级要读取的行数 vs 返回的行数— 差越大问题越严重Predicate谓语— 扫描完成后才过滤说明缺索引Predicate谓语就是过滤条件换成 SQL 里的WHERE。Seek Predicate查找谓语— 如果有说明索引用得对Ordered—False表示无序扫描True表示有序读取排序需要存储类型— RowStore / ColumnStore确认是否用对了存储模式结论建议在CBO_ItemMasterGuangDu上建索引解决当前慢查询核心就是让CBO_ItemMasterGuangDu不再扫 546 万行而是按条件直接定位到那 891 行。你这个执行计划里的过滤条件是G.Effective_IsEffective 1 G.MainItemCategoryCode NA04060204101还关联了G.Org Base_Organization.ID所以建议建一个复合索引CREATE NONCLUSTERED INDEX IX_CBO_ItemMasterGuangDu_Category_Effective_Org ON dbo.CBO_ItemMasterGuangDu ( MainItemCategoryCode, Effective_IsEffective, Org ) INCLUDE ( Id, Code, Name, DescFlexField_PrivateDescSeg4, DescFlexField_PrivateDescSeg5 );建完后重新执行查询看执行计划是否从Clustered Index Scan变成Index Seek同时看悬浮信息里要读取的预计行数5465660是否降到接近返回行数891另外两个注意点不要直接复制 SSMS 缺失索引提示的名字它会生成很长、很丑的名字建议用上面这种有意义的索引名。不要SELECT *只查实际需要的列。SELECT *会让 SQL Server 觉得索引不够覆盖可能又回去扫聚集索引。如果这个查询就是包装机里查光度档案的核心查询这个索引基本就是当前慢点的第一优先级处理。优化后对比这份新执行计划说明索引已经被用上了之前的 546 万行全表扫描问题基本解决了。现在读法是Base_Organization Index Seek 33% ↓ Top 0% CBO_ItemMasterGuangDu Index Seek 58% ↓ Nested Loops Inner Join 9% ↓ SELECT核心变化是这一条CBO_ItemMasterGuangDuClustered Index Scan 99% 变成 CBO_ItemMasterGuangDuIndex Seek 58%这说明 SQL Server 现在不再全表扫CBO_ItemMasterGuangDu而是通过非聚集索引直接定位数据。这是正确方向。但你看到Index Seek还有 58%不代表它仍然有大问题。执行计划里的百分比是当前查询内部的相对占比节点含义Base_Organization Index Seek 33%查组织表也用了索引CBO_ItemMasterGuangDu Index Seek 58%查光度表也用了索引是当前主要工作量Nested Loops 9%两边结果做连接因为这个查询只剩几个节点了光度表查找自然占比最高。你要判断是否还慢不能只看 58%要悬浮看这些值要看什么判断标准Actual Number of Rows/ 实际行数返回多少行Number of Rows Read/ 实际读取行数读取行数是否接近返回行数Seek Predicate过滤条件是否出现在这里Predicate如果还有很多条件在这里说明仍有扫描后过滤Key Lookup是否出现如果出现且次数多说明索引没覆盖列理想状态大概是Index Seek Seek Predicate: MainItemCategoryCode A04060204101 Effective_IsEffective 1 Org xxx Rows Read ≈ Rows Returned如果悬浮后发现读取 900 行返回 891 行那就已经很好。如果发现读取 100000 行返回 891 行那说明索引虽然用了但列顺序或包含列还不够理想需要继续调索引。这是悬浮显示的结果这个结果已经很理想了。关键证据有三条项当前值说明运算符Index Seek已经不是全表扫描命中索引IX_CBO_ItemMasterGuangDu_Category_Effective_Org用到了你为这个查询建的索引查找谓词3 个联合索引键都命中MainItemCategoryCode Effective_IsEffective Org全部用于定位最重要的是这里Seek Predicates: MainItemCategoryCode NA04060204101 Effective_IsEffective 1 Org Base_Organization.ID这说明 SQL Server 不是先读一堆数据再过滤而是直接按这三个条件去索引里找。再看行数要读取的预计行数213.108 估计返回行数213.108读取行数 ≈ 返回行数说明没有明显浪费读取。你现在看到58%不需要担心因为它只是当前这个很小查询里的相对比例。真实开销只有估计运算符开销0.0057386之前是48.5317粗略看单这个节点估计成本从48.5降到0.0057差不多是8000 多倍级别的下降。结论这个索引对当前查询是有效的当前慢点已经解决。下一步如果还感觉慢要看客户端、网络、返回数据量、或者其他 SQL。看缺失索引提示→ 执行计划里绿色文字 Missing Index 直接复制创建看表连接顺序→ SQL Server 会自动优化但大表放左边、小表放右边也有帮助Hash Match→ 通常意味着表大且无索引需要建索引Nested Loops→ 适合小表驱动大表但如果外层是大表则慢Merge Join→ 适合两个有序的大数据集检查 WHERE 条件是否 SARGable❌WHERE YEAR(Col) 2026→ 不走索引✅WHERE Col 2026-01-01 AND Col 2027-01-01❌WHERE LEFT(Code, 3) ABC✅WHERE Code LIKE ABC%检查隐式转换→ 执行计划找黄色的CONVERT_IMPLICIT警告例如WHERE VarcharCol 123→ SQL Server 要把全表每行转类型再比较统计信息是否过期→DBCC SHOW_STATISTICS(表名, 索引名)看Rows Sampled如果采样比例很低或Updated日期太久远 →UPDATE STATISTICS 表名检查数据量级→SELECT COUNT(*)每个参与表确认哪张是大表SET STATISTICS IO ON看logical reads大量读取说明索引没用到或扫描太多总结慢查询主要是数据库层面的优化吗不完全是。慢查询首先要从数据库层面查因为数据库通常是最大头但最终要按链路拆。常见来源大概分 5 层层级常见问题怎么判断数据库执行缺索引、全表扫描、统计信息过期、排序/哈希溢出看执行计划、STATISTICS IO/TIMESQL 写法SELECT *、函数包列、隐式转换、子查询重复执行、返回太多列看 SQL 本身和执行计划数据量一次查太多行、分页不合理、无条件查询大表看返回行数、网络传输量应用层循环查库、N1 查询、DataTable 处理慢、UI 一次加载太多看代码、日志耗时网络/服务器跨公网数据库、带宽低、SQL Server 资源紧张看实际耗时 vs 数据库执行耗时你刚才这个案例属于数据库层面CBO_ItemMasterGuangDu原来扫 546 万行建索引后变成Index Seek这就是典型数据库索引优化。但包装机项目还可能有应用层慢点比如foreach (...) { DBHelper.GetListBycInvCCode(...); // 每个分类查一次 }这种就算单条 SQL 很快循环 100 次也会慢。判断方法很简单SET STATISTICS IO ON; SET STATISTICS TIME ON; -- 你的SQL如果 SQL Server 显示执行只花几十毫秒但程序界面卡几秒那问题多半在应用层、网络传输或 UI 渲染。