Выполнено ли выполнение T-SQL "атомарное"?
Скажем, у меня есть простая хранимая процедура, которая выглядит так (примечание: это всего лишь пример, а не практическая процедура):
CREATE PROCEDURE incrementCounter AS
DECLARE @current int
SET @current = (select CounterColumn from MyTable) + 1
UPDATE
MyTable
SET
CounterColumn = current
GO
Мы предполагаем, что у меня есть таблица под названием "myTable", которая содержит одну строку с "CounterColumn", содержащей наш текущий счет.
Может ли эта хранимая процедура выполняться несколько раз, в то же время?
то есть. это возможно:
Я дважды вызываю "incrementCounter". Вызов A доходит до точки, где задается "текущая" переменная (предположим, что она равна 5). Вызов B доходит до того момента, когда он устанавливает текущую переменную (которая также будет равна 5). Вызов завершает выполнение, затем завершает вызов B. В конце таблица должна содержать значение 6, но вместо этого содержит 5 из-за перекрытия выполнения
Ответы
Ответ 1
Это для SQL Server.
Каждый оператор является атомарным, но если вы хотите, чтобы хранимая процедура была атомарной (или любой последовательностью инструкций в целом), вам необходимо явно окружать утверждения с помощью
НАЧАЛО РАБОТЫ
Заявление...
Заявление...
COMMIT TRANSACTION
(Обычно используется BEGIN TRAN и END TRAN для краткости.)
Конечно, есть много способов попасть в проблему блокировки, в зависимости от того, что еще происходит в одно и то же время, поэтому вам может понадобиться стратегия для работы с неудачными транзакциями. (Полное обсуждение всех обстоятельств, которые могут привести к блокировкам, независимо от того, как вы усовершенствуете этот конкретный СП, выходит за рамки вопроса.) Но они все равно будут повторно подавлены из-за атомарности. И, по моему опыту, вы, вероятно, будете в порядке, не зная о своих томах транзакций и других действиях в базе данных. Извините меня за то, что я указал на очевидное.
Вопреки распространенному заблуждению, это будет работать в вашем случае с настройками уровня транзакции по умолчанию.
Ответ 2
Помимо размещения кода между BEGIN TRANSACTION
и END TRANSACTION
, вам необходимо убедиться, что уровень изоляции транзакции установлен правильно.
Например, уровень изоляции SERIALIZABLE
предотвратит потерянные обновления при одновременном запуске кода, но READ COMMITTED
(по умолчанию в SQL Server Management Studio) не будет.
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
Как уже упоминалось ранее, при обеспечении последовательности, это может привести к блокировке и взаимоблокировкам и, следовательно, может быть не лучшим решением на практике.
Ответ 3
Я использую этот метод
CREATE PROCEDURE incrementCounter
AS
DECLARE @current int
UPDATE MyTable
SET
@current = CounterColumn = CounterColumn + 1
Return @current
эта процедура выполняет все две команды за один раз и изолируется от другой транзакции.
Ответ 4
Возможно, я слишком много читаю в вашем примере (и ваша реальная ситуация может быть значительно сложнее), но почему бы вам просто не сделать это в одном заявлении?
CREATE PROCEDURE incrementCounter AS
UPDATE
MyTable
SET
CounterColumn = CounterColumn + 1
GO
Таким образом, он автоматически атомарен, и если одновременно выполняются два обновления, они всегда будут упорядочены SQL Server, чтобы избежать описанного конфликта. Если, однако, ваша реальная ситуация намного сложнее, то лучше всего сделать это в транзакции.
Однако, если другой процесс включил "менее безопасный" уровень изоляции (например, тот, который допускает грязные чтения или не повторяемые чтения), тогда я не думаю, что транзакция будет защищать от этого, поскольку другой процесс может видеть в частично обновленные данные, если он решил разрешить небезопасные чтения.
Ответ 5
Короткий ответ на ваш вопрос - ДА, он может и будет коротким. Если вы хотите заблокировать одновременное выполнение хранимых процедур, запустите транзакцию и обновите одни и те же данные при каждом выполнении хранимой процедуры, прежде чем продолжать выполнять какую-либо работу в рамках процедуры.
CREATE PROCEDURE ..
BEGIN TRANSACTION
UPDATE mylock SET ref = ref + 1
...
Это заставит другие параллельные исполнения ждать своей очереди, так как они не смогут изменить значение "ref" до тех пор, пока не завершится другая транзакция (транзакции) и связанная с ней блокировка обновлений.
В целом рекомендуется предположить, что результаты всех и всех запросов SELECT являются устаревшими до, которые они когда-либо выполняются. Использование "тяжелых" уровней изоляции для устранения этой неудачной реальности серьезно ограничивает масштабируемость. Гораздо лучше структурировать изменения таким образом, чтобы сделать оптимистичные предположения о состоянии системы, которые вы ожидаете существовать во время обновления, поэтому, когда ваше допущение не удастся, вы можете попробовать еще раз позже и надеяться на лучший результат. Например:
UPDATE
MyTable
SET
CounterColumn = current
WHERE CounterColumn = current - 1
Используя ваш пример с добавленным предложением WHERE, это обновление не влияет на какие-либо строки, если предположение о его текущем состоянии не выполняется. Проверьте @@ROWCOUNT, чтобы проверить количество строк и откат или какое-то другое действие, если оно отличается от ожидаемого результата.