目录
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. 数据类型性能分析与选择
数据类型性能排序
*** 性能从高到低排序(基于存储效率和处理速度):
-
BIT - 1字节
布尔值存储首选,内存处理极快
适用场景:标记字段(如是否启用) -
TINYINT - 1字节
最小整数类型,适合小范围整数(0-255)
适用场景:年龄、状态码 -
SMALLINT - 2字节
中等范围整数,平衡存储与性能(-32,768到32,768)
适用场景:地理坐标、小计计数器 -
INT - 4字节
通用整数类型,索引和计算效率高
(-2,147,483,648到2,147,483,647)
适用场景:主键、常规计数 -
BIGINT - 8字节
处理超大整数
适用场景:高并发系统的唯一标识 -
DATE - 3字节
比 DATETIME 更紧凑,查询效率高(YYYY-MM-DD)
适用场景:生日、订单日期 -
TIME - 3-5字节
专注时间存储,无日期冗余(HH:mm:ss.nnn)
适用场景:事件时间戳、日志时间 -
DATETIME2 - 6-8字节
替代 DATETIME,更高精度和范围
适用场景:金融交易、高精度日志 -
CHAR(n) - n字节
固定长度数据处理快
适用场景:国家代码、短字符串 -
BINARY(n) - n字节
固定二进制数据处理快
适用场景:安全令牌、唯一标识 -
VARCHAR(n) - 实际长度+2字节
节省空间,适合长度变化的文本
适用场景:用户名、动态描述 -
VARBINARY(n) - 实际长度+2字节
灵活存储二进制数据
适用场景:非结构化数据、可变长度二进制 -
DECIMAL/NUMERIC - 变长
精确数值计算
适用场景:财务数据、科学计算 -
FLOAT/REAL - 4-8字节
科学计算、大范围数值
适用场景:统计、物理模拟 -
NCHAR/NVARCHAR - 双倍空间
多语言支持
适用场景:国际化文本、特殊字符 -
DATETIME - 8字节
范围小(1753年起),精度低
适用场景:遗留系统兼容 -
SMALLDATETIME - 4字节
范围和精度均受限
适用场景:低精度历史数据 -
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;

被折叠的 条评论
为什么被折叠?



