Выполнено ли выполнение 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, чтобы проверить количество строк и откат или какое-то другое действие, если оно отличается от ожидаемого результата.