SQLServer教程FG014-SQLServer执行计划分析实战
目录大纲
内容简介
本文档基于SQLServer官方文档的执行计划内容,结合生产环境实际情况,详细讲解SQLServer执行计划的获取方式、分析方法、常见运算符以及优化技巧等内容。风哥教程参考SQLServer官方文档Execution Plans、Query Tuning等相关章节。
Part01-基础概念与理论知识
1.1 SQLServer执行计划概念
SQLServer执行计划是查询优化器为SQL语句生成的执行路线图,描述了SQLServer如何获取数据。执行计划包含以下关键信息:
- 数据访问方式:表扫描、索引扫描、索引查找
- 连接方式:嵌套循环、哈希连接、合并连接
- 排序和聚合操作
- 预估成本和行数
更多视频教程www.fgedu.net.cn
1.2 SQLServer执行计划类型
SQLServer支持三种执行计划类型:
- 估计执行计划:不执行查询,仅显示优化器预估的执行计划
- 实际执行计划:执行查询后显示实际的执行计划,包含运行时统计信息
- 实时查询统计信息:实时显示查询执行进度
学习交流加群风哥微信: itpux-com
1.3 SQLServer执行计划运算符
SQLServer执行计划常见运算符:
- Table Scan:表扫描,扫描整个表
- Index Scan:索引扫描,扫描整个索引
- Index Seek:索引查找,使用索引定位数据
- Key Lookup:键值查找,通过聚集索引获取数据
- Nested Loops:嵌套循环连接
- Hash Match:哈希连接
- Merge Join:合并连接
- Sort:排序操作
- Filter:过滤操作
学习交流加群风哥QQ113257174
Part02-生产环境规划与建议
2.1 SQLServer执行计划获取方式
SQLServer执行计划获取方式:
- SSMS图形界面:使用”显示估计执行计划”或”包括实际执行计划”
- T-SQL命令:使用SET SHOWPLAN_TEXT、SET SHOWPLAN_XML等
- 动态管理视图:sys.dm_exec_query_plan、sys.dm_exec_cached_plans
- SQL Server Profiler:捕获执行计划事件
风哥提示:生产环境建议使用动态管理视图获取执行计划,避免影响性能
2.2 SQLServer执行计划分析策略
执行计划分析策略:
- 从右向左、从上向下阅读执行计划
- 关注高成本操作(粗箭头表示大量数据流)
- 检查是否有表扫描或索引扫描
- 分析连接方式是否合适
- 检查预估行数与实际行数差异
更多学习教程公众号风哥教程itpux_com
2.3 SQLServer执行计划监控建议
执行计划监控建议:
- 定期检查缓存的执行计划
- 监控执行计划重编译
- 收集执行计划统计信息
- 建立执行计划基线
from SQLServer视频:www.itpux.com
Part03-生产环境项目实施方案
3.1 SQLServer图形化执行计划
使用SSMS获取图形化执行计划:
USE fgedudb;
GO
CREATE TABLE fgedu.orders (
order_id INT IDENTITY(1,1) PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATETIME DEFAULT GETDATE(),
amount DECIMAL(10,2),
status VARCHAR(20)
);
GO
— 创建索引
CREATE INDEX idx_customer_id ON fgedu.orders(customer_id);
CREATE INDEX idx_order_date ON fgedu.orders(order_date);
GO
— 插入测试数据
INSERT INTO fgedu.orders (customer_id, order_date, amount, status)
SELECT
ABS(CHECKSUM(NEWID())) % 1000 + 1,
DATEADD(DAY, -ABS(CHECKSUM(NEWID())) % 365, GETDATE()),
ABS(CHECKSUM(NEWID())) % 10000 + 100,
CASE ABS(CHECKSUM(NEWID())) % 3 WHEN 0 THEN ‘PENDING’ WHEN 1 THEN ‘SHIPPED’ ELSE ‘COMPLETED’ END
FROM sys.objects a CROSS JOIN sys.objects b;
GO
执行结果:
Command(s) completed successfully.
(10000 rows affected)
3.2 SQLServer文本执行计划
使用T-SQL获取文本执行计划:
SET SHOWPLAN_TEXT ON;
GO
— 执行查询
SELECT order_id, customer_id, amount
FROM fgedu.orders
WHERE customer_id = 100;
GO
— 关闭文本执行计划
SET SHOWPLAN_TEXT OFF;
GO
执行结果:
—————————————————————-
SELECT order_id, customer_id, amount
FROM fgedu.orders
WHERE customer_id = 100;
|–Index Seek(OBJECT:([fgedudb].[dbo].[fgedu_orders].[idx_customer_id]), SEEK:([customer_id]=(100)) ORDERED FORWARD)
(1 row affected)
3.3 SQLServer XML执行计划
使用T-SQL获取XML执行计划:
SET SHOWPLAN_XML ON;
GO
— 执行查询
SELECT o.order_id, c.customer_name, o.amount
FROM fgedu.orders o
INNER JOIN fgedu.customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= ‘2025-01-01’
ORDER BY o.amount DESC;
GO
— 关闭XML执行计划
SET SHOWPLAN_XML OFF;
GO
— 从缓存获取执行计划
SELECT
cp.plan_handle,
qp.query_plan,
st.text
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) qp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
WHERE st.text LIKE ‘%fgedu.orders%’;
GO
执行结果:
<BatchSequence>
<Batch>
<Statements>
<StmtSimple StatementText=”SELECT o.order_id, c.customer_name, o.amount…” StatementId=”1″>
<QueryPlan CachedPlanSize=”48″ CompileTime=”8″ CompileCPU=”8″ CompileMemory=”496″>
<RelOp NodeId=”0″ PhysicalOp=”Sort” LogicalOp=”Sort” EstimateRows=”1000″>
</RelOp>
</QueryPlan>
</StmtSimple>
</Statements>
</Batch>
</BatchSequence>
</ShowPlanXML>
plan_handle query_plan text
—————————————————– —————————————————- —————————————————-
0x06000100A1B2C3D4E5F6070809 <ShowPlanXML…> SELECT o.order_id, c.customer_name…
Part04-生产案例与实战讲解
4.1 SQLServer索引扫描优化案例
问题:查询使用了索引扫描而非索引查找
SELECT order_id, customer_id, amount, status
FROM fgedu.orders
WHERE customer_id + 1 = 101;
GO
— 查看执行计划
SET STATISTICS PROFILE ON;
GO
SELECT order_id, customer_id, amount, status
FROM fgedu.orders
WHERE customer_id + 1 = 101;
GO
SET STATISTICS PROFILE OFF;
GO
执行结果:
———– ———– —————————————————-
1 1 SELECT order_id, customer_id, amount, status FROM fgedu.orders WHERE customer_id + 1 = 101
10000 1 |–Table Scan(OBJECT:([fgedudb].[dbo].[fgedu_orders]))
(2 rows affected)
优化方案:避免在索引列上使用函数
SELECT order_id, customer_id, amount, status
FROM fgedu.orders
WHERE customer_id = 100;
GO
— 查看优化后的执行计划
SET STATISTICS PROFILE ON;
GO
SELECT order_id, customer_id, amount, status
FROM fgedu.orders
WHERE customer_id = 100;
GO
SET STATISTICS PROFILE OFF;
GO
执行结果:
———– ———– —————————————————-
12 1 SELECT order_id, customer_id, amount, status FROM fgedu.orders WHERE customer_id = 100
12 1 |–Index Seek(OBJECT:([fgedudb].[dbo].[fgedu_orders].[idx_customer_id]), SEEK:([customer_id]=(100)) ORDERED FORWARD)
(2 rows affected)
4.2 SQLServer连接查询优化案例
问题:连接查询性能较差
CREATE TABLE fgedu.customers (
customer_id INT PRIMARY KEY,
customer_name VARCHAR(100),
city VARCHAR(50),
create_date DATETIME DEFAULT GETDATE()
);
GO
— 插入测试数据
INSERT INTO fgedu.customers (customer_id, customer_name, city)
SELECT
number,
‘Customer_’ + CAST(number AS VARCHAR(10)),
CASE number % 5 WHEN 0 THEN ‘Beijing’ WHEN 1 THEN ‘Shanghai’ WHEN 2 THEN ‘Guangzhou’ WHEN 3 THEN ‘Shenzhen’ ELSE ‘Hangzhou’ END
FROM master..spt_values WHERE type = ‘P’ AND number BETWEEN 1 AND 1000;
GO
— 问题查询:缺少索引的连接
SELECT o.order_id, c.customer_name, o.amount
FROM fgedu.orders o
INNER JOIN fgedu.customers c ON o.customer_id = c.customer_id
WHERE c.city = ‘Beijing’;
GO
— 查看执行计划
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO
SELECT o.order_id, c.customer_name, o.amount
FROM fgedu.orders o
INNER JOIN fgedu.customers c ON o.customer_id = c.customer_id
WHERE c.city = ‘Beijing’;
GO
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
GO
执行结果:
CPU time = 15 ms, elapsed time = 12 ms.
Table ‘fgedu_orders’. Scan count 1, logical reads 85, physical reads 0.
Table ‘fgedu_customers’. Scan count 1, logical reads 8, physical reads 0.
order_id customer_name amount
———– —————– ———–
1 Customer_100 5234.00
15 Customer_100 3891.00
…
优化方案:添加合适的索引
CREATE INDEX idx_customers_city ON fgedu.customers(city) INCLUDE (customer_name);
GO
— 优化后的查询
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO
SELECT o.order_id, c.customer_name, o.amount
FROM fgedu.orders o
INNER JOIN fgedu.customers c ON o.customer_id = c.customer_id
WHERE c.city = ‘Beijing’;
GO
SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
GO
执行结果:
CPU time = 0 ms, elapsed time = 2 ms.
Table ‘fgedu_orders’. Scan count 1, logical reads 42, physical reads 0.
Table ‘fgedu_customers’. Scan count 1, logical reads 4, physical reads 0.
order_id customer_name amount
———– —————– ———–
1 Customer_100 5234.00
15 Customer_100 3891.00
…
4.3 SQLServer复杂查询优化案例
问题:复杂查询使用临时表优化
SELECT
c.customer_name,
COUNT(*) as order_count,
SUM(o.amount) as total_amount,
AVG(o.amount) as avg_amount
FROM fgedu.orders o
INNER JOIN fgedu.customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= ‘2025-01-01’
GROUP BY c.customer_name
HAVING SUM(o.amount) > 5000
ORDER BY total_amount DESC;
GO
— 查看执行计划
SET STATISTICS XML ON;
GO
SELECT
c.customer_name,
COUNT(*) as order_count,
SUM(o.amount) as total_amount,
AVG(o.amount) as avg_amount
FROM fgedu.orders o
INNER JOIN fgedu.customers c ON o.customer_id = c.customer_id
WHERE o.order_date >= ‘2025-01-01’
GROUP BY c.customer_name
HAVING SUM(o.amount) > 5000
ORDER BY total_amount DESC;
GO
SET STATISTICS XML OFF;
GO
执行结果:
—————– ———– ————– ————
Customer_100 8 45234.00 5654.25
Customer_250 6 38912.00 6485.33
Customer_500 5 35678.00 7135.60
…
(150 rows affected)
Part05-风哥经验总结与分享
5.1 SQLServer执行计划分析最佳实践
- 从右向左、从上向下阅读执行计划
- 关注高成本操作和粗箭头
- 检查表扫描和索引扫描
- 分析连接方式选择是否合理
- 对比预估行数与实际行数
5.2 SQLServer性能问题定位技巧
- 使用SET STATISTICS IO/TIME分析IO和CPU消耗
- 检查缺失索引建议
- 分析参数嗅探问题
- 监控执行计划缓存和重用
- 使用Query Store跟踪执行计划变化
5.3 SQLServer执行计划常见问题
- 索引扫描替代索引查找:检查查询条件是否使用索引
- 键值查找过多:考虑使用覆盖索引
- 哈希连接替代嵌套循环:检查连接条件和统计信息
- 排序操作过多:检查是否可以在索引中避免排序
- 参数嗅探:使用OPTION(RECOMPILE)或OPTIMIZE FOR
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
