query-optimization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseQuery 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) |
|---|---|
| |
| |
| |
| |
| |
| 非SARGable(不推荐) | SARGable(推荐) |
|---|---|
| |
| |
| |
| |
| |
Join Types Performance
连接类型性能对比
| Join Type | Best For | Characteristics |
|---|---|---|
| Nested Loop | Small outer, indexed inner | Low memory, good for small sets |
| Merge Join | Sorted inputs, similar sizes | Efficient for sorted data |
| Hash Join | Large unsorted inputs | High memory, good for large sets |
| 连接类型 | 适用场景 | 特点 |
|---|---|---|
| Nested Loop | 外部数据集小、内部数据集有索引 | 内存占用低,适合小数据集 |
| Merge Join | 输入已排序、数据集大小相近 | 对已排序数据处理高效 |
| Hash Join | 大型未排序输入数据集 | 内存占用高,适合大数据集 |
Query Hints Quick Reference
查询提示快速参考
| Hint | Purpose |
|---|---|
| Fresh plan each execution |
| Optimize for specific value |
| Use average statistics |
| Limit parallelism |
| Use exact join order |
| Read uncommitted (dirty reads) |
| Force index seek |
| 提示 | 用途 |
|---|---|
| 每次执行生成新的执行计划 |
| 针对特定参数值优化 |
| 使用平均统计信息优化 |
| 限制并行度 |
| 使用指定的连接顺序 |
| 读取未提交数据(脏读) |
| 强制使用索引查找 |
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 > 400SARG即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 > 4002. 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 <> 14. 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.CustomerIDsql
-- 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.CustomerIDParameter 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 = @CustomerIDsql
-- 首次执行使用CustomerID=1(10行数据)生成执行计划
-- 后续执行使用CustomerID=999(100万行数据)仍使用相同计划
CREATE PROCEDURE GetOrders @CustomerID INT AS
SELECT * FROM Orders WHERE CustomerID = @CustomerIDSolution 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 distributionsql
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 UNKNOWNsql
CREATE PROCEDURE GetOrders @CustomerID INT AS
BEGIN
DECLARE @LocalID INT = @CustomerID
SELECT * FROM Orders WHERE CustomerID = @LocalID
END
-- 对优化器隐藏参数,效果类似OPTIMIZE FOR UNKNOWNSolution 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 changessql
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 valuessql
-- 启用参数敏感计划优化
ALTER DATABASE YourDB SET COMPATIBILITY_LEVEL = 160
-- 根据参数值自动生成多个执行计划Execution Plan Analysis
执行计划分析
Key Operators to Watch
需要关注的关键操作符
| Operator | Warning Sign | Action |
|---|---|---|
| Table Scan | Missing index | Add appropriate index |
| Index Scan | Non-SARGable predicate | Rewrite query |
| Key Lookup | Missing covering index | Add INCLUDE columns |
| Sort | Missing index for ORDER BY | Add sorted index |
| Hash Match | Large memory grant | Consider index |
| Spools | Repeated scans | Restructure 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 ONsql
-- 两者差异大表示统计信息存在问题
-- 检查是否需要更新统计信息:
UPDATE STATISTICS TableName WITH FULLSCAN
-- 或启用自动更新:
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS ONFinding 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 DESCsql
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 DESCStatistics 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 StatisticsNamesql
-- 更新表上的所有统计信息
UPDATE STATISTICS TableName
-- 全扫描更新(最准确)
UPDATE STATISTICS TableName WITH FULLSCAN
-- 更新特定统计信息
UPDATE STATISTICS TableName StatisticsNameAuto-Update Settings
自动更新设置
sql
-- Enable async auto-update (better for OLTP)
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS_ASYNC ONsql
-- 启用异步自动更新(更适合OLTP系统)
ALTER DATABASE YourDB SET AUTO_UPDATE_STATISTICS_ASYNC ONAdditional References
额外参考资料
For deeper coverage of performance diagnostics, see:
- - DMV queries for performance analysis
references/dmv-diagnostic-queries.md
如需深入了解性能诊断,可查看:
- - 用于性能分析的DMV查询语句 ",
references/dmv-diagnostic-queries.md