B
B
Boris the Animal2016-09-02 09:31:00
Database
Boris the Animal, 2016-09-02 09:31:00

Is it dangerous to execute such code in C# + T-SQL? Or is there a different way to write it?

Есть 3 таблицы. Основная SOME_GROUP и две дочерние, что видно из столбцов GROUP_ID.
У всех трёх таблиц первичный ключ:
CONSTRAINT PK_BLA_BLA PRIMARY KEY CLUSTERED (ID)
Так же у дочерних таблиц есть внешние ключи (GROUP_ID), которые указывают на ID в таблице SOME_GROUP.
Эти строки в запросе приведены для удобства и понимания, что как выполняется.

BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED -- REPEATABLE READ

В реальности же из C# кода создаётся транзакция и вызывается получение данных для 3-ёх таблиц. То есть к MSSQL серверу отправляется 3 запроса по очереди в одной транзакции.
Вопрос: Есть ли вероятность, что на одном из запросов строка...
SELECT TOP(@Quantity) ID FROM SOME_GROUP WITH(UPDLOCK) WHERE REPLICATED <> 1

...вернёт другой результат? По идее строки отсортированы по первичному ключу. Если другая транзакция залочит одну из необходимых записей, то мой даже первый запрос будет ждать, пока записи разлочатся, а далее сразу же залочит все записи, которые будут выбраны и в пределах одной транзакции все 3 запроса выполнятся и все 3 раза получение SELECT TOP(@Quantity) вернёт один и тот же результат. Верно?
BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL READ COMMITTED -- REPEATABLE READ

SET DEADLOCK_PRIORITY LOW;
DECLARE @Quantity INT = 5;

-- Первый запрос, вызываемый из C# кода в одной транзакции.
-- 1 ------------------------------------------------------------------
UPDATE SOME_GROUP SET REPLICATED = 2 
OUTPUT 
  INSERTED.ID
 ,INSERTED.NAME
WHERE ID IN(SELECT TOP(@Quantity) ID FROM SOME_GROUP WITH(UPDLOCK) WHERE REPLICATED <> 1)

-- Второй запрос, вызываемый из C# кода в одной транзакции.
-- 2------------------------------------------------------------------
UPDATE SOME_CHILD_ONE SET REPLICATED = 2 
OUTPUT 
  INSERTED.ID
 ,INSERTED.GROUP_ID
 ,INSERTED.UPDATED
WHERE GROUP_ID IN(SELECT TOP(@Quantity) ID FROM SOME_GROUP WITH(UPDLOCK) WHERE REPLICATED <> 1) AND REPLICATED <> 1

-- Третий запрос, вызываемый из C# кода в одной транзакции.
-- 3 ------------------------------------------------------------------
UPDATE SOME_CHILD_TWO SET REPLICATED = 2 
OUTPUT 
  INSERTED.ID
 ,INSERTED.GROUP_ID
 ,INSERTED.NAME
 ,INSERTED.CREATED
WHERE GROUP_ID IN(SELECT TOP(@Quantity) ID FROM SOME_GROUP WITH(UPDLOCK) WHERE REPLICATED <> 1) AND REPLICATED <> 1
-- 3 ------------------------------------------------------------------

COMMIT TRANSACTION

Answer the question

In order to leave comments, you need to log in

1 answer(s)
E
Eduard, 2016-09-02
@Casper-SC

Ваш код не будет работать всегда предсказуемым образом. Основная проблема в том, что SELECT TOP без ORDER BY не гарантирует ни в коем случае одни и те же данные. Да, в вашем случае они скорее всего приходят в порядке из сохранения в кластерном индексе. Но если изменится индекс или добавится новый, то ваш код может перестать работать как ожидается.
Другая проблема в том, что несмотря на то, что подзапросы в WHERE одинаковые, они могут возвращать разные данные из-за первого UPDATE. Если у вас было 10 записей с REPLICATED =-10 (минус!), то до первого обновления подзапрос выдаст записи с 1 по 5, а после него с 6 по 10.
Совет: сохраните обновляемые ID в отдельную временную таблицу и присоединяйте ее для обновления.
Либо настройте внешние ключи и каскадное обновление.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question