如何找到具有大量虚拟日志文件的数据库?

Han*_*non 7 sql-server-2005 sql-server transaction-log

我在生产服务器上有几个数百 GB 的数据库,每天都有成千上万的事务通过它们运行。

几乎所有这些数据库都使用 SQL Server 镜像进行镜像。

尽管我们已经仔细规划了物理日志文件大小以匹配预期的日志文件活动;偶尔会出现问题,日志需要增长到超出我们预测的最大值。我们已将所有日志文件设置为增长 8192MB,但是当数据库面临增长日志文件的压力时,它有时只会以非常小的块增长日志,从而在某些情况下创建数十万个虚拟日志文件 (VLF) .

当我们的一个生产数据库意外恢复超过 200,000 个 VLF 时,我开始理解保持低 VLF 数量的重要性。恢复需要 20 多个小时;在此期间,我们的部分业务无法运营。

我需要一个可以监控服务器上所有数据库的虚拟日志文件数量的解决方案,如果任何特定日志文件的 VLF 数量超过给定数量,则发送警报电子邮件。

我知道DBCC LOGINFO;返回 VLF 列表,但是,我不想手动运行它。

我创建了以下 SQL 语句,该语句创建了一个列出数据库以及 VLF 数量的漂亮表,但是,我不知道如何将其放入 SQL 代理作业中,以便在任何数据库的数量超过“x”时向我们的团队发送电子邮件的 VLF。

DECLARE @cmd_per_database_prefix nvarchar(max);
DECLARE @cmd_per_database nvarchar(max);
DECLARE @database_name nvarchar(255);
SET @cmd_per_database = '';
SET @cmd_per_database_prefix = 
'
    SET NOCOUNT ON;
    DECLARE @vlf_count_table TABLE (database_name nvarchar(255), vlf_count int);
    DECLARE @params nvarchar(max);
    DECLARE @db_name nvarchar(255);
    DECLARE @vlf_count int;
    SET @params = ''@db_name nvarchar(255) OUTPUT, @vlf_count int OUTPUT'';
    DECLARE @cmdGetVLFCount nvarchar(max);
    SET @cmdGetVLFCount = 
    ''
        DECLARE @tab TABLE 
        (
            FileId int
            , FileSize nvarchar(255)
            , StartOffset nvarchar(255)
            , FSeqNo nvarchar(255)
            , Status int
            , Parity int
            , CreateLSN nvarchar(255)
        );
        DECLARE @cmd nvarchar(max);
        SET @cmd = ''''DBCC LOGINFO;'''';
        INSERT INTO @tab
        EXEC sp_executesql @cmd;
        SET @db_name = db_name();
        SET @vlf_count = (select count(*) FROM @tab t);
    '';
';
DECLARE cur CURSOR FOR
SELECT NAME 
FROM sys.databases
WHERE database_id > 4 and state=0;
OPEN cur;
FETCH NEXT FROM cur INTO @database_name;
WHILE @@FETCH_STATUS = 0
BEGIN
    SET @cmd_per_database = @cmd_per_database + 
    '
        EXEC ' + @database_name + '.sys.sp_executesql @cmdGetVLFCount, @params, @db_name OUTPUT, @vlf_count OUTPUT;
        INSERT INTO @vlf_count_table (database_name, vlf_count) VALUES (@db_name, @vlf_count);
    ';
    FETCH NEXT FROM cur INTO @database_name;
END
SET @cmd_per_database = @cmd_per_database_prefix + @cmd_per_database + char(13) + char(10) + 'select * from @vlf_count_table t;';
EXEC sp_executesql @cmd_per_database;
CLOSE cur;
DEALLOCATE cur;
Run Code Online (Sandbox Code Playgroud)

尝试将其输出INSERT...EXEC到表格中以附加到电子邮件中sp_send_dbmail被证明是徒劳的。

SQL Server 窒息:

An INSERT EXEC statement cannot be nested.
Msg 8164, Level 16, State 1, Line 5
Run Code Online (Sandbox Code Playgroud)

Aar*_*and 10

这是一个稍微简单的方法,可以避免游标和嵌套的 exec:

SET NOCOUNT ON;

CREATE TABLE #to
(
  DBName SYSNAME,
  FileCount INT
);

DECLARE @v INT;
SELECT @v = CONVERT(INT, PARSENAME(CONVERT(VARCHAR(32), 
  SERVERPROPERTY('ProductVersion')), 4));

DECLARE @sql NVARCHAR(MAX);

SET @sql = N'CREATE TABLE #ti
  (
    ' + CASE WHEN @v >= 11 THEN 'RecoveryUnitId INT,' ELSE '' END + '    
    FileId int
    , FileSize nvarchar(255)
    , StartOffset nvarchar(255)
    , FSeqNo nvarchar(255)
    , Status int
    , Parity int
    , CreateLSN nvarchar(255)
);';

SELECT @sql = @sql + N'
  INSERT #ti EXEC ' + QUOTENAME(name) 
    + '.sys.sp_executesql N''DBCC LOGINFO WITH NO_INFOMSGS'';
  INSERT #to(DBName,FileCount) SELECT N''' + name + ''', COUNT(*) FROM #ti;
  TRUNCATE TABLE #ti;'
FROM sys.databases
WHERE database_id > 4 AND [state] = 0;

EXEC sp_executesql @sql;

SELECT DBName, FileCount FROM #to -- WHERE FileCount > [some threshold];

DROP TABLE #to;
Run Code Online (Sandbox Code Playgroud)

  • 还有一个警告:如果你用撇号命名数据库,这会中断,例如`O'Brien`。你可以解决这个问题,但它很丑 - 更容易只是不给数据库选择错误的名称。:-) (2认同)