欢迎光临桐乡市华金智网
详情描述
教你如何识别SQL Server中需要添加索引的查询

一、识别高成本查询

1. 使用SQL Server内置报告

-- 查看最耗资源的查询
SELECT TOP 20
    qs.total_worker_time / qs.execution_count AS avg_cpu_time,
    qs.total_elapsed_time / qs.execution_count AS avg_duration,
    qs.total_logical_reads / qs.execution_count AS avg_logical_reads,
    qs.execution_count,
    SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
        ((CASE qs.statement_end_offset
            WHEN -1 THEN DATALENGTH(st.text)
            ELSE qs.statement_end_offset
        END - qs.statement_start_offset)/2) + 1) AS query_text,
    qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
ORDER BY avg_logical_reads DESC;

2. 查看缺失索引建议

-- 获取缺失索引信息
SELECT 
    migs.avg_total_user_cost,
    migs.avg_user_impact,
    migs.last_user_seek,
    mid.statement AS table_name,
    mid.equality_columns,
    mid.inequality_columns,
    mid.included_columns
FROM sys.dm_db_missing_index_group_stats migs
INNER JOIN sys.dm_db_missing_index_groups mig
    ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid
    ON mig.index_handle = mid.index_handle
WHERE migs.avg_user_impact > 80  -- 影响程度大于80%
ORDER BY migs.avg_user_impact DESC;

二、分析执行计划中的警告

1. 识别执行计划中的问题

-- 获取有问题的执行计划
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT
    qp.query_plan,
    st.text,
    cp.usecounts,
    cp.size_in_bytes
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 qp.query_plan.exist('//Warnings') = 1
    AND qp.query_plan.exist('//Warnings/MissingIndexes') = 1;

2. 查看具体警告类型

-- 分析执行计划中的警告详情
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
WITH XMLNAMESPACES (DEFAULT 'http://schemas.microsoft.com/sqlserver/2004/07/showplan')
SELECT 
    n.value('(@StatementText)[1]', 'VARCHAR(4000)') AS sql_text,
    n.value('(//MissingIndexGroup/@Impact)[1]', 'FLOAT') AS impact,
    DB_ID(REPLACE(REPLACE(n.value('(//MissingIndex/@Database)[1]', 'VARCHAR(128)'), 
        '[', ''), ']', '')) AS database_id,
    n.query('.') AS query_plan
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) AS qp
CROSS APPLY qp.query_plan.nodes('//StmtSimple') AS qn(n)
WHERE n.exist('//MissingIndex') = 1;

三、监控实时查询性能

1. 实时监控当前运行查询

-- 查看当前正在执行的查询
SELECT 
    r.session_id,
    r.status,
    r.cpu_time,
    r.logical_reads,
    r.writes,
    t.text AS query_text,
    p.query_plan,
    r.wait_type,
    r.wait_time
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) t
CROSS APPLY sys.dm_exec_query_plan(r.plan_handle) p
WHERE r.status = 'running'
ORDER BY r.logical_reads DESC;

2. 使用扩展事件监控

-- 创建扩展事件会话监控高成本查询
CREATE EVENT SESSION [HighCostQueries] ON SERVER 
ADD EVENT sqlserver.sql_statement_completed(
    ACTION(sqlserver.sql_text, sqlserver.plan_handle)
    WHERE ([duration] > 1000000))  -- 超过1秒的查询
ADD TARGET package0.event_file(SET filename=N'C:\temp\HighCostQueries.xel');
GO

ALTER EVENT SESSION [HighCostQueries] ON SERVER STATE = START;

四、分析索引使用情况

1. 查看索引使用统计

-- 查看索引使用情况
SELECT 
    o.name AS table_name,
    i.name AS index_name,
    i.type_desc,
    s.user_seeks,
    s.user_scans,
    s.user_lookups,
    s.user_updates,
    s.last_user_seek,
    s.last_user_scan
FROM sys.dm_db_index_usage_stats s
INNER JOIN sys.indexes i ON s.object_id = i.object_id 
    AND s.index_id = i.index_id
INNER JOIN sys.objects o ON i.object_id = o.object_id
WHERE s.database_id = DB_ID()
    AND o.type = 'U'  -- 用户表
ORDER BY s.user_seeks + s.user_scans DESC;

2. 识别未使用的索引

-- 查找可能不需要的索引
SELECT 
    o.name AS table_name,
    i.name AS index_name,
    i.type_desc,
    s.user_seeks,
    s.user_scans,
    s.user_lookups,
    s.user_updates
FROM sys.indexes i
INNER JOIN sys.objects o ON i.object_id = o.object_id
LEFT JOIN sys.dm_db_index_usage_stats s ON i.object_id = s.object_id 
    AND i.index_id = s.index_id AND s.database_id = DB_ID()
WHERE o.type = 'U'
    AND i.is_primary_key = 0
    AND i.is_unique_constraint = 0
    AND (s.user_seeks = 0 OR s.user_seeks IS NULL)
    AND (s.user_scans = 0 OR s.user_scans IS NULL)
    AND (s.user_lookups = 0 OR s.user_lookups IS NULL)
ORDER BY o.name, i.name;

五、实用诊断脚本

1. 综合诊断脚本

-- 综合诊断:高成本查询 + 缺失索引
SELECT TOP 20
    'High Cost Query' AS issue_type,
    qs.total_worker_time / qs.execution_count AS avg_cpu_time,
    qs.total_logical_reads / qs.execution_count AS avg_logical_reads,
    SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
        ((CASE qs.statement_end_offset
            WHEN -1 THEN DATALENGTH(st.text)
            ELSE qs.statement_end_offset
        END - qs.statement_start_offset)/2) + 1) AS query_text
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
WHERE qs.execution_count > 10
    AND qs.total_logical_reads / qs.execution_count > 1000

UNION ALL

SELECT TOP 20
    'Missing Index' AS issue_type,
    migs.avg_total_user_cost,
    migs.avg_user_impact,
    'Table: ' + mid.statement + 
    ' | Equality: ' + ISNULL(mid.equality_columns, '') +
    ' | Inequality: ' + ISNULL(mid.inequality_columns, '') +
    ' | Included: ' + ISNULL(mid.included_columns, '') AS details
FROM sys.dm_db_missing_index_group_stats migs
INNER JOIN sys.dm_db_missing_index_groups mig
    ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid
    ON mig.index_handle = mid.index_handle
WHERE migs.avg_user_impact > 50
ORDER BY 2 DESC;

2. 索引建议生成脚本

-- 生成索引创建语句
SELECT 
    'CREATE INDEX IX_' + 
    REPLACE(REPLACE(REPLACE(
        ISNULL(mid.equality_columns, '') + 
        ISNULL(mid.inequality_columns, ''), 
        '[', ''), ']', ''), ', ', '_') +
    '_' + CAST(migs.index_group_handle AS VARCHAR) AS index_name,
    'ON ' + mid.statement + 
    ' (' + ISNULL(mid.equality_columns, '') +
    CASE WHEN mid.equality_columns IS NOT NULL 
        AND mid.inequality_columns IS NOT NULL THEN ',' 
        ELSE '' END +
    ISNULL(mid.inequality_columns, '') + ')' +
    ISNULL(' INCLUDE (' + mid.included_columns + ')', '') +
    ';' AS create_index_statement,
    migs.avg_user_impact,
    migs.avg_total_user_cost
FROM sys.dm_db_missing_index_group_stats migs
INNER JOIN sys.dm_db_missing_index_groups mig
    ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid
    ON mig.index_handle = mid.index_handle
WHERE migs.avg_user_impact > 80
ORDER BY migs.avg_user_impact DESC;

六、最佳实践建议

添加索引前考虑:

查询频率:只对频繁执行的查询添加索引 数据修改频率:高更新频率的表需要谨慎添加索引 选择性:选择高选择性的列作为索引键 索引维护成本:考虑索引对INSERT/UPDATE/DELETE的影响 现有索引:避免创建重复或冗余的索引

监控工具推荐:

  • SQL Server Management Studio (SSMS):执行计划分析器
  • Database Engine Tuning Advisor:自动索引建议
  • SQL Server Profiler:跟踪查询性能
  • Extended Events:轻量级监控

定期维护任务:

每周分析缺失索引建议 每月检查未使用的索引 定期更新统计信息 重建/重组碎片化索引

通过这些方法和脚本,你可以系统性地识别需要添加索引的查询,并做出明智的索引优化决策。