В tsql безопасна Вставка с оператором Select с точки зрения параллелизма?

В моем ответе на это ТАК вопрос я предлагаю использовать единственный оператор вставки с выбором, который увеличивает значение, как показано ниже.

Insert Into VersionTable 
(Id, VersionNumber, Title, Description, ...) 
Select @ObjectId, max(VersionNumber) + 1, @Title, @Description 
From VersionTable 
Where Id = @ObjectId 

Я предложил это, потому что я полагаю, что этот оператор безопасен с точки зрения параллелизма, в том, что, если другие вставляют для того же идентификатора объекта, выполняется одновременно, нет никакого шанса наличия дублирующихся номеров версий.

Я корректен?

6
задан Community 23 May 2017 в 12:02
поделиться

4 ответа

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

Вам нужны уровни изоляции REPEATABLE READ или SERIALIZABLE, чтобы сделать это безопасным.

.
4
ответ дан 8 December 2019 в 13:00
поделиться

Я думаю, вы предполагаете, что это неправильно. Когда вы запрашиваете таблицу VersionNumber, вы только ставите на строку блокировку чтения. Это не мешает другим пользователям читать одну и ту же строку из той же таблицы. Следовательно, два процесса могут одновременно читать одну и ту же строку в таблице VersionNumber и генерировать одно и то же значение VersionNumber.

.
2
ответ дан 8 December 2019 в 13:00
поделиться
  • Вам нужно уникальное ограничение на (Id, VersionNumber) для его применения

  • Я бы использовал ROWLOCK, XLOCK намеки для блокировки других людей, читающих заблокированную строку, в которой вы вычисляется

  • или обертывания INSERT в TRY/CATCH. Если я получу дубликат, попробуйте еще раз...

0
ответ дан 8 December 2019 в 13:00
поделиться

Как пишет Пол: Нет, это небезопасно , для чего я хотел бы добавить эмпирическое свидетельство: Создайте таблицу Таблица_1 с одним полем ID и одной записью со значением 0 . Затем выполните следующий код одновременно в двух окнах запросов Management Studio :

declare @counter int
set @counter = 0
while @counter < 1000
begin
  set @counter = @counter + 1

  INSERT INTO Table_1
    SELECT MAX(ID) + 1 FROM Table_1 

end

Затем выполните

SELECT ID, COUNT(*) FROM Table_1 GROUP BY ID HAVING COUNT(*) > 1

На моем SQL Server 2008 один идентификатор ( 662 ) был создан дважды. Таким образом, уровень изоляции по умолчанию, применяемый к отдельным операторам, недостаточен .


РЕДАКТИРОВАТЬ: Очевидно, что упаковка INSERT с BEGIN TRANSACTION и COMMIT не исправит этого, поскольку уровень изоляции по умолчанию для транзакций по-прежнему READ COMMITTED , чего недостаточно. Обратите внимание, что установка уровня изоляции транзакции на REPEATABLE READ является также недостаточной. Единственный способ сделать приведенный выше код безопасным - это добавить

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

вверху. Однако это время от времени приводило к тупикам в моих тестах.

РЕДАКТИРОВАТЬ: Единственное безопасное решение, которое я нашел и , не вызывает взаимоблокировок (по крайней мере, в моих тестах), - это явная блокировка таблицы исключительно (здесь достаточно уровня изоляции транзакции по умолчанию). Однако будьте осторожны; это решение может убить производительность:

...loop stuff...
    BEGIN TRANSACTION

    SELECT * FROM Table_1 WITH (TABLOCKX, HOLDLOCK) WHERE 1=0

    INSERT INTO Table_1
      SELECT MAX(ID) + 1 FROM Table_1 

    COMMIT
...loop end...
12
ответ дан 8 December 2019 в 13:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: