web-dev-qa-db-fra.com

Comment puis-je suivre ce qui remplisse mon journal de transaction 'Tempdb'?

Nous avons une grande et assez bien réglée SQL Server 2016 Enterprise Edition. Compte tenu du nombre de cœurs que nous avons, notre tempdb est actuellement composé de 16 fichiers de 2 950 Mo exécutés sur un disque RAM avec un fichier journal de transaction de 16 Go. Tous les fichiers sont définis pour ne pas autogrow et, en règle générale, cela a fonctionné bien.

Nous avons récemment commencé à obtenir un "journal de transaction aléatoire pour tempdb la base de données est pleine" Erreurs. Il n'y a pas de temps défini lorsque cela se produit, il s'agit probablement d'une interaction de l'utilisateur. Étant donné que toutes les interactions sont via des procédures stockées, il est probablement un ensemble étrange de paramètres ou de données qui causent le problème, mais nous ne pouvons pas suivre exactement ce qui pourrait le causer. Toute suggestion qui pourrait nous aider à identifier le coupable est appréciée.

J'utilise les informations dans Comment identifier quelle requête remplit le journal des transactions TEMPDB? pour suivre cette descente. Et cela donne des informations précieuses, mais cela ne me donne pas un moyen de déterminer en réalité les circonstances erronées qui causent le journal de transaction à remplir.

Y a-t-il un moyen de déclencher un instantané ou de loger ce qui cause réellement le problème. Le pire des cas, je peux écrire un programme de surveillance qui pinge le serveur chaque demi-seconde et utilise des variations sur ces requêtes pour capturer tout ce qui est énorme et une transaction ouverte, mais qui ne garantit toujours pas que je tiens tout ce qui cause la question.

2
Josef

Vous pouvez utiliser une alerte d'agent SQL Server pour effectuer automatiquement une opération chaque fois que le journal de transaction traverse un seuil de pourcentage d'occasion.

À titre d'exemple, les résultats suivants courent automatiquement les résultats de l'une des questions de la réponse de Aaron Bertrand sur une question sur la manière d'identifier quelle requête remplit le journal des transactions Tempdb chaque fois que le journal de transaction TEMPDB devient 80% complet.

USE [msdb]
GO

BEGIN TRANSACTION;

DECLARE @ReturnCode INT;
DECLARE @msg nvarchar(1000);
DECLARE @jobId BINARY(16);
DECLARE @DatabaseName sysname;
DECLARE @DBAEmailAddress nvarchar(100);
DECLARE @JobName sysname;
DECLARE @JobCommand nvarchar(max);
DECLARE @PerformanceCondition nvarchar(512);

/*
    Change the parameters below to suit
*/
SET @DatabaseName = 'tempdb';
SET @DBAEmailAddress = '<email address here>';
SET @JobCommand = N'SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

DECLARE @msg_body nvarchar(max) = N'''';

;WITH s AS
(
    SELECT 
        s.session_id,
        [pages] = SUM(s.user_objects_alloc_page_count 
          + s.internal_objects_alloc_page_count) 
    FROM sys.dm_db_session_space_usage AS s
    GROUP BY s.session_id
    HAVING SUM(s.user_objects_alloc_page_count 
      + s.internal_objects_alloc_page_count) > 0
)
SELECT @msg_body = @msg_body + N''<tr><td>'' + CONVERT(nvarchar(10), s.session_id) + N''</td>'' 
    + N''<td>'' + CONVERT(nvarchar(10), s.[pages]) + N''</td>''
    + N''<td>'' + COALESCE(t.[text], N'''') + N''</td>''
    + N''<td>'' + COALESCE(NULLIF(
        SUBSTRING(
            t.[text]
            , r.statement_start_offset / 2
            , CASE WHEN r.statement_end_offset < r.statement_start_offset 
                THEN 0 
                ELSE( r.statement_end_offset - r.statement_start_offset ) / 2 
            END
          )
          , ''''
        )
    , COALESCE(t.[text], N'''')) + N''</td></tr>''
FROM s
    LEFT OUTER JOIN sys.dm_exec_requests AS r ON s.session_id = r.session_id
OUTER APPLY sys.dm_exec_sql_text(r.plan_handle) AS t
ORDER BY s.[pages] DESC;

SET @msg_body = N''<html><body><table><tr><th>session_id</th><th>pages</th><th>text</th><th>statement text</th></tr>'' + @msg_body + N''</table></body></html>'';

EXEC msdb.dbo.sp_send_dbmail @profile_name = N''DBA''
    , @recipients = N''[email protected]''
    , @subject = N''tempdb task space usage''
    , @body_format = N''HTML''
    , @body = @msg_body;
';

SET @ReturnCode = 0;

/*
    Add an operator to receive email alerts
*/
IF NOT EXISTS (
    SELECT 1
    FROM dbo.sysoperators so
    WHERE so.name = N'DBA'
    )
BEGIN
    EXEC msdb.dbo.sp_add_operator @name=N'DBA'
        , @enabled = 1
        , @email_address = @DBAEmailAddress
        , @category_name = N'[Uncategorized]';
    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;
    SET @msg = N'Added "DBA" operator.';
    PRINT @msg;
END
ELSE
BEGIN
    SET @msg = N'DBA operator already exists.';
    PRINT @msg;
END

/* Add a job category*/
IF NOT EXISTS (
    SELECT name 
    FROM msdb.dbo.syscategories 
    WHERE name = N'Reliability' 
        AND category_class = 1
    )
BEGIN
    EXEC @ReturnCode = msdb.dbo.sp_add_category @class = N'JOB'
        , @type = N'LOCAL'
        , @name = N'Reliability';
    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;
    PRINT N'Added "Reliability" job category.';
END
ELSE
BEGIN
    SET @msg = N'Job category "Reliability" already exists.';
    PRINT @msg;
END

/*
    Add a job that performs a backup of the target database's transaction log
    This should free up space in the transaction log, assuming nothing else
    is preventing re-use of virtual log files, such as transactional replication,
    Database Mirroring, participation in an Availability Group, or an open
    transaction.
*/
SET @JobName = @DatabaseName + N' - log space usage' ;
IF NOT EXISTS (
    SELECT 1
    FROM dbo.sysjobs sj
    WHERE sj.name = @JobName
    )
BEGIN
    EXEC @ReturnCode = msdb.dbo.sp_add_job @job_name = @JobName
        , @enabled = 1
        , @notify_level_eventlog = 3
        , @notify_level_email = 2
        , @notify_level_netsend = 0
        , @notify_level_page = 0
        , @delete_level = 0
        , @description = N'No description available.'
        , @category_name = N'Reliability'
        , @owner_login_name = N'sa'
        , @notify_email_operator_name = N'DBA'
        , @job_id = @jobId OUTPUT;

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;
    SET @msg = N'Added "' + @JobName + '" job.';
    PRINT @msg;

    EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id = @jobId
        , @step_name = N'email space usage report'
        , @step_id = 1
        , @cmdexec_success_code = 0
        , @on_success_action = 1
        , @on_success_step_id = 0
        , @on_fail_action = 2
        , @on_fail_step_id = 0
        , @retry_attempts = 0
        , @retry_interval = 0
        , @os_run_priority = 0
        , @subsystem = N'TSQL'
        , @command = @JobCommand
        , @database_name = @DatabaseName
        , @flags = 0;

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;
    SET @msg = N'Added "email space usage report" job step.';
    PRINT @msg;

    EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId
        , @start_step_id = 1;
    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;
    SET @msg = N'Job is configured to start at step 1.';
    PRINT @msg;

    EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId
        , @server_name = N'(local)';

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;
END 
ELSE
BEGIN
    SET @jobId = (
        SELECT sj.job_id
        FROM dbo.sysjobs sj
        WHERE sj.name = @JobName
        );
    SET @msg = N'"' + @JobName + N'" job already exists.  Using that job''s JobID.';
    PRINT @msg;
END

IF @jobId IS NOT NULL
BEGIN
    /*
        Add an alert to fire the above job whenever the Transaction log crosses 80% full
    */
    SET @JobName = @@SERVERNAME + N' ' + @DatabaseName + N' : 80pct TxLog Alert';
    IF NOT EXISTS (
        SELECT 1
        FROM dbo.sysalerts sa
        WHERE sa.name = @JobName
        )
    BEGIN
        IF SERVERPROPERTY('InstanceName') IS NOT NULL
        BEGIN
            SET @PerformanceCondition = N'MSSQL$' + CONVERT(nvarchar(128), SERVERPROPERTY('InstanceName')) + N':Databases|Percent Log Used|' + @DatabaseName + '|>|80'
        END
        ELSE
        BEGIN
            SET @PerformanceCondition = N'MSSQL:Databases|Percent Log Used|' + @DatabaseName + '|>|80';
        END
        EXEC @ReturnCode = msdb.dbo.sp_add_alert @name = @JobName
                , @message_id = 0
                , @severity = 0
                , @enabled = 1
                , @delay_between_responses = 300
                , @include_event_description_in = 7
                , @category_name = N'[Uncategorized]'
                , @performance_condition = @PerformanceCondition
                , @job_id = @jobId;

        IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;
        SET @msg = N'Added "' + @JobName + '" alert.';
        PRINT @msg;

        EXEC msdb.dbo.sp_add_notification @alert_name = @JobName
            , @operator_name = N'DBA'
            , @notification_method = 1;
        IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;
        SET @msg = N'Added job notification to email the DBA.';
        PRINT @msg;
    END
    ELSE
    BEGIN
        SET @msg = N'Alert already exists.';
        PRINT @msg;
    END
END
ELSE
BEGIN
    SET @msg = @JobName + N' not found.  Aborting.';
    RAISERROR (@msg, 14, 0) WITH NOWAIT;
END

COMMIT TRANSACTION
GOTO EndSave

QuitWithRollback:
    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION;

EndSave:
GO
2
Max Vernon