Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Délai d'attente de verrouillage de SQL Server dépassé Suppression d'enregistrements dans une boucle

J'ai trouvé la réponse :ma suppression en boucle est en conflit avec la procédure de nettoyage des fantômes.

En utilisant la suggestion de Nicholas, j'ai ajouté un BEGIN TRANSACTION et un COMMIT . J'ai enveloppé la boucle de suppression dans un BEGIN TRY / BEGIN CATCH . Dans le BEGIN CATCH , juste avant un ROLLBACK , j'ai lancé sp_lock et sp_who2 . (J'ai ajouté les modifications de code dans la question ci-dessus.)

Lorsque mon processus s'est bloqué, j'ai vu le résultat suivant :

spid   dbid   ObjId       IndId  Type Resource                         Mode     Status
------ ------ ----------- ------ ---- -------------------------------- -------- ------
20     2      1401108082  0      TAB                                   IX       GRANT
20     2      1401108082  1      PAG  1:102368                         X        GRANT

SPID  Status     Login HostName BlkBy DBName Command       CPUTime DiskIO
----  ---------- ----- -------- ----- ------ ------------- ------- ------
20    BACKGROUND sa    .        .     tempdb GHOST CLEANUP 31      0

Pour référence future, lorsque SQL Server supprime des enregistrements, il les définit un peu pour les marquer simplement comme "enregistrements fantômes". Toutes les quelques minutes, un processus interne appelé nettoyage fantôme s'exécute pour récupérer les pages d'enregistrements qui ont été entièrement supprimés (c'est-à-dire que tous les enregistrements sont des enregistrements fantômes).

Le processus de nettoyage fantôme a été discuté sur ServerFault dans cette question.

Voici Paul Explication de S. Randal sur le processus de nettoyage des fantômes.

Il est possible de désactiver le processus de nettoyage fantôme avec un indicateur de trace. Mais je n'avais pas à le faire dans ce cas.

J'ai fini par ajouter un délai d'attente de verrouillage de 100 ms. Cela provoque des délais d'attente de verrouillage occasionnels dans le processus de nettoyage des enregistrements fantômes, mais cela est acceptable. J'ai également ajouté une boucle our qui réessaye de verrouiller les délais d'attente jusqu'à 5 fois. Avec ces deux changements, mon processus se termine maintenant généralement. Maintenant, il n'obtient un délai d'expiration que s'il y a un très long processus poussant beaucoup de données qui acquiert des verrous de table ou de page sur les données que mon processus doit nettoyer.

MODIFIER 2016-07-20

Le code final ressemble à ceci :

-- Do not block long if records are locked.
SET LOCK_TIMEOUT 100

-- This process volunteers to be a deadlock victim in the case of a deadlock.
SET DEADLOCK_PRIORITY LOW

DECLARE @Error BIT
SET @Error = 0

DECLARE @ErrMsg VARCHAR(1000)
DECLARE @DeletedCount INT
SELECT @DeletedCount = 0

DECLARE @LockTimeoutCount INT
SET @LockTimeoutCount = 0

DECLARE @ContinueDeleting BIT,
    @LastDeleteSuccessful BIT

SET @ContinueDeleting = 1
SET @LastDeleteSuccessful = 1

WHILE @ContinueDeleting = 1
BEGIN
    DECLARE @RowCount INT
    SET @RowCount = 0

    BEGIN TRY

        BEGIN TRANSACTION

        -- The READPAST below attempts to skip over locked records.
        -- However, it might still cause a lock wait error (1222) if a page or index is locked, because the delete has to modify indexes.
        -- The threshold for row lock escalation to table locks is around 5,000 records,
        -- so keep the deleted number smaller than this limit in case we are deleting a large chunk of data.
        -- Table name, field, and value are all set dynamically in the actual script.
        SET @SQL = N'DELETE TOP (1000) MyTable WITH(ROWLOCK, READPAST) WHERE MyField = SomeValue' 
        EXEC sp_executesql @SQL, N'@ProcGuid uniqueidentifier', @ProcGUID

        SET @RowCount = @@ROWCOUNT

        COMMIT

        SET @LastDeleteSuccessful = 1

        SET @DeletedCount = @DeletedCount + @RowCount
        IF @RowCount = 0
        BEGIN
            SET @ContinueDeleting = 0
        END

    END TRY
    BEGIN CATCH

        IF @@TRANCOUNT > 0
            ROLLBACK

        IF Error_Number() = 1222 -- Lock timeout
        BEGIN

            IF @LastDeleteSuccessful = 1
            BEGIN
                -- If we hit a lock timeout, and we had already deleted something successfully, try again.
                SET @LastDeleteSuccessful = 0
            END
            ELSE
            BEGIN
                -- The last delete failed, too.  Give up for now.  The job will run again shortly.
                SET @ContinueDeleting = 0
            END
        END
        ELSE -- On anything other than a lock timeout, report an error.
        BEGIN       
            SET @ErrMsg = 'An error occurred cleaning up data.  Table: MyTable Column: MyColumn Value: SomeValue.  Message: ' + ERROR_MESSAGE() + ' Error Number: ' + CONVERT(VARCHAR(20), ERROR_NUMBER()) + ' Line: ' + CONVERT(VARCHAR(20), ERROR_LINE())
            PRINT @ErrMsg -- this error message will be included in the SQL Server job history
            SET @Error = 1
            SET @ContinueDeleting = 0
        END

    END CATCH

END

IF @Error <> 0
    RAISERROR('Not all data could be cleaned up.  See previous messages.', 16, 1)