Является ли выполнение хранимых процедур T-SQL «атомарным»? - PullRequest
14 голосов
/ 03 ноября 2008

Допустим, у меня есть простая хранимая процедура, которая выглядит следующим образом (примечание: это всего лишь пример, а не практическая процедура):

CREATE PROCEDURE incrementCounter AS

DECLARE @current int
SET @current = (select CounterColumn from MyTable) + 1

UPDATE
    MyTable
SET
    CounterColumn = current
GO

Мы предполагаем, что у меня есть таблица myTable, которая содержит одну строку, а CounterColumn содержит текущий счетчик.

Может ли эта хранимая процедура выполняться несколько раз одновременно?

т.е. возможно ли это:

Я вызываю 'incrementCounter' дважды. Вызов A достигает точки, где он устанавливает переменную «current» (скажем, 5). Вызов B достигает точки, где он устанавливает переменную «current» (которая также будет равна 5). Вызов A завершает выполнение, затем Call B завершает. В конце таблица должна содержать значение 6, но вместо этого содержит 5 из-за перекрытия выполнения

Ответы [ 5 ]

12 голосов
/ 03 ноября 2008

Помимо размещения кода между BEGIN TRANSACTION и END TRANSACTION, вам необходимо убедиться, что уровень изоляции транзакции установлен правильно.

Например, SERIALIZABLE уровень изоляции предотвратит потерю обновлений при одновременном выполнении кода, но READ COMMITTED (по умолчанию в SQL Server Management Studio) - нет.

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

Как уже упоминали другие, при обеспечении согласованности это может привести к блокировке и взаимным блокировкам и, следовательно, не может быть лучшим решением на практике.

12 голосов
/ 03 ноября 2008

Это для SQL Server.

Каждый оператор является атомарным, но если вы хотите, чтобы хранимая процедура была атомарной (или любая последовательность операторов в целом), вам необходимо явно окружить операторы

НАЧАЛО СДЕЛКИ
Заявление ...
Заявление ...
СОВЕРШЕНИЕ СДЕЛКИ

(для краткости принято использовать BEGIN TRAN и END TRAN.)

Конечно, существует множество способов решить проблему с блокировкой, в зависимости от того, что еще происходит одновременно, поэтому вам может потребоваться стратегия для обработки неудачных транзакций. (Полное обсуждение всех обстоятельств, которые могут привести к блокировкам, независимо от того, как вы придумали этот конкретный SP, выходит за рамки вопроса.) Но они все равно будут повторно представлены из-за атомарности. И по моему опыту вы, вероятно, будете в порядке, не зная об объемах транзакций и других действиях с базой данных. Извините за утверждение очевидного.

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

1 голос
/ 15 сентября 2013

Я использую этот метод

CREATE PROCEDURE incrementCounter
AS

DECLARE @current int

UPDATE MyTable
SET
  @current = CounterColumn = CounterColumn + 1

Return @current

эта процедура выполняет все две команды одновременно, и она изолирована от другой транзакции.

0 голосов
/ 08 февраля 2015

Короткий ответ на ваш вопрос: ДА, может и будет коротким. Если вы хотите заблокировать одновременное выполнение хранимых процедур, запустите транзакцию и обновляйте одни и те же данные при каждом выполнении хранимой процедуры, прежде чем продолжать выполнять какую-либо работу в рамках процедуры.

CREATE PROCEDURE ..
BEGIN TRANSACTION
UPDATE mylock SET ref = ref + 1
...

Это заставит другие параллельные выполнения ждать своей очереди, поскольку они не смогут изменять значение 'ref' до тех пор, пока не завершатся другие транзакции и не будет снята соответствующая блокировка обновления.

Как правило, рекомендуется предположить, что результат любого и всех запросов SELECT устарел до , когда они вообще выполнялись. Использование «тяжелых» уровней изоляции для обхода этой печальной реальности серьезно ограничивает масштабируемость. Гораздо лучше структурировать изменения таким образом, чтобы сделать оптимистичные предположения о состоянии системы, которое вы ожидаете существовать во время обновления, поэтому, если ваше предположение не удастся, вы можете попробовать позже и надеяться на лучший результат. Например:

UPDATE
    MyTable
SET
    CounterColumn = current 
WHERE CounterColumn = current - 1

Используя ваш пример с добавленным предложением WHERE, это обновление не влияет на строки, если предположение о его текущем состоянии не выполняется. Проверьте @@ ROWCOUNT, чтобы проверить количество строк и откат или какое-либо другое действие в зависимости от ситуации, если оно отличается от ожидаемого результата.

0 голосов
/ 19 января 2010

Может быть, я читаю слишком много в вашем примере (и ваша реальная ситуация может быть значительно сложнее), но почему бы вам просто не сделать это в одном утверждении?

CREATE PROCEDURE incrementCounter AS

UPDATE
    MyTable
SET
    CounterColumn = CounterColumn + 1

GO

Таким образом, он автоматически становится атомарным, и если два обновления выполняются одновременно, они всегда будут заказываться SQL Server, чтобы избежать конфликта, который вы описываете. Однако, если ваша реальная ситуация намного сложнее, лучше всего это сделать с помощью транзакции.

Однако, если другой процесс включил «менее безопасный» уровень изоляции (например, тот, который допускает грязное чтение или неповторяемое чтение), то я не думаю, что транзакция защитит от этого, так как другой процесс может увидеть частично обновленные данные, если они разрешают небезопасные чтения.

...