1. 首页 > SQLServer教程 > 正文

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.

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

执行结果:

StmtText
—————————————————————-
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执行计划:

— 启用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

执行结果:

<ShowPlanXML xmlns=”http://schemas.microsoft.com/sqlserver/2004/07/showplan” Version=”1.6″ Build=”16.0.1000.6″>
<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

执行结果:

Rows Executes StmtText
———– ———– —————————————————-
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

执行结果:

Rows Executes StmtText
———– ———– —————————————————-
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

执行结果:

SQL Server Execution Times:
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

执行结果:

SQL Server Execution Times:
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_name order_count total_amount avg_amount
—————– ———– ————– ————
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

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息