Wie finde ich Datenbanken mit einer großen Anzahl virtueller Protokolldateien?

7

Ich habe mehrere Datenbanken auf einem Produktionsserver, die Hunderte von Gigabyte groß sind, und es werden täglich viele tausend Transaktionen ausgeführt.

Fast alle diese Datenbanken werden mithilfe der SQL Server-Spiegelung gespiegelt.

Obwohl wir die Größe der physischen Protokolldateien sorgfältig geplant haben, um sie an die erwartete Aktivität der Protokolldateien anzupassen. Gelegentlich geht etwas schief und die Protokolle müssen über unser vorhergesagtes Maximum hinaus wachsen. Wir haben alle Protokolldateien so eingerichtet, dass sie um 8192 MB wachsen. Wenn die Datenbank jedoch unter dem Druck steht, die Protokolldatei zu vergrößern, wird das Protokoll manchmal nur in sehr kleinen Blöcken vergrößert, wodurch in einigen Fällen Hunderttausende von virtuellen Protokolldateien (VLFs) erstellt werden. .

Ich habe verstanden, wie wichtig es ist, die Anzahl der VLFs niedrig zu halten, als eine unserer Produktionsdatenbanken mit über 200.000 VLFs unerwartet wiederhergestellt wurde. Die Wiederherstellung dauerte mehr als 20 Stunden. Während dieser Zeit konnte ein Teil unseres Geschäfts nicht betrieben werden.

Ich benötige eine Lösung, die die Anzahl der virtuellen Protokolldateien für alle auf einem Server vorhandenen Datenbanken überwachen und eine Warn-E-Mail senden kann, wenn eine bestimmte Protokolldatei mehr als eine bestimmte Anzahl von VLFs enthält.

Ich weiß, dass DBCC LOGINFO;die Liste der VLFs zurückgegeben wird, ich möchte dies jedoch nicht manuell ausführen.

Ich habe die folgende SQL-Anweisung erstellt, die eine schöne Tabelle mit den Datenbanken zusammen mit der Anzahl der VLFs erstellt. Ich weiß jedoch nicht, wie ich dies in einen SQL Agent-Job einfügen kann, um unserem Team eine E-Mail zu senden, wenn eine Datenbank eine "x" -Nummer hat von VLFs.

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;

Der Versuch, dies mit INSERT...EXECin eine Tabelle zum Anhängen an eine E-Mail mit auszugeben, sp_send_dbmailerweist sich als zwecklos.

SQL Server drosselt mit:

An INSERT EXEC statement cannot be nested.
Msg 8164, Level 16, State 1, Line 5
Max Vernon
quelle
Ich habe mir simple-talk.com/sql/database-administration/… angesehen. - Ich möchte nicht wirklich nach Excel exportieren oder so etwas. Ich möchte, dass ein SQL Agent-Job die Arbeit erledigt!
Max Vernon
Ihr aktueller Cursor hat dieselben Probleme wie sp_msforeachdb - Sie müssen ihn als LOCAL FAST_FORWARD deklarieren, um ein mögliches Überspringen von Datenbanken zu vermeiden.
Aaron Bertrand

Antworten:

10

Hier ist ein etwas einfacherer Ansatz, der den Cursor und die verschachtelte Ausführung vermeidet:

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;
Aaron Bertrand
quelle
Danke Aaron! Ich kann nicht glauben, wie viel sauberer das ist. Ich habe versucht herauszufinden, wie man @sqlaus einer SELECTAussage baut - und dabei das Offensichtliche völlig übersehen SELECT @sql = @sql + . Manchmal hasse ich meinen Entwickler- / Verfahrenshintergrund! Ich denke immer, es ist besser zu verwenden, SET @sql = @sql +während SELECT @sql =Typkonstrukte mich aus irgendeinem seltsamen Grund erschaudern lassen. Nicht mehr so ​​!!!
Max Vernon
2
@ MaxVernon Ich werde es eine Zeile einfacher machen. Der Befehl USE ist nicht erforderlich.
Aaron Bertrand
2
Und eine Einschränkung: Dies wird unterbrochen, wenn Sie eine Datenbank mit einem Apostroph benannt haben, z O'Brien. Sie können das beheben, aber es ist hässlich - einfacher, Datenbanken keine schlecht ausgewählten Namen zu geben. :-)
Aaron Bertrand
Schön - Ihre Aussage ist sogar abwärtskompatibel!
Max Vernon
0

Sie können eine temporäre Tabelle erstellen und in der dynamischen SQL verwenden. Ich habe mich nicht an Ihrem Code orientiert, aber dies zeigt das Konzept:

create table #mainTab 
(
    DatabaseName varchar(max)
    ,FileId int
    , FileSize nvarchar(255)
    , StartOffset nvarchar(255)
    , FSeqNo nvarchar(255)
    , Status int
    , Parity int
    , CreateLSN nvarchar(255)
);

exec sp_msforeachdb '
    DECLARE @tab TABLE 
    (
        FileId int
        , FileSize nvarchar(255)
        , StartOffset nvarchar(255)
        , FSeqNo nvarchar(255)
        , Status int
        , Parity int
        , CreateLSN nvarchar(255)
    );
    use ?
    insert into @tab exec(''DBCC LOGINFO'')

    insert into #mainTab
    select db_name(), * from @tab'

select DatabaseName, COUNT(*) from #mainTab group by DatabaseName
cfradenburg
quelle
2
Seien Sie sehr vorsichtig sp_msforeachdb- siehe hier und hier .
Aaron Bertrand
Guter Punkt. Ich verwende Ihre Version, in der ich arbeite, und habe hier gerade eine Demo gemacht, aber das ist gut, um darauf hinzuweisen.
Cfradenburg