sql >> データベース >  >> RDS >> Sqlserver

SQL Server のロック タイムアウトがループ内のレコードの削除を超えました

    私は答えを見つけました:ループした削除がゴースト クリーンアップ プロシージャと競合しています。

    ニコラスの提案を使用して、 BEGIN TRANSACTION を追加しました と COMMIT . BEGIN TRY で削除ループをラップしました / BEGIN CATCH . BEGIN CATCH で 、 ROLLBACK の直前 、 sp_lock を実行しました と sp_who2 . (上記の質問にコードの変更を追加しました。)

    プロセスがブロックされたとき、次の出力が表示されました:

    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
    

    今後の参考のために、SQL Server がレコードを削除するとき、レコードにビットを設定して、単に "ゴースト レコード" としてマークします。数分ごとに、ゴースト クリーンアップと呼ばれる内部プロセスが実行され、完全に削除されたレコードのページが再利用されます (つまり、すべてのレコードはゴースト レコードです)。

    ゴースト クリーンアップ プロセスについては、この質問の ServerFault で説明されています。

    ポールですS. Randal によるゴースト クリーンアップ プロセスの説明

    トレース フラグを使用してゴースト クリーンアップ プロセスを無効にすることができます。 しかし、この場合はそうする必要はありませんでした.

    最終的に 100 ミリ秒のロック待機タイムアウトを追加しました。これにより、ゴースト レコードのクリーンアップ プロセスでロック待機タイムアウトが発生することがありますが、許容範囲内です。また、ロックのタイムアウトを最大 5 回再試行するループも追加しました。これら 2 つの変更により、私のプロセスは通常完了します。現在は、大量のデータをプッシュする非常に長いプロセスがあり、プロセスがクリーンアップする必要があるデータのテーブルまたはページ ロックを取得する場合にのみ、タイムアウトが発生します。

    2016 年 7 月 20 日編集

    最終的なコードは次のようになります:

    -- 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. GROUPBYを使用した複雑なMySQLJOIN

    2. ユーザーの友達と友達の友達を取得するSQL

    3. mysqlクエリPHP:特定のアイテムを最初にしたいので、クエリを変更して表示するアイテムの数を変更できます

    4. DjangoAdmin-ログイン