Удаление строк в таблице вызывает блокировки

Я запускаю следующую команду для удаления строк в пакетах из большой таблицы (150 миллионов строк):

DECLARE @RowCount int
WHILE 1=1
    BEGIN
        DELETE TOP (10000) t1
        FROM table t1
        INNER JOIN table2 t2 ON t2.PrimaryKey = t1.PrimaryKey
        WHERE t1.YearProcessed <= 2007

        SET @RowCount = @@ROWCOUNT

        IF (@RowCount < 10000) BREAK
    END

эта таблица широко используется. Тем не менее, он удаляет записи, но он также вызывает блокировку некоторых записей, тем самым бросая ошибки пользователю (что неприемлемо в среде, в которой мы находимся).

Как удалить старые записи, не вызывая блокировок? Должен ли я уменьшить размер пакета с 10000 записей до 1000? Каким будет этот эффект размеры журнала (у нас осталось очень мало места на жестком диске для большого роста журнала).

какие предложения?

7 ответов


Я видел подобные спорадические проблемы в прошлом, где даже в небольших партиях 0f 5000 записей блокировка все равно произойдет. В нашем случае каждое удаление / обновление содержалось в собственном Begin Tran...Цикл фиксации. Чтобы исправить проблему, логика

WaitFor задержка '00:00:00:01'

был помещен в верхней части каждого цикла и что исправили проблему.


прежде всего-похоже, что ваше удаление выполняет сканирование кластеризованного индекса, я рекомендую сделать следующее:

create index [IX.IndexName] ON t1(YearProcessed, PrimaryKey)

во-вторых - есть ли какие-либо потребности присоединиться к таблице t2?

а затем используйте следующий запрос для удаления строк, предполагая, что ваш столбец PrimaryKey имеет тип INT:

declare @ids TABLE(PrimaryKey INT)
WHILE 1=1
    BEGIN
        INSERT @ids 
        SELECT top 10000 DISTINCT t1.PrimaryKey
        FROM table t1
        INNER JOIN table2 t2 ON t2.PrimaryKey = t1.PrimaryKey
        WHERE t1.YearProcessed <= 2007

        IF @@ROWCOUNT = 0 BREAK

        DELETE  t1
        WHERE PrimaryKey in (Select PrimaryKey from @ids)

        delete from @ids

    END

и не забудьте удалить таблицу t2 из join, если она не нужна

если он по-прежнему вызывает блокировки-тогда уменьшите количество строк, удаленных в каждом раунд


Я думаю, что вы на правильном пути.

посмотрите и на эти две статьи:

и:

http://www.dbforums.com/microsoft-sql-server/985516-deleting-without-locking.html

перед запуском delete проверьте план предполагаемого запроса, чтобы увидеть, если он выполняет поиск индекса для удаления или все еще делает полную таблицу сканирование / доступ.


в дополнение к другим предложениям (которые направлены на сокращение работы, выполняемой во время удаления) вы также можете настроить SQL Server, чтобы не блокировать другие читатели при выполнении удалений в таблице.

Это можно сделать с помощью "изоляции моментальных снимков", которая была введена с SQL Server 2005:

http://msdn.microsoft.com/en-us/library/ms345124%28v=sql.90%29.aspx


Если у вас есть что-либо с каскадными удалениями, убедитесь, что они индексированы.

выделение запроса на удаление и щелчок Display estimated execution plan покажет предлагаемые индексы , которые в моем случае включали некоторые каскадные удаления.

добавление индексов для тех, кто сделал удаление намного быстрее , но я все равно не буду пытаться удалить все строки сразу.


лучший способ, который я нашел, - это форма asp.net DeleteExpiredSessions . вы делаете READUNCOMMITTED select и помещаете записи во временную таблицу, чем удаляете запись с помощью курсора.

 ALTER PROCEDURE [dbo].[DeleteExpiredSessions]
    AS
        SET NOCOUNT ON
        SET DEADLOCK_PRIORITY LOW 

        DECLARE @now datetime
        SET @now = GETUTCDATE() 

        CREATE TABLE #tblExpiredSessions 
        ( 
            SessionID nvarchar(88) NOT NULL PRIMARY KEY
        )

        INSERT #tblExpiredSessions (SessionID)
            SELECT SessionID
            FROM [ASPState].dbo.ASPStateTempSessions WITH (READUNCOMMITTED)
            WHERE Expires < @now

        IF @@ROWCOUNT <> 0 
        BEGIN 
            DECLARE ExpiredSessionCursor CURSOR LOCAL FORWARD_ONLY READ_ONLY
            FOR SELECT SessionID FROM #tblExpiredSessions 

            DECLARE @SessionID nvarchar(88)

            OPEN ExpiredSessionCursor

            FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID

            WHILE @@FETCH_STATUS = 0 
                BEGIN
                    DELETE FROM [ASPState].dbo.ASPStateTempSessions WHERE SessionID = @SessionID AND Expires < @now
                    FETCH NEXT FROM ExpiredSessionCursor INTO @SessionID
                END

            CLOSE ExpiredSessionCursor

            DEALLOCATE ExpiredSessionCursor

        END 

        DROP TABLE #tblExpiredSessions

    RETURN 0   

попробуйте это,

DECLARE @RowCount int
WHILE 1=1
    BEGIN
        BEGIN TRANSACTION 
        DELETE TOP (10000) t1
        FROM table t1
        INNER JOIN table2 t2 ON t2.PrimaryKey = t1.PrimaryKey
        WHERE t1.YearProcessed <= 2007
         END TRANSACTION 
         COMMIT TRANSACTION 
        SET @RowCount = @@ROWCOUNT

        IF (@RowCount < 10000) BREAK
    END