Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Zeitüberschreitung für SQL Server-Sperre beim Löschen von Datensätzen in einer Schleife überschritten

Ich habe die Antwort gefunden:Meine Löschschleife steht im Konflikt mit dem Geister-Bereinigungsvorgang.

Auf Nicholas' Vorschlag hin habe ich ein BEGIN TRANSACTION hinzugefügt und ein COMMIT . Ich habe die Löschschleife in einen BEGIN TRY eingeschlossen / BEGIN CATCH . Im BEGIN CATCH , direkt vor einem ROLLBACK , ich habe sp_lock ausgeführt und sp_who2 . (Ich habe die Codeänderungen in der obigen Frage hinzugefügt.)

Als mein Prozess blockiert wurde, sah ich die folgende Ausgabe:

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

Für die Zukunft:Wenn SQL Server Datensätze löscht, legt er ein bisschen darauf fest, sie einfach als „Geisterdatensätze“ zu markieren. Alle paar Minuten wird ein interner Prozess namens Ghost Cleanup ausgeführt, um Seiten von Datensätzen zurückzugewinnen, die vollständig gelöscht wurden (d. h. alle Datensätze sind Ghost-Datensätze).

Der Ghost-Bereinigungsprozess wurde in dieser Frage auf ServerFault diskutiert.

Hier ist Paul S. Randals Erklärung des Ghost Cleanup-Prozesses.

Es ist möglich, den Geister-Bereinigungsprozess mit einem Trace-Flag zu deaktivieren. Aber das musste ich in diesem Fall nicht.

Am Ende fügte ich ein Sperrwartezeitlimit von 100 ms hinzu. Dies führt zu gelegentlichen Wartezeitüberschreitungen bei der Geisterdatensatzbereinigung, aber das ist akzeptabel. Ich habe auch eine Our-Schleife hinzugefügt, die Sperrzeitüberschreitungen bis zu 5 Mal wiederholt. Mit diesen beiden Änderungen ist mein Prozess jetzt normalerweise abgeschlossen. Jetzt kommt es nur noch zu einem Timeout, wenn ein sehr langer Prozess viele Daten herumschiebt, der Tabellen- oder Seitensperren für die Daten erwirbt, die mein Prozess bereinigen muss.

BEARBEITEN 20.07.2016

Der endgültige Code sieht folgendermaßen aus:

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