sql >> Databasteknik >  >> RDS >> Sqlserver

SQL Server Lock Timeout överskred Ta bort poster i en loop

Jag har hittat svaret:min loopade borttagning är i konflikt med spökrensningsproc.

Med hjälp av Nicholas förslag lade jag till en BEGIN TRANSACTION och en COMMIT . Jag slog raderingsslingan i en BEGIN TRY / BEGIN CATCH . I BEGIN CATCH , precis före en ROLLBACK , jag körde sp_lock och sp_who2 . (Jag lade till kodändringarna i frågan ovan.)

När min process blockerades såg jag följande utdata:

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 framtida referens, när SQL Server tar bort poster, ställer den in en bit på dem för att bara markera dem som "spökposter". Med några minuters mellanrum körs en intern process som kallas spökrensning för att återta sidor med poster som har raderats helt (dvs. alla poster är spökposter).

Spökrensningsprocessen diskuterades på ServerFault i den här frågan.

Här är Paul S. Randals förklaring av spökrensningsprocessen.

Det är möjligt att inaktivera spökrensningsprocessen med en spårningsflagga. Men jag behövde inte göra det i det här fallet.

Det slutade med att jag lade till en låsväntetid på 100 ms. Detta orsakar tillfälliga tidsgränser för låsväntetider i spökpostrensningsprocessen, men det är acceptabelt. Jag har också lagt till en vår loop som försöker låsa timeouts igen upp till 5 gånger. Med dessa två förändringar brukar min process nu slutföras. Nu får det bara en timeout om det finns en mycket lång process som driver runt massor av data som skaffar tabell- eller sidlås på data som min process behöver rensa upp.

REDIGERA 2016-07-20

Den slutliga koden ser ut så här:

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


  1. Missförstår jag anslutningar?

  2. Postgres pg_dump dumpar databasen i en annan ordning varje gång

  3. Bästa metoder:Spåra bannervisningar

  4. Rails råfråga för csv-format, som ska returneras via styrenhet