Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Timeout blocco SQL Server superato Eliminazione record in un ciclo

Ho trovato la risposta:la mia eliminazione in loop è in conflitto con il processo di pulizia fantasma.

Utilizzando il suggerimento di Nicholas, ho aggiunto un BEGIN TRANSACTION e un COMMIT . Ho racchiuso il ciclo di eliminazione in un BEGIN TRY / BEGIN CATCH . Nel BEGIN CATCH , subito prima di un ROLLBACK , ho eseguito sp_lock e sp_who2 . (Ho aggiunto le modifiche al codice nella domanda precedente.)

Quando il mio processo si è bloccato, ho visto il seguente output:

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

Per riferimento futuro, quando SQL Server elimina i record, imposta un po' su di essi per contrassegnarli semplicemente come "record fantasma". Ogni pochi minuti, viene eseguito un processo interno chiamato pulizia fantasma per recuperare pagine di record che sono stati completamente eliminati (ovvero tutti i record sono record fantasma).

Il processo di pulizia dei fantasmi è stato discusso su ServerFault in questa domanda.

Ecco Paul La spiegazione di S. Randal del processo di pulizia dei fantasmi.

È possibile disabilitare il processo di pulizia fantasma con un flag di traccia. Ma in questo caso non dovevo farlo.

Ho finito per aggiungere un timeout di attesa del blocco di 100 ms. Ciò causa timeout di attesa di blocco occasionali nel processo di pulizia del record fantasma, ma ciò è accettabile. Ho anche aggiunto un nostro ciclo che riprova i timeout di blocco fino a 5 volte. Con queste due modifiche, il mio processo ora di solito viene completato. Ora ottiene un timeout solo se c'è un processo molto lungo che spinge molti dati in giro che acquisisce blocchi di tabelle o pagine sui dati che il mio processo deve ripulire.

MODIFICA 20-07-2016

Il codice finale è simile al seguente:

-- 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)