sql >> Database >  >> RDS >> Sqlserver

Time-out voor SQL Server-vergrendeling overschreden Verwijderen van records in een lus

Ik heb het antwoord gevonden:mijn lusverwijdering is in strijd met het spookopruimproces.

Op voorstel van Nicholas heb ik een BEGIN TRANSACTION . toegevoegd en een COMMIT . Ik heb de verwijderlus verpakt in een BEGIN TRY / BEGIN CATCH . In de BEGIN CATCH , vlak voor een ROLLBACK , ik heb sp_lock uitgevoerd en sp_who2 . (Ik heb de codewijzigingen in de bovenstaande vraag toegevoegd.)

Toen mijn proces blokkeerde, zag ik de volgende uitvoer:

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

Voor toekomstig gebruik, wanneer SQL Server records verwijdert, wordt er een bit op geplaatst om ze gewoon als "spookrecords" te markeren. Om de paar minuten wordt een intern proces met de naam spookopruiming uitgevoerd om pagina's met volledig verwijderde records terug te winnen (d.w.z. alle records zijn spookrecords).

Het proces voor het opschonen van de geest is in deze vraag op ServerFault besproken.

Hier is Paul S. Randal's uitleg van het spookopruimingsproces.

Het is mogelijk om het spookopruimingsproces uit te schakelen met een traceringsvlag. Maar in dit geval hoefde ik dat niet te doen.

Ik heb uiteindelijk een time-out voor de vergrendeling van 100 ms toegevoegd. Dit veroorzaakt af en toe time-outs voor het wachten op vergrendelingen in het proces voor het opschonen van spookrecords, maar dat is acceptabel. Ik heb ook een lus toegevoegd die vergrendelingstime-outs tot 5 keer opnieuw probeert. Met deze twee wijzigingen is mijn proces nu meestal voltooid. Nu krijgt het alleen een time-out als er een heel lang proces is dat veel gegevens rondduwt waardoor tabel- of paginavergrendelingen worden verkregen op de gegevens die mijn proces moet opschonen.

BEWERKEN 20-07-2016

De uiteindelijke code ziet er als volgt uit:

-- 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. Meest efficiënte aanpak voor meertalige PHP-website

  2. EnumSet toewijzen aan mysql Set met behulp van JPA 2.1

  3. SSIS-pakket mislukt met fout Als 64-bits stuurprogramma niet is geïnstalleerd, uitvoeren in 32-bits modus

  4. SQL Bulkimport vanuit CSV