邳州市本地咨询网

SQL Server跟踪自动统计信息更新实战指南

2026-03-26 09:04:02 浏览次数:2
详细信息

SQL Server 跟踪自动统计信息更新实战指南

一、自动统计信息更新机制

1.1 触发条件
-- 查看统计信息更新设置
SELECT 
    name AS 数据库名,
    is_auto_update_stats_on AS 自动更新统计信息,
    is_auto_update_stats_async_on AS 异步更新统计信息
FROM sys.databases
WHERE name = DB_NAME();

-- 修改设置(如果需要)
ALTER DATABASE [YourDB] 
SET AUTO_UPDATE_STATISTICS ON;
ALTER DATABASE [YourDB] 
SET AUTO_UPDATE_STATISTICS_ASYNC OFF; -- 通常建议关闭异步
1.2 阈值规则

SQL Server使用以下阈值决定是否更新统计信息:

二、监控自动统计信息更新

2.1 使用系统视图监控
-- 查看最近统计信息更新情况
SELECT 
    OBJECT_NAME(s.object_id) AS 表名,
    s.name AS 统计信息名称,
    STATS_DATE(s.object_id, s.stats_id) AS 最后更新日期,
    s.auto_created AS 是否自动创建,
    s.user_created AS 是否用户创建,
    s.no_recompute AS 是否禁止自动更新
FROM sys.stats s
WHERE OBJECT_NAME(s.object_id) IN ('YourTable1', 'YourTable2')
ORDER BY STATS_DATE(s.object_id, s.stats_id) DESC;

-- 查看统计信息修改计数器
SELECT 
    OBJECT_NAME(object_id) AS 表名,
    rowmodctr AS 修改计数器,
    STATS_DATE(object_id, stats_id) AS 最后更新
FROM sys.sysindexes
WHERE id = OBJECT_ID('YourTable')
AND indid < 2; -- 只查看表级统计信息
2.2 使用扩展事件跟踪(推荐)
-- 创建扩展事件会话跟踪统计信息更新
CREATE EVENT SESSION [Track_Stats_Updates] 
ON SERVER 
ADD EVENT sqlserver.auto_stats (
    ACTION (
        sqlserver.client_app_name,
        sqlserver.client_hostname,
        sqlserver.database_name,
        sqlserver.sql_text,
        sqlserver.username
    )
    WHERE ([database_id] = DB_ID('YourDB'))
)
ADD TARGET package0.event_file (
    SET filename = N'D:\XEvents\StatsUpdates.xel'
)
WITH (
    MAX_MEMORY = 4096 KB,
    EVENT_RETENTION_MODE = ALLOW_SINGLE_EVENT_LOSS,
    MAX_DISPATCH_LATENCY = 30 SECONDS,
    MAX_EVENT_SIZE = 0 KB,
    MEMORY_PARTITION_MODE = NONE,
    TRACK_CAUSALITY = OFF,
    STARTUP_STATE = ON
);

-- 启动事件会话
ALTER EVENT SESSION [Track_Stats_Updates] 
ON SERVER STATE = START;

-- 查看收集的数据
SELECT 
    event_data.value('(event/@name)[1]', 'varchar(50)') AS event_name,
    event_data.value('(event/@timestamp)[1]', 'datetime2') AS event_time,
    event_data.value('(event/data[@name="database_name"]/value)[1]', 'varchar(100)') AS database_name,
    event_data.value('(event/data[@name="object_id"]/value)[1]', 'int') AS object_id,
    OBJECT_NAME(event_data.value('(event/data[@name="object_id"]/value)[1]', 'int'),
                event_data.value('(event/data[@name="database_id"]/value)[1]', 'int')) AS table_name,
    event_data.value('(event/data[@name="statistics_list"]/value)[1]', 'varchar(max)') AS statistics_updated,
    event_data.value('(event/action[@name="sql_text"]/value)[1]', 'varchar(max)') AS sql_text
FROM (
    SELECT 
        CAST(event_data AS XML) AS event_data
    FROM sys.fn_xe_file_target_read_file(
        'D:\XEvents\StatsUpdates*.xel', 
        NULL, NULL, NULL)
) AS EventData
ORDER BY event_time DESC;
2.3 使用SQL Trace/Profiler 打开SQL Server Profiler 选择模板:Standard (默认) 事件选择: 筛选特定数据库

三、性能影响分析

3.1 识别统计信息更新导致的阻塞
-- 查看当前统计信息更新相关的等待
SELECT 
    wait_type,
    waiting_tasks_count,
    wait_time_ms,
    max_wait_time_ms,
    signal_wait_time_ms
FROM sys.dm_os_wait_stats
WHERE wait_type LIKE '%STATS%'
ORDER BY wait_time_ms DESC;

-- 实时监控统计信息更新活动
SELECT 
    er.session_id,
    er.status,
    er.command,
    er.wait_type,
    er.wait_time,
    er.last_wait_type,
    er.blocking_session_id,
    OBJECT_NAME(st.objectid, st.dbid) AS 对象名,
    st.text AS SQL语句
FROM sys.dm_exec_requests er
CROSS APPLY sys.dm_exec_sql_text(er.sql_handle) st
WHERE er.command LIKE '%UPDATE STATS%' 
   OR er.wait_type LIKE '%STATS%';
3.2 分析更新频率和耗时
-- 使用查询存储分析(SQL Server 2016+)
SELECT 
    qsrs.start_time,
    qsrs.end_time,
    qsrs.avg_duration / 1000 AS avg_duration_sec,
    qsqt.query_sql_text
FROM sys.query_store_runtime_stats qsrs
JOIN sys.query_store_runtime_stats_interval qsrsi 
    ON qsrs.runtime_stats_interval_id = qsrsi.runtime_stats_interval_id
JOIN sys.query_store_plan qsp 
    ON qsrs.plan_id = qsp.plan_id
JOIN sys.query_store_query qsq 
    ON qsp.query_id = qsq.query_id
JOIN sys.query_store_query_text qsqt 
    ON qsq.query_text_id = qsqt.query_text_id
WHERE qsqt.query_sql_text LIKE '%UPDATE STATISTICS%'
   OR qsqt.query_sql_text LIKE '%sp_updatestats%'
ORDER BY qsrs.start_time DESC;

四、最佳实践配置

4.1 调整统计信息更新策略
-- 1. 为关键大表调整异步更新
ALTER DATABASE [YourDB] 
SET AUTO_UPDATE_STATISTICS_ASYNC ON;

-- 2. 禁用特定统计信息的自动更新
UPDATE STATISTICS [Schema].[TableName] [StatsName] 
WITH NORECOMPUTE;

-- 3. 重新启用自动更新
UPDATE STATISTICS [Schema].[TableName] [StatsName] 
WITH RECOMPUTE;

-- 4. 设置采样率(SQL Server 2016+)
UPDATE STATISTICS [Schema].[TableName] [StatsName] 
WITH SAMPLE 50 PERCENT, PERSIST_SAMPLE_PERCENT = ON;
4.2 维护计划优化
-- 创建自定义统计信息维护计划
USE [msdb];
GO

-- 创建作业
EXEC dbo.sp_add_job
    @job_name = N'Custom_Stats_Maintenance',
    @enabled = 1;

-- 创建作业步骤:更新过期的统计信息
EXEC sp_add_jobstep
    @job_name = N'Custom_Stats_Maintenance',
    @step_name = N'Update_Outdated_Stats',
    @subsystem = N'TSQL',
    @command = N'
DECLARE @UpdateStatsSQL NVARCHAR(MAX) = N'''';
SELECT @UpdateStatsSQL = @UpdateStatsSQL + 
    N''UPDATE STATISTICS ['' + s.name + N''].['' + t.name + N''] ['' + st.name + N''] 
    WITH FULLSCAN, PERSIST_SAMPLE_PERCENT = ON;'' + CHAR(13)
FROM sys.stats st
JOIN sys.tables t ON st.object_id = t.object_id
JOIN sys.schemas s ON t.schema_id = s.schema_id
CROSS APPLY sys.dm_db_stats_properties(st.object_id, st.stats_id) sp
WHERE 
    t.is_ms_shipped = 0
    AND DATEDIFF(HOUR, sp.last_updated, GETDATE()) > 24  -- 超过24小时未更新
    AND sp.modification_counter > 
        CASE 
            WHEN sp.rows < 500 THEN 500
            ELSE 500 + (0.20 * sp.rows)
        END
ORDER BY sp.modification_counter DESC;

-- 限制每次最多更新10个统计信息
SET @UpdateStatsSQL = (
    SELECT TOP 10 
        REPLACE(REPLACE(sql, N''WITH FULLSCAN'', N''WITH SAMPLE 50 PERCENT''), 
                N''PERSIST_SAMPLE_PERCENT = ON'', N'''')
    FROM (VALUES (1, @UpdateStatsSQL)) AS t(id, sql)
    ORDER BY id
);

IF @UpdateStatsSQL IS NOT NULL
    EXEC sp_executesql @UpdateStatsSQL;',
    @database_name = N'YourDB';

五、故障排除

5.1 常见问题及解决方案
-- 问题1:统计信息更新过于频繁
-- 解决方案:增加修改阈值
DBCC TRACEON (2371, -1); -- 启用可变的统计信息更新阈值

-- 问题2:统计信息更新导致性能下降
-- 监控并手动安排在非高峰时段更新
SELECT 
    t.name AS 表名,
    s.name AS 统计信息名,
    sp.last_updated,
    sp.rows,
    sp.modification_counter,
    sp.modification_counter * 100.0 / NULLIF(sp.rows, 0) AS 修改百分比
FROM sys.stats s
JOIN sys.tables t ON s.object_id = t.object_id
CROSS APPLY sys.dm_db_stats_properties(s.object_id, s.stats_id) sp
WHERE 
    t.is_ms_shipped = 0
    AND sp.modification_counter > 
        CASE 
            WHEN sp.rows < 500 THEN 500
            ELSE 500 + (0.20 * sp.rows)
        END
ORDER BY 修改百分比 DESC;

-- 问题3:统计信息过期但未自动更新
-- 强制更新特定表的统计信息
EXEC sp_updatestats 
    @resample = 'RESAMPLE';  -- 保持现有采样率

-- 或者手动更新
UPDATE STATISTICS [Schema].[TableName] 
WITH FULLSCAN, PERSIST_SAMPLE_PERCENT = ON;
5.2 自动化监控脚本
-- 创建监控存储过程
CREATE PROCEDURE dbo.Monitor_Stats_Health
AS
BEGIN
    SET NOCOUNT ON;

    -- 创建监控表(如果不存在)
    IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Stats_Monitoring_History')
    BEGIN
        CREATE TABLE dbo.Stats_Monitoring_History (
            monitor_date DATETIME DEFAULT GETDATE(),
            database_name NVARCHAR(128),
            table_name NVARCHAR(128),
            stats_name NVARCHAR(128),
            last_updated DATETIME,
            modification_counter BIGINT,
            rows BIGINT,
            is_auto_updated BIT,
            modification_percentage DECIMAL(5,2)
        );
        CREATE INDEX IX_StatsHistory ON dbo.Stats_Monitoring_History(monitor_date, database_name);
    END

    -- 收集统计信息健康状态
    DECLARE @SQL NVARCHAR(MAX) = N'';

    SELECT @SQL = @SQL + 
        N'INSERT INTO dbo.Stats_Monitoring_History 
        (database_name, table_name, stats_name, last_updated, 
         modification_counter, rows, is_auto_updated, modification_percentage)
        SELECT 
            DB_NAME() AS database_name,
            OBJECT_SCHEMA_NAME(st.object_id) + ''.'' + OBJECT_NAME(st.object_id),
            st.name,
            sp.last_updated,
            sp.modification_counter,
            sp.rows,
            st.no_recompute AS is_auto_updated,
            CASE 
                WHEN sp.rows = 0 THEN 0
                ELSE (sp.modification_counter * 100.0 / sp.rows)
            END
        FROM sys.stats st
        CROSS APPLY sys.dm_db_stats_properties(st.object_id, st.stats_id) sp
        WHERE sp.modification_counter > 0
        AND sp.last_updated < DATEADD(HOUR, -1, GETDATE());' + CHAR(13);

    -- 在所有用户数据库执行
    EXEC sp_MSforeachdb '
        IF ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'')
        BEGIN
            USE [?];
            DECLARE @DBSQL NVARCHAR(MAX) = REPLACE(''?' + @SQL + ''', ''?'', DB_NAME());
            EXEC sp_executesql @DBSQL;
        END';

    -- 生成报告
    SELECT 
        CONVERT(VARCHAR(16), monitor_date, 120) AS 监控时间,
        database_name AS 数据库,
        table_name AS 表名,
        stats_name AS 统计信息,
        DATEDIFF(HOUR, last_updated, GETDATE()) AS 未更新小时数,
        modification_percentage AS 修改百分比,
        CASE 
            WHEN modification_percentage > 50 THEN '需要立即更新'
            WHEN modification_percentage > 30 THEN '建议更新'
            ELSE '正常'
        END AS 状态
    FROM dbo.Stats_Monitoring_History
    WHERE monitor_date >= DATEADD(DAY, -1, GETDATE())
    ORDER BY modification_percentage DESC;
END
GO

-- 创建作业每天运行
EXEC msdb.dbo.sp_add_job
    @job_name = N'Daily_Stats_Health_Monitor';

EXEC msdb.dbo.sp_add_jobstep
    @job_name = N'Daily_Stats_Health_Monitor',
    @step_name = N'Run_Monitoring',
    @subsystem = N'TSQL',
    @command = N'EXEC dbo.Monitor_Stats_Health;',
    @database_name = N'YourDB';

EXEC msdb.dbo.sp_add_schedule
    @schedule_name = N'Daily_8AM',
    @freq_type = 4, -- 每天
    @freq_interval = 1,
    @active_start_time = 80000; -- 08:00:00

EXEC msdb.dbo.sp_attach_schedule
    @job_name = N'Daily_Stats_Health_Monitor',
    @schedule_name = N'Daily_8AM';

六、关键建议

监控策略

优化建议

故障响应

这个指南提供了从监控到优化的完整方案。建议先实施监控部分,了解当前系统的统计信息更新模式,再根据实际情况调整优化策略。

相关推荐