SQL Server 性能优化实战案例

目录

1.  隐式类型转换陷阱

案例 1:数据类型不匹配导致的索引失效

错误场景

优化方案

2. 索引列函数运算灾难

案例 2:函数操作导致索引失效

错误场景

优化方案

3.  联合索引最左匹配原则

案例 3:索引结构设计与查询优化

错误场景

优化策略

4. LIKE 通配符的致命影响

案例 4:通配符位置对索引使用的影响

性能对比分析

优化建议​​​​​​​

5.  OR 条件的性能黑洞

案例 5:OR 连接不同列导致的性能问题

错误场景​​​​​​​

优化方案​​​​​​​

6. 负向查询的灾难性影响

案例 6:NOT IN 导致的性能问题

错误场景​​​​​​​

优化方案​​​​​​​

7.  NULL 值判断的索引失效

案例 7:IS NULL 查询优化策略

错误场景​​​​​​​

优化方案​​​​​​​

8.  排序与分页的性能陷阱

案例 8:大偏移量分页查询优化

错误场景​​​​​​​

优化方案​​​​​​​

9. 未优化的连接顺序

案例 9:小表驱动大表的连接优化

错误场景​​​​​​​

优化方案​​​​​​​

10. 参数嗅探导致的性能波动

案例 10:存储过程执行计划优化

错误场景​​​​​​​

优化方案​​​​​​​

11.  临时表滥用导致性能下降

案例 11:CTE 替代临时表的最佳实践

错误场景​​​​​​​

优化方案​​​​​​​

12.  游标滥用导致效率低下

案例 12:集合操作替代游标方案

错误场景​​​​​​​

优化方案​​​​​​​

13.  未利用覆盖索引导致回表

案例 13:避免 Key Lookup 的性能优化

错误场景​​​​​​​

优化方案​​​​​​​

14.  索引碎片导致性能下降

案例 14:索引维护策略优化

错误场景​​​​​​​

优化方案​​​​​​​

15.  忽略统计信息导致错误执行计划

案例 15:统计信息过时的性能影响

错误场景​​​​​​​

优化方案​​​​​​​

16.  未优化的子查询

案例 16:相关子查询的性能陷阱

错误场景​​​​​​​

优化方案​​​​​​​

17.   未考虑事务隔离级别

案例 17:隔离级别选择与数据一致性

错误场景​​​​​​​

优化方案​​​​​​​

18.  未优化的动态 SQL

案例 18:参数化查询避免 SQL 注入

错误场景​​​​​​​

优化方案​​​​​​​

19.  未优化的字符串拼接

案例 19:动态 SQL 硬编码值问题

错误场景​​​​​​​

优化方案​​​​​​​

10.  Link Server 跨服务器执行计划陷阱

案例 20:Link Server 的执行计划陷阱

错误场景​​​​​​​

优化方案​​​​​​​

21.   数据类型性能分析与选择

数据类型性能排序

优化建议

22.  性能优化最佳实践总结

索引设计原则

查询优化技巧

日常维护策略

23. 常见问题与解决方案

24.  如何抓取异常 SQL

1. 最消耗磁盘IO的SQL语句​​​​​​​

2. 最消耗时间的SQL语句​​​​​​​

3. 最消耗CPU的SQL语句​​​​​​​


1.  隐式类型转换陷阱

案例 1:数据类型不匹配导致的索引失效

错误场景
-- Mobile 为 varchar 类型,但查询值用数字
SELECT * FROM User_info
WHERE Mobile = 13888888888;

问题分析:当字段定义为字符类型(varchar)但使用数字值查询时,SQL Server 会进行隐式类型转换,导致无法使用索引。

优化方案
-- 显式类型匹配(推荐)
SELECT * FROM User_info
WHERE Mobile = '13888888888';

-- 参数化查询(最佳实践)
DECLARE @Mobile VARCHAR(20) = '13888888888';
SELECT * FROM Users
WHERE Mobile = @Mobile;

优化效果:避免隐式转换后,查询从索引扫描(Index Scan)变为索引查找(Index Seek),性能提升 10 倍以上,扫描行数从百万级降至个位数。

2. 索引列函数运算灾难

案例 2:函数操作导致索引失效

错误场景
-- 对 CreateTime 列使用 YEAR 函数
SELECT * FROM Order_main
WHERE YEAR(CreateTime) = 2025;

-- 对索引列 ContactMobile 使用函数
SELECT OrderID FROM Order_main
WHERE LEFT(ContactMobile, 3) = '138';

查询方式

执行计划

扫描行数

性能影响

原始查询

Clustered Index Scan

1,200 万

耗时 2.8 秒

优化后

Index Seek + Key Lookup

500

耗时 0.02 秒

优化方案
-- 改用范围查询(覆盖索引更佳)
SELECT OrderID FROM Order_main
WHERE CreateTime >= '2025-01-01'
  AND CreateTime < '2026-01-01';

-- 强制使用特定索引(谨慎使用)
SELECT OrderID FROM Order_main
WITH(INDEX(idx_CreateTime))
WHERE CreateTime >= '2025-01-01'
  AND CreateTime < '2026-01-01';

优化效果:避免在索引列上使用函数后,查询从全表扫描变为索引查找,扫描行数从 1200 万降至 500 行,性能提升 140 倍。

3.  联合索引最左匹配原则

案例 3:索引结构设计与查询优化

错误场景

索引结构:(HotelCd, OrderID)

  • 索引按 HotelCd 排序

  • 相同 HotelCd 内按 OrderID 排序

  • 跳过 HotelCd 直接查询 OrderID 无法使用索引

-- 错误查询:跳过最左列 HotelCd
SELECT * FROM Order_main
WHERE OrderID = 'R202507017890888';

查询方式

执行计划

扫描行数

跳过最左列

Clustered Index Scan

全表扫描

使用完整索引

Index Seek

1 行

优化策略​​​​​​​
-- 方案 1:补充最左列条件
SELECT * FROM Order_main
WHERE hotelCDD = '021040'
  AND OrderID = 'R202507017890888';

-- 方案 2:创建包含性索引
CREATE INDEX idx_order_main_orderID
  ON Order_main(OrderID)
  INCLUDE (ContactMobile, OrderDate);

优化效果:补充最左列条件后查询从全表扫描变为索引查找,扫描行数从百万级降至个位数。包含性索引可避免回表操作,提升查询性能 30%。

4. LIKE 通配符的致命影响

案例 4:通配符位置对索引使用的影响

性能对比分析

查询方式

执行计划

扫描行数占比

索引使用

LIKE '%138%'

Clustered Index Scan

100%

无法使用索引

LIKE '138%'

Index Seek + Key Lookup

0.5%

B 树索引

CONTAINS(ContactMobile, '"138*"')

Full-Text Search

0.01%

全文索引

关键发现:前置百分号通配符导致索引完全失效,改为后缀匹配或使用全文索引可大幅提升性能。

优化建议​​​​​​​
-- 前缀匹配(使用 B 树索引)
SELECT * FROM Customer
WHERE ContactMobile LIKE '138%';

-- 复杂模式匹配(使用全文索引)
SELECT * FROM Customer
WHERE CONTAINS(ContactMobile, '"138*"');

-- 全文索引配置(需提前设置)
CREATE FULLTEXT CATALOG ft_catalog;
CREATE FULLTEXT INDEX ON Customer(ContactMobile)
  KEY INDEX PK_CustomerID;

优化效果:使用前缀匹配扫描行数降至 0.5%,全文索引进一步降至 0.01%,查询性能提升 100 倍以上。

5.  OR 条件的性能黑洞

案例 5:OR 连接不同列导致的性能问题

错误场景​​​​​​​
-- 当 ContactMobile 列无索引时,OR 连接不同列导致全表扫描
SELECT OrderID FROM Order_main
WHERE HotelCd = '021040'
  OR ContactMobile = '13888888';

查询方式

执行计划

扫描行数

性能影响

原始 OR 查询

Clustered Index Scan

全表扫描

耗时 3.2 秒

优化后

Index Seek + Key Lookup

1,200 行

耗时 0.05 秒

优化方案​​​​​​​
-- 方案 1:UNION ALL 拆分(避免重复行)
SELECT OrderID FROM Order_main
  WHERE HotelCd = '021040'
UNION ALL
SELECT OrderID FROM Order_main
  WHERE ContactMobile = '13888888'
  AND HotelCd != '021040';

-- 方案 2:为 ContactMobile 列添加索引
CREATE INDEX idx_o_ContactMobile
  ON Order_main (ContactMobile);

优化效果:使用 UNION ALL 拆分后,查询从全表扫描变为两次索引查找,扫描行数从全表降至 1,200 行,性能提升 64 倍。添加索引后查询性能进一步提升。

6. 负向查询的灾难性影响

案例 6:NOT IN 导致的性能问题

错误场景​​​​​​​
-- NOT IN(子查询结果集大时可能失效)
SELECT OrderID FROM Order_main
WHERE OrderID NOT IN (
  SELECT OrderID FROM Order_billdetail
);

查询方式

执行计划

扫描行数

性能影响

NOT IN 查询

Clustered Index Scan

全表扫描

耗时 8.5 秒

优化后

Index Seek + Merge Join

15,000 行

耗时 0.3 秒

优化方案​​​​​​​
-- 改用 NOT EXISTS(适用于关联表场景)
SELECT o.* FROM Order_main o
WHERE NOT EXISTS (
  SELECT 1 FROM Order_billdetail c
  WHERE c.OrderID = o.OrderID
);

-- 改用 LEFT JOIN + IS NULL
SELECT o.OrderID FROM Order_main o
LEFT JOIN Order_billdetail b
  ON o.OrderID = b.OrderID
WHERE b.OrderID IS NULL;

优化效果:使用 NOT EXISTS 或 LEFT JOIN 替代 NOT IN 后,查询从全表扫描变为索引查找+合并连接,扫描行数从全表降至 15,000 行,性能提升 28 倍以上。

7.  NULL 值判断的索引失效

案例 7:IS NULL 查询优化策略

错误场景​​​​​​​
-- 查询 NULL 值导致索引失效
SELECT OrderID FROM Order_billdetail
WHERE OriginalOrderRoomID IS NULL;

查询方式

执行计划

扫描行数

性能影响

IS NULL 查询

Clustered Index Scan

全表扫描

耗时 4.2 秒

优化后

Index Seek

1,200 行

耗时 0.03 秒

优化方案​​​​​​​
-- 创建过滤索引(推荐)
CREATE INDEX idx_filtered_notnull
  ON Order_billdetail (OriginalOrderRoomID)
  WHERE OriginalOrderRoomID IS NOT NULL;

-- 调整字段默认值避免 NULL
ALTER TABLE Order_billdetail
  ALTER COLUMN OriginalOrderRoomID INT NOT NULL
  DEFAULT 0;

优化效果:使用过滤索引后,IS NULL 查询从全表扫描变为索引查找,扫描行数从全表降至 1,200 行,性能提升 140 倍。调整字段默认值可从根本上避免 NULL 值问题。

8.  排序与分页的性能陷阱

案例 8:大偏移量分页查询优化

错误场景​​​​​​​
-- 未优化的分页查询(大偏移量问题)
SELECT OrderID, Amount FROM Order_billdetail
ORDER BY CreateTime
OFFSET 100000 ROWS
FETCH NEXT 10 ROWS ONLY;

偏移量

执行计划

逻辑读取

执行时间

100,000 行

Sort + Index Scan

15,280 次

1.8 秒

500,000 行

Sort + Index Scan

76,450 次

8.5 秒

优化方案​​​​​​​
-- 创建覆盖索引
CREATE INDEX idx_cover_create
  ON Order_billdetail (CreateTime, OrderID)
  INCLUDE (Amount);

-- 键集分页(避免大偏移量)
SELECT OrderID, Amount FROM Order_billdetail
WHERE CreateTime > (
  SELECT MAX(CreateTime) FROM (
    SELECT TOP 100000 CreateTime FROM Order_billdetail
    ORDER BY CreateTime
  ) AS tmp
)
ORDER BY CreateTime
FETCH NEXT 10 ROWS ONLY;

优化效果:使用键集分页后,逻辑读取降至 120 次,执行时间从 1.8 秒降至 0.02 秒,性能提升 90 倍。覆盖索引进一步减少 IO 操作。

9. 未优化的连接顺序

案例 9:小表驱动大表的连接优化

错误场景​​​​​​​
-- 小表驱动大表,但连接顺序错误
SELECT o.OrderID, u.UserName FROM Order_main o
JOIN User_info u ON o.ContactMobile = u.mobile
JOIN Order_billdetail b ON o.OrderID = b.OrderID
WHERE u.UserName = 'yxdeng';

表数据量对比

  • User_info:5,000 行(小表)

  • Order_main:1,200,000 行(大表)

  • Order_billdetail:3,600,000 行(大表)

问题:优化器错误选择从 Order_main 开始连接,导致大表作为驱动表

优化方案​​​​​​​
-- 强制小表作为驱动表(推荐)
SELECT o.OrderID, u.UserName FROM User_info u
JOIN Order_main o ON u.mobile = o.ContactMobile
JOIN Order_billdetail b ON o.OrderID = b.OrderID
WHERE u.UserName = 'yxdeng';

-- 或使用查询提示强制连接顺序(谨慎使用)
SELECT o.OrderID, u.UserName FROM Order_main o
JOIN User_info u ON o.ContactMobile = u.mobile
JOIN Order_billdetail b ON o.OrderID = b.OrderID
WHERE u.UserName = 'yxdeng'
OPTION (FORCE ORDER);

优化效果:调整连接顺序后,查询从大表驱动变为小表驱动,执行时间从 8.2 秒降至 0.3 秒,逻辑读取从 280,000 次降至 1,200 次,性能提升 27 倍。

10. 参数嗅探导致的性能波动

案例 10:存储过程执行计划优化

错误场景​​​​​​​
-- 存储过程首次执行参数影响执行计划
CREATE PROCEDURE GetOrders @HotelCd VARCHAR(10) AS
SELECT OrderID FROM Order_main WHERE HotelCd = @HotelCd;
GO

-- 首次执行(高选择性参数)
EXEC GetOrders @HotelCd = '021040'; -- 返回 10 行
-- 生成索引扫描计划

-- 后续执行(低选择性参数)
EXEC GetOrders @HotelCd = '021098'; -- 返回 50,000 行
-- 仍使用索引扫描计划,性能下降
优化方案​​​​​​​
-- 使用 OPTION (RECOMPILE) 强制每次编译
ALTER PROCEDURE GetOrders @HotelCd VARCHAR(10) AS
SELECT OrderID FROM Order_main WHERE HotelCd = @HotelCd
OPTION (RECOMPILE);

-- 或使用本地变量"屏蔽"参数嗅探
ALTER PROCEDURE GetOrders @HotelCd VARCHAR(10) AS
DECLARE @LocalHotelCd VARCHAR(10) = @HotelCd;
SELECT OrderID FROM Order_main WHERE HotelCd = @LocalHotelCd;

优化方式

执行计划

执行时间

未优化

固定索引扫描

1.2 秒 - 8.5 秒

OPTION (RECOMPILE)

动态选择最优计划

0.02 秒 - 0.8 秒

优化效果:使用 RECOMPILE 后,存储过程根据每次参数值选择最优执行计划,性能波动减少 90%,平均执行时间降低 65%。

11.  临时表滥用导致性能下降

案例 11:CTE 替代临时表的最佳实践

错误场景​​​​​​​
-- 频繁创建/删除临时表
SELECT OrderID, Amount INTO #TempOrders
FROM Order_billdetail
WHERE HotelCd = '021040';

-- 多次操作临时表
UPDATE #TempOrders SET Amount = Amount * 1.1;
SELECT * FROM #TempOrders;

DROP TABLE #TempOrders;

操作类型

CPU 时间

逻辑读取

执行时间

临时表操作

420ms

8,500 次

650ms

优化后

85ms

1,200 次

120ms

优化方案​​​​​​​
-- 改用 CTE(公用表表达式)
WITH CTE_Orders AS (
  SELECT OrderID, Amount * 1.1 AS NewAmount
  FROM Order_billdetail
  WHERE HotelCd = 'HOTEL01'
)
SELECT OrderID, NewAmount FROM CTE_Orders;

-- 或使用派生表
SELECT OrderID, Amount * 1.1 AS NewAmount
FROM (
  SELECT OrderID, Amount
  FROM Order_billdetail
  WHERE HotelCd = 'HOTEL01'
) AS DerivedTable;

优化效果:使用 CTE 或派生表替代临时表后,CPU 时间减少 80%,逻辑读取减少 86%,执行时间从 650ms 降至 120ms,性能提升 5 倍以上,同时减少 TempDB 资源争用。

12.  游标滥用导致效率低下

案例 12:集合操作替代游标方案

错误场景​​​​​​​
-- 使用游标逐行处理订单
DECLARE @OrderID VARCHAR(50);
DECLARE cur_orders CURSOR FOR
  SELECT OrderID FROM Order_main
  WHERE CreateTime > '2025-01-01';

OPEN cur_orders;
FETCH NEXT FROM cur_orders INTO @OrderID;

WHILE @@FETCH_STATUS = 0
BEGIN
  UPDATE Order_billdetail SET Amount = Amount * 1.1
  WHERE OrderID = @OrderID;

  FETCH NEXT FROM cur_orders INTO @OrderID;
END;

CLOSE cur_orders;
DEALLOCATE cur_orders;

处理方式

执行时间

事务日志

锁竞争

游标处理

42 秒 (50,000 行)

严重

集合操作

0.8 秒

轻微

优化方案​​​​​​​
-- 改用集合 UPDATE 操作
UPDATE b SET b.Amount = b.Amount * 1.1
FROM Order_billdetail b
JOIN Order_main o ON b.OrderID = o.OrderID
WHERE o.CreateTime > '2025-01-01';

-- 批量更新(每 10,000 行提交一次)
WHILE 1=1
BEGIN
  UPDATE TOP (10000) b
  SET b.Amount = b.Amount * 1.1
  FROM Order_billdetail b
  JOIN Order_main o ON b.OrderID = o.OrderID
  WHERE o.CreateTime > '2025-01-01'
    AND b.Amount < CAST(b.Amount * 1.1 AS DECIMAL(18,2));

  IF @@ROWCOUNT = 0 BREAK;
END;

优化效果:使用集合 UPDATE 替代游标后,处理 50,000 行数据的时间从 42 秒降至 0.8 秒,性能提升 52 倍。批量更新方案减少锁竞争和事务日志增长,适合大规模数据更新。

13.  未利用覆盖索引导致回表

案例 13:避免 Key Lookup 的性能优化

错误场景​​​​​​​
-- 创建普通索引
CREATE INDEX idx_hotel
  ON Order_main (HotelCd);

-- 查询需要回表获取 CreateTime
SELECT OrderID, CreateTime
FROM Order_main
WHERE HotelCd = '021040';

执行计划问题

  • 索引查找(idx_hotel)找到匹配行

  • Key Lookup 操作回表获取 CreateTime

  • 每行额外 2-3 次逻辑读取

  • 查询成本增加 300%

优化方案​​​​​​​
-- 创建覆盖索引(包含所需列)
CREATE INDEX idx_cover
  ON Order_main (HotelCd)
  INCLUDE (CreateTime, OrderID);

-- 查询直接从索引获取数据
SELECT OrderID, CreateTime
FROM Order_main
WHERE HotelCd = '021040';

索引类型

逻辑读取

执行时间

CPU 开销

普通索引

15,200 次

420ms

320ms

覆盖索引

850 次

25ms

45ms

优化效果:使用覆盖索引后,Key Lookup 操作完全消除,逻辑读取减少 94%,执行时间从 420ms 降至 25ms,性能提升 16 倍,CPU 开销减少 86%。

14.  索引碎片导致性能下降

案例 14:索引维护策略优化

错误场景​​​​​​​
-- 频繁插入删除导致索引碎片
DELETE FROM Order_billdetail
  WHERE HotelCd = '021040';

INSERT INTO Order_billdetail
  SELECT * FROM Temp_Orders;

碎片级别

扫描密度

逻辑碎片

性能影响

0-5%

优秀

可忽略

无影响

5-30%

良好

中等

轻微下降

30%+

严重

显著下降

优化方案​​​​​​​
-- 定期重建索引(在线操作)
ALTER INDEX idx_hotel ON Order_main
  REBUILD WITH (ONLINE = ON);

-- 设置填充因子(预留空间)
CREATE INDEX idx_hotel ON Order_main (HotelCd)
  WITH (FILLFACTOR = 80);

-- 自动化维护脚本
DECLARE @Fragmentation FLOAT = 30.0;
SELECT 'ALTER INDEX ' + name + ' ON ' +
OBJECT_NAME(object_id)
  + ' REBUILD;' AS RebuildCommand
FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL,
NULL, NULL)
WHERE avg_fragmentation_in_percent > @Fragmentation;

优化效果:定期重建索引后,查询性能恢复至初始水平,逻辑读取减少 40%。设置填充因子 80% 后,索引碎片增长率降低 65%,维护频率减少 50%。

15.  忽略统计信息导致错误执行计划

案例 15:统计信息过时的性能影响

错误场景​​​​​​​
-- 统计信息过时,优化器选择错误索引
SELECT OrderID FROM Order_main
WHERE HotelCd = '021040'
  AND OrderStatus = 'I';

问题分析

  • 统计信息未及时更新,优化器低估匹配行数

  • 错误选择非聚集索引而非聚集索引扫描

  • 实际执行时发现行数远超预期

  • 导致 Key Lookup 操作过多,性能下降

优化方案​​​​​​​
-- 手动更新统计信息
UPDATE STATISTICS Order_main
  (Idx_OrderMain_HotelCdOrderStatus);

-- 启用自动更新统计信息
ALTER DATABASE YourDB
  SET AUTO_UPDATE_STATISTICS ON;

-- 创建计划指南(高级)
EXEC sp_create_plan_guide
  @name = N'ForceIndexGuide',
  @stmt = N'SELECT OrderID FROM Order_main...',
  @type = N'SQL',
  @module_or_batch = NULL,
  @params = NULL,
  @hints = N'OPTION (TABLE HINT(Order_main,
INDEX(Idx_OrderMain_HotelCdOrderStatus)))';

优化效果:更新统计信息后,优化器正确选择执行计划,查询性能提升 8 倍。启用自动更新后,统计信息过时问题减少 90%,执行计划稳定性提高。

16.  未优化的子查询

案例 16:相关子查询的性能陷阱

错误场景​​​​​​​
-- 相关子查询效率低
SELECT o.OrderID, (
  SELECT TOP 1 Amount
  FROM Order_billdetail b
  WHERE b.OrderID = o.OrderID
) AS Amount
FROM Order_main o;

数据量

执行时间

逻辑读取

10,000 行

8.2 秒

210,000 次

50,000 行

42 秒

1,050,000 次

优化方案​​​​​​​
-- 改用 JOIN 操作
SELECT o.OrderID, b.Amount
FROM Order_main o
LEFT JOIN (
  SELECT OrderID, Amount
  FROM Order_billdetail
) b ON o.OrderID = b.OrderID;

优化效果:使用 JOIN 替代相关子查询后,执行时间从 8.2 秒降至 0.3 秒,逻辑读取从 210,000 次降至 1,200 次,性能提升 27 倍,资源消耗减少 99%。

17.   未考虑事务隔离级别

案例 17:隔离级别选择与数据一致性

错误场景​​​​​​​
-- 默认隔离级别导致脏读风险
BEGIN TRANSACTION;
SELECT * FROM Order_billdetail
WHERE HotelCd = '021040'
  AND OrderID = 'R000001';
-- 未提交,另一事务可能读取未提交数据
COMMIT;

隔离级别

脏读

不可重复读

幻读

READ UNCOMMITTED

允许

允许

允许

READ COMMITTED

阻止

允许

允许

REPEATABLE READ

阻止

阻止

允许

优化方案​​​​​​​
-- 使用 REPEATABLE READ 隔离级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRANSACTION;
SELECT * FROM Order_billdetail
WHERE HotelCd = '021040'
  AND OrderID = 'R000001';
COMMIT TRANSACTION;

-- 或使用 SNAPSHOT 隔离级别
ALTER DATABASE YourDB
  SET ALLOW_SNAPSHOT_ISOLATION ON;

SET TRANSACTION ISOLATION LEVEL SNAPSHOT;

优化效果:使用 REPEATABLE READ 后,保证在事务执行期间读取的数据不会被其他事务修改,数据一致性提高,脏读风险完全消除,不可重复读问题减少 100%。

18.  未优化的动态 SQL

案例 18:参数化查询避免 SQL 注入

错误场景​​​​​​​
-- 字符串拼接导致索引失效和 SQL 注入风险
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM Order_main
WHERE HotelCd = ''021040''';
EXEC sp_executesql @sql;

风险类型

影响

严重性

SQL 注入

数据泄露/篡改

严重

隐式转换

索引失效

执行计划无法重用

性能下降

优化方案​​​​​​​

-- 使用参数化查询(推荐)
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM Order_main
WHERE HotelCd = @HotelCd';
DECLARE @params NVARCHAR(MAX) = '@HotelCd VARCHAR(10)';
EXEC sp_executesql @sql, @params, @HotelCd = '021040';

-- 或使用存储过程
CREATE PROCEDURE GetOrders @HotelCd VARCHAR(10) AS
BEGIN
  SELECT * FROM Order_main WHERE HotelCd = @HotelCd;
END;

优化效果:参数化查询消除 SQL 注入风险,保证索引有效使用,执行计划重用率提高 80%,查询性能提升 30%,安全性达到企业级标准。

19.  未优化的字符串拼接

案例 19:动态 SQL 硬编码值问题

错误场景​​​​​​​
-- 动态 SQL 硬编码值导致性能和安全问题
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM Order_main
WHERE HotelCd = ''021040''';

EXEC sp_executesql @sql;

问题类型

影响

严重性

SQL 注入风险

数据安全威胁

严重

执行计划无法重用

每次查询都需要重新编译

参数嗅探问题

执行计划选择不当

优化方案​​​​​​​
-- 使用参数化查询(推荐)
DECLARE @sql NVARCHAR(MAX) = 'SELECT * FROM Order_main
WHERE HotelCd = @HotelCd';
DECLARE @params NVARCHAR(MAX) = '@HotelCd VARCHAR(10)';
EXEC sp_executesql @sql, @params, @HotelCd = '021040';

-- 或使用存储过程
CREATE PROCEDURE GetOrders @HotelCd VARCHAR(10) AS
BEGIN
  SELECT * FROM Order_main WHERE HotelCd = @HotelCd;
END;

优化效果:参数化查询消除 SQL 注入风险,执行计划重用率提高 85%,查询性能提升 40%,CPU 使用率降低 35%,系统安全性达到企业级标准。

10.  Link Server 跨服务器执行计划陷阱

案例 20:Link Server 的执行计划陷阱

错误场景​​​​​​​
-- 案例1:条件过滤在本地执行
IF NOT EXISTS (SELECT TOP 1 * FROM [SERVER1312].[PMS_Report].[dbo].[R_NAZH] 
 WHERE hotelid=@HotelCd AND rq=@BizDate AND cd='QCK’) 

-- 案例2:JOIN 操作在本地执行
SELECT TOP 1 * FROM [SERVER1312].[PMS_Report].[dbo].[R_NAZH] AS r 
INNER JOIN Hotel_info AS h ON r.HotelCd=h.Hotelcd 
WHERE hotelcd='021040'

问题类型

影响

严重性

大量无效网络传输

大量消耗资源, 影响系统稳定

严重

本地资源过度消耗

大量消耗资源, 影响系统稳定

严重

查询响应时间延长

大量消耗资源, 影响系统稳定

严重

优化方案​​​​​​​
-- 正确案例:使用OPENQUERY强制远程执行
-- 案例 1:(仅仅适用于临时,手工处理数据)
IF NOT EXISTS (
    SELECT TOP 1 FROM OPENQUERY([SERVER1312], 
        'SELECT 1 FROM PMS_Report.dbo.R_NAZH 
         WHERE hotelid=''' + @HotelCd + ''' 
           AND rq=''' + CAST(@BizDate AS VARCHAR(10)) + ''' 
           AND cd=''QCK''')
)
BEGIN
    -- 执行逻辑
END

优化建议:Link Server 适合简单、低频、小数据量的跨服务器操作,在高并发、大数据量或复杂事务场景下,不要将 LinkServer 写入正式的代码业务里面。

21.   数据类型性能分析与选择

数据类型性能排序

***   性能从高到低排序(基于存储效率和处理速度):

  1. BIT - 1字节
    布尔值存储首选,内存处理极快
    适用场景:标记字段(如是否启用)

  2. TINYINT - 1字节
    最小整数类型,适合小范围整数(0-255)
    适用场景:年龄、状态码

  3. SMALLINT - 2字节
    中等范围整数,平衡存储与性能(-32,768到32,768)
    适用场景:地理坐标、小计计数器

  4. INT - 4字节
    通用整数类型,索引和计算效率高
    (-2,147,483,648到2,147,483,647)
    适用场景:主键、常规计数

  5. BIGINT - 8字节
    处理超大整数
    适用场景:高并发系统的唯一标识

  6. DATE - 3字节
    比 DATETIME 更紧凑,查询效率高(YYYY-MM-DD)
    适用场景:生日、订单日期

  7. TIME - 3-5字节
    专注时间存储,无日期冗余(HH:mm:ss.nnn)
    适用场景:事件时间戳、日志时间

  8. DATETIME2 - 6-8字节
    替代 DATETIME,更高精度和范围
    适用场景:金融交易、高精度日志

  9. CHAR(n) - n字节
    固定长度数据处理快
    适用场景:国家代码、短字符串

  10. BINARY(n) - n字节
    固定二进制数据处理快
    适用场景:安全令牌、唯一标识

  11. VARCHAR(n) - 实际长度+2字节
    节省空间,适合长度变化的文本
    适用场景:用户名、动态描述

  12. VARBINARY(n) - 实际长度+2字节
    灵活存储二进制数据
    适用场景:非结构化数据、可变长度二进制

  13. DECIMAL/NUMERIC - 变长
    精确数值计算
    适用场景:财务数据、科学计算

  14. FLOAT/REAL - 4-8字节
    科学计算、大范围数值
    适用场景:统计、物理模拟

  15. NCHAR/NVARCHAR - 双倍空间
    多语言支持
    适用场景:国际化文本、特殊字符

  16. DATETIME - 8字节
    范围小(1753年起),精度低
    适用场景:遗留系统兼容

  17. SMALLDATETIME - 4字节
    范围和精度均受限
    适用场景:低精度历史数据

  18. TEXT/NTEXT/IMAGE - 已废弃
    性能差,不支持行内存存储
    适用场景:无(建议迁移)

优化建议

数据类型选择与使用的最佳实践

  • 存储空间优化:优先使用最小满足需求的类型

    • 使用 TINYINT(0-255) 代替 INT 存储小范围数值

    • 使用 DATE(3字节) 代替 DATETIME(8字节) 存储日期

  • 日期时间处理

    • 新项目用 DATE、TIME、DATETIME2,避免 DATETIME 和 SMALLDATETIME

    • 精确到毫秒使用 DATETIME2(7) 而非 DATETIME

  • 字符编码优化

    • 非 Unicode 优先(VARCHAR 代替 NVARCHAR),除非需多语言支持

    • 固定长度文本使用 CHAR(n),可变长度使用 VARCHAR(n)

  • 二进制数据处理

    • 小数据用 BINARY/VARBINARY,大数据用 VARBINARY(MAX)

    • 避免使用已废弃的 TEXT/NTEXT/IMAGE 类型

  • 索引设计优化

    • 定长类型(如 CHAR、INT)在索引中更紧凑,查询更快

    • 避免在索引中使用大型对象类型(VARCHAR(MAX))

  • 精度平衡

    • 高精度类型(如 DECIMAL、DATETIME2)可能牺牲部分性能,需权衡

    • 财务计算必须使用 DECIMAL,科学计算可考虑 FLOAT

结论:合理选择数据类型可提升 30% 以上查询性能,减少 50% 存储空间

22.  性能优化最佳实践总结

索引设计原则

  • 遵循最左匹配原则创建联合索引

  • 避免创建冗余索引

  • 使用覆盖索引减少回表操作

  • 为频繁查询字段创建合适索引

  • 考虑过滤索引优化特定查询

  • 定期评估索引使用效率

查询优化技巧

  • 避免在索引列使用函数

  • 使用参数化查询避免隐式转换

  • 拆分复杂查询为简单查询

  • 优先使用 JOIN 替代子查询

  • 避免前置 % 的 LIKE 查询

  • 使用 UNION ALL 替代 OR 条件

日常维护策略

  • 定期更新统计信息

  • 监控并重建碎片化索引

  • 合理设置事务隔离级别

  • 避免游标和临时表滥用

  • 启用自动更新统计信息

  • 实施参数化查询规范

23. 常见问题与解决方案

问题场景

解决方案

执行计划选择错误

更新统计信息、使用查询提示强制索引、重建索引

高并发下阻塞

使用行版本控制隔离级别、优化事务范围(READ_COMMITTED_SNAPSHOT)

大偏移量分页性能差

采用键集分页、使用覆盖索引、避免 OFFSET FETCH

索引失效

检查查询条件是否匹配索引结构、避免隐式类型转换、避免索引列函数操作

参数嗅探导致性能波动

使用 OPTION(RECOMPILE)、本地变量屏蔽参数、更新统计信息

统计信息过时

启用 AUTO_UPDATE_STATISTICS、手动更新关键表统计信息

事务隔离级别不当

根据业务需求选择合适隔离级别、使用 SNAPSHOT 隔离避免阻塞

临时表滥用

使用 CTE 或派生表替代、减少临时表创建频率

游标性能低下

使用基于集合的操作替代、批量处理数据

动态 SQL 安全问题

使用参数化查询、输入验证、存储过程封装

24.  如何抓取异常 SQL

1. 最消耗磁盘IO的SQL语句​​​​​​​

-- IO消耗列出前10个最差性能SQL语句分析
SELECT TOP 10
    st.text,        -- 执行的SQL语句
    qp.query_plan,  -- SQL执行计划
    qs.*
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY total_logical_reads DESC
GO

IO高的常见原因:可能包括查询效率低下,比如没有使用索引,导致全表扫描;或者表结构不合理,比如数据分布不均;还有可能是硬件问题,比如磁盘性能差,或者配置不当。并发执行多个IO密集型查询也可能导致高IO。

2. 最消耗时间的SQL语句​​​​​​​

-- 最消耗CPU的SQL语句
SELECT TOP 10
    st.text,       -- 执行的SQL语句
    qp.query_plan, -- SQL执行计划
    qs.*
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.plan_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY total_worker_time DESC
GO

CPU消耗高常见原因:通常由SQL语句效率不高、并发操作、数据设计不合理等,执行计划分析、调整及SQL优化,可以显著降低CPU负载,或升级服务器CPU的硬件性能。

3. 最消耗CPU的SQL语句​​​​​​​

-- CPU消耗最高的SQL语句分析
SELECT spid, cmd, cpu, physical_io, memusage, loginame,hostname,
(SELECT TOP 1 [text] FROM::fn_get_sql(sql_handle)) sql_text
FROM master..sysprocesses
ORDER BY cpu DESC, physical_io DESC;

引入地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值