Вставка большого количества записей без блокировки таблицы

Я пытаюсь вставить 1 500 000 записей в таблицу. Я сталкиваюсь с проблемами блокировки таблицы во время вставки. Поэтому я придумал следующую пакетную вставку.

DECLARE @BatchSize INT = 50000

WHILE 1 = 1
  BEGIN
      INSERT INTO [dbo].[Destination] 
                  (proj_details_sid,
                   period_sid,
                   sales,
                   units)
      SELECT TOP(@BatchSize) s.proj_details_sid,
                             s.period_sid,
                             s.sales,
                             s.units
      FROM   [dbo].[SOURCE] s
      WHERE  NOT EXISTS (SELECT 1
                         FROM   dbo.Destination d
                         WHERE  d.proj_details_sid = s.proj_details_sid
                                AND d.period_sid = s.period_sid)

      IF @@ROWCOUNT < @BatchSize
        BREAK
  END 

У меня есть кластеризованный индекс для таблицы Destination (proj_details_sid ,period_sid ). Часть NOT EXISTS предназначена только для ограничения повторной вставки вставленных записей в таблицу.

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

Примечание. Время, необходимое для пакетной вставки и без пакетной вставки, примерно одинаково.


person Pரதீப்    schedule 09.09.2016    source источник
comment
добавить with(nolock) внутри предложения exists, чтобы избежать ожидания блокировки   -  person Akshey Bhat    schedule 09.09.2016
comment
@AksheyBhat Добавление NOLOCK вызовет здесь проблемы из-за грязного чтения   -  person Pரதீப்    schedule 09.09.2016
comment
@AksheyBhat Просто любопытно, SQL будет вставлять данные в таблицу Destination, которые nolock не вступят в силу в exists clause, не так ли?   -  person Prisoner    schedule 09.09.2016
comment
@Alex with(nolock) — это подсказка таблицы, которая указывает, что запрос не должен ждать блокировки таблицы перед выполнением. SQL будет учитывать все зафиксированные и незафиксированные данные таблицы.   -  person Akshey Bhat    schedule 09.09.2016
comment
Расширение блокировок будет предпринято после снятия 5000 блокировок. Я не знаю, почему ответы до сих пор сосредоточены на части SELECT. Блокировки, снятые вновь вставленными строками в пакете, скорее всего, вызовут любые проблемы с укрупнением блокировок, а не часть SELECT. На уровне изоляции по умолчанию SELECT все равно снимет блокировку строки, как только она будет прочитана.   -  person Martin Smith    schedule 09.09.2016
comment
@MartinSmith - извините, я не совсем понял, что вы имели в виду. Не могли бы вы уточнить это.   -  person Pரதீப்    schedule 09.09.2016
comment
Этот вопрос похож на ваш вопрос: stackoverflow.com/questions/2857320/   -  person Prisoner    schedule 09.09.2016
comment
@AksheyBhat, автор msdn.microsoft.com/en-us/library/ms187373. aspx, он указывает READUNCOMMITTED and NOLOCK cannot be specified for tables modified by insert, update, or delete operations, поэтому он не будет применять таблицу в exists clause?   -  person Prisoner    schedule 09.09.2016
comment
@Alex Алекс, разве это не относится только к операторам обновления и удаления? оператор select есть только в предложении exists   -  person Akshey Bhat    schedule 09.09.2016
comment
RE: уточнение - если ваши недавно вставленные строки защищены блокировками строк, вам нужно сделать размер пакета 5000, а не 50 000, чтобы предотвратить укрупнение блокировки.   -  person Martin Smith    schedule 09.09.2016
comment
@AksheyBhat На этой странице полное предложение должно быть READUNCOMMITTED and NOLOCK cannot be specified for tables modified by insert, update, or delete operations. The SQL Server query optimizer ignores the READUNCOMMITTED and NOLOCK hints in the FROM clause that apply to the target table of an UPDATE or DELETE statement.. И exists clause должен быть частью from clause?   -  person Prisoner    schedule 09.09.2016
comment
Если ваш кластерный индекс уникален, вы можете избавиться от EXISTS и использовать вместо него WITH IGNORE_DUP_KEY, верно? Я думаю, что это было бы более эффективным, а также. См. stackoverflow.com/a/2513187/78522.   -  person Patrick Honorez    schedule 09.09.2016
comment
@MartinSmith - когда я уменьшаю размер пакета, вставка становится очень медленной   -  person Pரதீப்    schedule 10.09.2016


Ответы (4)


Расширение блокировки вряд ли вообще связано с SELECT частью вашего заявления.

Это естественное следствие вставки большого количества строк.

Расширение блокировки запускается, если расширение блокировки не отключено для таблицы с помощью параметра ALTER TABLE SET LOCK_ESCALATION и при наличии одного из следующих условий:

  • Одна инструкция Transact-SQL устанавливает не менее 5000 блокировок на одну неразделенную таблицу или индекс.
  • Одна инструкция Transact-SQL устанавливает не менее 5000 блокировок на один раздел многораздельной таблицы, а для параметра ALTER TABLE SET LOCK_ESCALATION установлено значение AUTO.
  • Количество блокировок в экземпляре компонента Database Engine превышает пороговые значения памяти или конфигурации.

Если укрупнение блокировок невозможно из-за конфликтов блокировок, Компонент Database Engine периодически инициирует укрупнение блокировок после получения каждых 1250 новых блокировок.

Вы можете легко убедиться в этом сами, отследив событие эскалации блокировки в Profiler или просто попробовав приведенное ниже с различными размерами пакетов. Для меня TOP (6228) показывает 6250 удерживаемых блокировок, но TOP (6229) оно внезапно падает до 1, когда начинается укрупнение блокировок. Точные цифры могут отличаться (в зависимости от настроек базы данных и доступных ресурсов). Используйте метод проб и ошибок, чтобы найти порог, при котором для вас появляется эскалация блокировки.

CREATE TABLE [dbo].[Destination]
  (
     proj_details_sid INT,
     period_sid       INT,
     sales            INT,
     units            INT
  )

BEGIN TRAN --So locks are held for us to count in the next statement
INSERT INTO [dbo].[Destination]
SELECT TOP (6229) 1,
                  1,
                  1,
                  1
FROM   master..spt_values v1,
       master..spt_values v2

SELECT COUNT(*)
FROM   sys.dm_tran_locks
WHERE  request_session_id = @@SPID;

COMMIT

DROP TABLE [dbo].[Destination] 

Вы вставляете 50 000 строк, поэтому почти наверняка будет предпринята попытка укрупнения блокировки.

Статья Как устранить проблемы с блокировкой, вызванные укрупнением блокировки в SQL Server. довольно старый, но многие предложения все еще в силе.

  1. Разбейте большие пакетные операции на несколько более мелких операций (т. е. используйте меньший размер пакета)
  2. Расширение блокировки не может произойти, если другой SPID в настоящее время удерживает несовместимую блокировку таблицы. Пример, который они приводят, - это выполнение другого сеанса.

BEGIN TRAN
SELECT * FROM mytable (UPDLOCK, HOLDLOCK) WHERE 1=0
WAITFOR DELAY '1:00:00'
COMMIT TRAN 
  1. Отключите расширение блокировки, включив флаг трассировки 1211. Однако это глобальный параметр, который может вызвать серьезные проблемы. Существует более новая опция 1224, которая менее проблематична, но по-прежнему глобальна. .

Другим вариантом может быть ALTER TABLE blah SET (LOCK_ESCALATION = DISABLE), но это все еще не очень целенаправленно, поскольку влияет на все запросы к таблице, а не только на ваш единственный сценарий здесь.

Так что я бы выбрал вариант 1 или, возможно, вариант 2 и обесценил бы остальные.

person Martin Smith    schedule 10.09.2016

Вместо того, чтобы проверять наличие данных в Destination, лучше сначала сохранить все данные во временной таблице, а затем вставить их в Destination.

Ссылка: Использование ROWLOCK в инструкции INSERT (SQL Server)

DECLARE @batch int = 100
DECLARE @curRecord int = 1
DECLARE @maxRecord int

-- remove (nolock) if you don't want to have dirty read
SELECT row_number over (order by s.proj_details_sid, s.period_sid) as rownum,
       s.proj_details_sid,
       s.period_sid,
       s.sales,
       s.units
INTO #Temp
FROM   [dbo].[SOURCE] s WITH (NOLOCK)
WHERE  NOT EXISTS (SELECT 1
                   FROM   dbo.Destination d WITH (NOLOCK)
                   WHERE  d.proj_details_sid = s.proj_details_sid
                          AND d.period_sid = s.period_sid)

-- change this maxRecord if you want to limit the records to insert
SELECT @maxRecord = count(1) from #Temp

WHILE @maxRecord >= @curRecord
   BEGIN
       INSERT INTO [dbo].[Destination] 
              (proj_details_sid,
               period_sid,
               sales,
               units)
       SELECT proj_details_sid, period_sid, sales, units
       FROM #Temp
       WHERE rownum >= @curRecord and rownum < @curRecord + @batch

       SET @curRecord = @curRecord + @batch
   END

DROP TABLE #Temp
person Prisoner    schedule 09.09.2016
comment
Попробую к понедельнику - person Pரதீப்; 10.09.2016
comment
Это быстрее, чем мой метод. - person Pரதீப்; 13.09.2016

Я добавил (NOLOCK) вашу целевую таблицу -> dbo.Destination (NOLOCK). Теперь, Вы не будете запирать свой стол.

WHILE 1 = 1
  BEGIN
      INSERT INTO [dbo].[Destination] 
                  (proj_details_sid,
                   period_sid,
                   sales,
                   units)
      SELECT TOP(@BatchSize) s.proj_details_sid,
                             s.period_sid,
                             s.sales,
                             s.units
      FROM   [dbo].[SOURCE] s
      WHERE  NOT EXISTS (SELECT 1
                         FROM   dbo.Destination(NOLOCK) d
                         WHERE  d.proj_details_sid = s.proj_details_sid
                                AND d.period_sid = s.period_sid)

      IF @@ROWCOUNT < @BatchSize
        BREAK
  END 
person Huseyin Durmus    schedule 09.09.2016
comment
Добавление NOLOCK вызовет здесь какие-либо проблемы из-за грязного чтения? - person Pரதீப்; 09.09.2016
comment
Если вы не хотите, чтобы ваша программа работала параллельно, грязное чтение не будет проблемой. - person Huseyin Durmus; 09.09.2016
comment
В приведенной выше ситуации NOLOCK не вызовет никаких проблем. - person Mohit Dagar; 09.09.2016

Для этого вы можете использовать WITH (NOLOCK) в своем операторе выбора. НО NOLOCK не рекомендуется для баз данных OLTP.

person Mohit Dagar    schedule 09.09.2016