query-optimization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Query Optimization

查询优化

Comprehensive guide to T-SQL query optimization techniques.
T-SQL查询优化技巧全面指南。

Quick Reference

快速参考

SARGable vs Non-SARGable Patterns

SARGable与非SARGable模式

Non-SARGable (Bad)SARGable (Good)
WHERE YEAR(Date) = 2024
WHERE Date >= '2024-01-01' AND Date < '2025-01-01'
WHERE LEFT(Name, 3) = 'ABC'
WHERE Name LIKE 'ABC%'
WHERE Amount * 1.1 > 1000
WHERE Amount > 1000 / 1.1
WHERE ISNULL(Col, 0) = 5
WHERE Col = 5 OR Col IS NULL
WHERE VarcharCol = 123
WHERE VarcharCol = '123'
非SARGable(不推荐)SARGable(推荐)
WHERE YEAR(Date) = 2024
WHERE Date >= '2024-01-01' AND Date < '2025-01-01'
WHERE LEFT(Name, 3) = 'ABC'
WHERE Name LIKE 'ABC%'
WHERE Amount * 1.1 > 1000
WHERE Amount > 1000 / 1.1
WHERE ISNULL(Col, 0) = 5
WHERE Col = 5 OR Col IS NULL
WHERE VarcharCol = 123
WHERE VarcharCol = '123'

Join Types Performance

连接类型性能对比

Join TypeBest ForCharacteristics
Nested LoopSmall outer, indexed innerLow memory, good for small sets
Merge JoinSorted inputs, similar sizesEfficient for sorted data
Hash JoinLarge unsorted inputsHigh memory, good for large sets
连接类型适用场景特点
Nested Loop外部数据集小、内部数据集有索引内存占用低,适合小数据集
Merge Join输入已排序、数据集大小相近对已排序数据处理高效
Hash Join大型未排序输入数据集内存占用高,适合大数据集

Query Hints Quick Reference

查询提示快速参考

HintPurpose
OPTION (RECOMPILE)
Fresh plan each execution
OPTION (OPTIMIZE FOR (@p = value))
Optimize for specific value
OPTION (OPTIMIZE FOR UNKNOWN)
Use average statistics
OPTION (MAXDOP n)
Limit parallelism
OPTION (FORCE ORDER)
Use exact join order
WITH (NOLOCK)
Read uncommitted (dirty reads)
WITH (FORCESEEK)
Force index seek
提示用途
OPTION (RECOMPILE)
每次执行生成新的执行计划
OPTION (OPTIMIZE FOR (@p = value))
针对特定参数值优化
OPTION (OPTIMIZE FOR UNKNOWN)
使用平均统计信息优化
OPTION (MAXDOP n)
限制并行度
OPTION (FORCE ORDER)
使用指定的连接顺序
WITH (NOLOCK)
读取未提交数据(脏读)
WITH (FORCESEEK)
强制使用索引查找

Core Optimization Principles

核心优化原则

1. SARGability

1. SARGability

SARG = Search ARGument. SARGable queries can use index seeks:
sql
-- Non-SARGable: Function on column
WHERE DATEPART(year, OrderDate) = 2024
WHERE UPPER(CustomerName) = 'JOHN'
WHERE OrderAmount + 100 > 500

-- SARGable: Preserve column
WHERE OrderDate >= '2024-01-01' AND OrderDate < '2025-01-01'
WHERE CustomerName = 'john' COLLATE SQL_Latin1_General_CP1_CI_AS
WHERE OrderAmount > 400
SARG即Search ARGument(搜索参数)。SARGable查询可以使用索引查找:
sql
-- 非SARGable:对列使用函数
WHERE DATEPART(year, OrderDate) = 2024
WHERE UPPER(CustomerName) = 'JOHN'
WHERE OrderAmount + 100 > 500

-- SARGable:保留列的原始形式
WHERE OrderDate >= '2024-01-01' AND OrderDate < '2025-01-01'
WHERE CustomerName = 'john' COLLATE SQL_Latin1_General_CP1_CI_AS
WHERE OrderAmount > 400

2. Implicit Conversions

2. 隐式转换

Avoid data type mismatches:
sql
-- Bad: Implicit conversion (varchar column compared to int)
WHERE VarcharColumn = 12345

-- Good: Match types exactly
WHERE VarcharColumn = '12345'

-- Check for implicit conversions in execution plan
-- Look for CONVERT_IMPLICIT warnings
避免数据类型不匹配:
sql
-- 不推荐:隐式转换(varchar列与int类型比较)
WHERE VarcharColumn = 12345

-- 推荐:完全匹配数据类型
WHERE VarcharColumn = '12345'

-- 在执行计划中检查隐式转换
-- 查找CONVERT_IMPLICIT警告

3. OR Optimization

3. OR条件优化

OR on different columns prevents seek:
sql
-- Inefficient: OR on different columns
SELECT * FROM Orders
WHERE CustomerID = 1 OR ProductID = 2

-- Better: UNION for OR optimization
SELECT * FROM Orders WHERE CustomerID = 1
UNION ALL
SELECT * FROM Orders WHERE ProductID = 2 AND CustomerID <> 1
不同列上的OR条件会阻止索引查找:
sql
-- 低效:不同列使用OR
SELECT * FROM Orders
WHERE CustomerID = 1 OR ProductID = 2

-- 优化方案:使用UNION替代OR
SELECT * FROM Orders WHERE CustomerID = 1
UNION ALL
SELECT * FROM Orders WHERE ProductID = 2 AND CustomerID <> 1

4. EXISTS vs IN vs JOIN

4. EXISTS、IN与JOIN的对比

sql
-- EXISTS: Best for semi-joins (checking existence)
SELECT * FROM Customers c
WHERE EXISTS (SELECT 1 FROM Orders o WHERE o.CustomerID = c.CustomerID)

-- IN: Good for small static lists
SELECT * FROM Products WHERE CategoryID IN (1, 2, 3)

-- JOIN: Best when you need data from both tables
SELECT c.*, o.OrderDate
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID
sql
-- EXISTS:半连接场景最佳(检查存在性)
SELECT * FROM Customers c
WHERE EXISTS (SELECT 1 FROM Orders o WHERE o.CustomerID = c.CustomerID)

-- IN:适合小型静态列表
SELECT * FROM Products WHERE CategoryID IN (1, 2, 3)

-- JOIN:需要获取两张表数据时最佳
SELECT c.*, o.OrderDate
FROM Customers c
JOIN Orders o ON c.CustomerID = o.CustomerID

Parameter Sniffing Solutions

参数嗅探解决方案

Problem

问题示例

sql
-- First execution with CustomerID=1 (10 rows) creates plan
-- Subsequent execution with CustomerID=999 (1M rows) uses same plan
CREATE PROCEDURE GetOrders @CustomerID INT AS
    SELECT * FROM Orders WHERE CustomerID = @CustomerID
sql
-- 首次执行使用CustomerID=1(10行数据)生成执行计划
-- 后续执行使用CustomerID=999(100万行数据)仍使用相同计划
CREATE PROCEDURE GetOrders @CustomerID INT AS
    SELECT * FROM Orders WHERE CustomerID = @CustomerID

Solution 1: OPTION (RECOMPILE)

解决方案1:OPTION (RECOMPILE)

sql
CREATE PROCEDURE GetOrders @CustomerID INT AS
    SELECT * FROM Orders
    WHERE CustomerID = @CustomerID
    OPTION (RECOMPILE)
-- Best for: Infrequent queries, highly variable data distribution
sql
CREATE PROCEDURE GetOrders @CustomerID INT AS
    SELECT * FROM Orders
    WHERE CustomerID = @CustomerID
    OPTION (RECOMPILE)
-- 最佳适用场景:执行频率低、数据分布差异大的查询

Solution 2: OPTIMIZE FOR

解决方案2:OPTIMIZE FOR

sql
-- Optimize for specific value
OPTION (OPTIMIZE FOR (@CustomerID = 1))

-- Optimize for unknown (average statistics)
OPTION (OPTIMIZE FOR UNKNOWN)
sql
-- 针对特定参数值优化
OPTION (OPTIMIZE FOR (@CustomerID = 1))

-- 针对未知值优化(使用平均统计信息)
OPTION (OPTIMIZE FOR UNKNOWN)

Solution 3: Local Variables

解决方案3:局部变量

sql
CREATE PROCEDURE GetOrders @CustomerID INT AS
BEGIN
    DECLARE @LocalID INT = @CustomerID
    SELECT * FROM Orders WHERE CustomerID = @LocalID
END
-- Hides parameter from optimizer, similar to OPTIMIZE FOR UNKNOWN
sql
CREATE PROCEDURE GetOrders @CustomerID INT AS
BEGIN
    DECLARE @LocalID INT = @CustomerID
    SELECT * FROM Orders WHERE CustomerID = @LocalID
END
-- 对优化器隐藏参数,效果类似OPTIMIZE FOR UNKNOWN

Solution 4: Query Store Hints (SQL 2022+)

解决方案4:查询存储提示(SQL 2022+)

sql
EXEC sys.sp_query_store_set_hints
    @query_id = 12345,
    @hints = N'OPTION (RECOMPILE)'
-- Apply hints without code changes
sql
EXEC sys.sp_query_store_set_hints
    @query_id = 12345,
    @hints = N'OPTION (RECOMPILE)'
-- 无需修改代码即可应用提示

Solution 5: PSP Optimization (SQL 2022+)

解决方案5:PSP优化(SQL 2022+)

sql
-- Enable Parameter Sensitive Plan optimization
ALTER DATABASE YourDB SET COMPATIBILITY_LEVEL = 160
-- Automatically creates multiple plans based on parameter values
sql
-- 启用参数敏感计划优化
ALTER DATABASE YourDB SET COMPATIBILITY_LEVEL = 160
-- 根据参数值自动生成多个执行计划

Execution Plan Analysis

执行计划分析

Key Operators to Watch

需要关注的关键操作符

OperatorWarning SignAction
Table ScanMissing indexAdd appropriate index
Index ScanNon-SARGable predicateRewrite query
Key LookupMissing covering indexAdd INCLUDE columns
SortMissing index for ORDER BYAdd sorted index
Hash MatchLarge memory grantConsider index
SpoolsRepeated scansRestructure query
操作符警告信号处理措施
表扫描缺少索引添加合适的索引
索引扫描存在非SARGable谓词重写查询
键查找缺少覆盖索引添加INCLUDE列
排序缺少用于ORDER BY的索引添加排序索引
哈希匹配内存授予过大考虑添加索引
假脱机重复扫描重构查询

Estimated vs Actual Rows

估计行数与实际行数

sql
-- Large difference indicates statistics problem
-- Check if stats need updating:
UPDATE STATISTICS TableName WITH FULLSCAN

-- Or enable auto-update:
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS ON
sql
-- 两者差异大表示统计信息存在问题
-- 检查是否需要更新统计信息:
UPDATE STATISTICS TableName WITH FULLSCAN

-- 或启用自动更新:
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS ON

Finding Missing Indexes

查找缺失的索引

sql
SELECT
    CONVERT(DECIMAL(18,2), migs.avg_user_impact) AS AvgImpact,
    mid.statement AS TableName,
    mid.equality_columns,
    mid.inequality_columns,
    mid.included_columns
FROM sys.dm_db_missing_index_groups mig
JOIN sys.dm_db_missing_index_group_stats migs ON mig.index_group_handle = migs.group_handle
JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE mid.database_id = DB_ID()
ORDER BY migs.avg_user_impact DESC
sql
SELECT
    CONVERT(DECIMAL(18,2), migs.avg_user_impact) AS AvgImpact,
    mid.statement AS TableName,
    mid.equality_columns,
    mid.inequality_columns,
    mid.included_columns
FROM sys.dm_db_missing_index_groups mig
JOIN sys.dm_db_missing_index_group_stats migs ON mig.index_group_handle = migs.group_handle
JOIN sys.dm_db_missing_index_details mid ON mig.index_handle = mid.index_handle
WHERE mid.database_id = DB_ID()
ORDER BY migs.avg_user_impact DESC

Statistics Management

统计信息管理

View Statistics Info

查看统计信息详情

sql
DBCC SHOW_STATISTICS('TableName', 'IndexName')
sql
DBCC SHOW_STATISTICS('TableName', 'IndexName')

Update Statistics

更新统计信息

sql
-- Update all statistics on table
UPDATE STATISTICS TableName

-- Update with full scan (most accurate)
UPDATE STATISTICS TableName WITH FULLSCAN

-- Update specific statistics
UPDATE STATISTICS TableName StatisticsName
sql
-- 更新表上的所有统计信息
UPDATE STATISTICS TableName

-- 全扫描更新(最准确)
UPDATE STATISTICS TableName WITH FULLSCAN

-- 更新特定统计信息
UPDATE STATISTICS TableName StatisticsName

Auto-Update Settings

自动更新设置

sql
-- Enable async auto-update (better for OLTP)
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS_ASYNC ON
sql
-- 启用异步自动更新(更适合OLTP系统)
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS_ASYNC ON

Additional References

额外参考资料

For deeper coverage of performance diagnostics, see:
  • references/dmv-diagnostic-queries.md
    - DMV queries for performance analysis
如需深入了解性能诊断,可查看:
  • references/dmv-diagnostic-queries.md
    - 用于性能分析的DMV查询语句 ",