Инструкция EXEC и область действия транзакции

#sql #sql-server-2008 #transactions

#sql #sql-server-2008 #транзакции

Вопрос:

Сначала это казалось таким простым, но все, что я знаю, снова кажется неправильным.

Глядя на PAQ, кажется, что консенсус заключается в том, что EXEC не запускает неявную транзакцию, вы можете проверить это, выполнив :

 create procedure usp_foo
as
begin
  select @@trancount;
end
go

exec usp_foo;
  

который возвращает 0.

Однако, если вы выполните это с помощью отладчика T-SQL, @@Transaction фактически равен 1 внутри процедуры в соответствии с наблюдением, хотя он возвращает 0 …

Итак, я думаю, что это побочный эффект отладчика, но затем я пишу некоторый код для его тестирования, выполняю обновление в таблице, а затем выбираю max (id) из классического :

 create table nextid
(
  id int
)

insert into nextid values (0)

create procedure nextid
as
BEGIN
  UPDATE nextid set id = id   1
  select max(id) from nextid
END
  

Итак, я ожидаю, что это выдаст дублирующиеся идентификаторы при параллельном выполнении, 2 обновления могут завершиться до того, как 2 выберут выборку последнего идентификатора и вернут то же значение, но, как бы я ни пытался использовать его с нескольких компьютеров, я не могу заставить его прерваться. При мониторинге блокировок и транзакций на компьютере сообщается, что exec выполняется в транзакции, и, что важно, все инструкции внутри exec обрабатываются как одна единица работы / одна транзакция.

Я бы понял, если бы обновление было в транзакции, и это было причиной блокировки, но блокировка, похоже, сохраняется до окончания выбора.

Если я выполняю трассировку с помощью profiler, я вижу, что идентификатор транзакции предоставляется для всего выполнения инструкции EXEC, и идентификатор транзакции не равен 0, как я ожидал бы при выполнении…

Может кто-нибудь, пожалуйста, объяснить мне, где я пропускаю график, или я ошибаюсь, и на самом деле безопасно генерировать подобные идентификаторы?

Ответ №1:

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

 CREATE TABLE NextID
(
    ID int
)
GO

INSERT INTO NextID VALUES (0)
GO

CREATE PROC GetNextID
AS
BEGIN
    UPDATE NextID SET ID = ID   1
    WAITFOR DELAY '00:00:05'
    SELECT Max(ID) FROM NextID
END
  

Выполните EXEC GetNextID и выдайте другую EXEC GetNextID , как только сможете, из другого сеанса. Примерно через 5 секунд оба EXEC вернут один и тот же результат, т. е. неверное значение. Теперь измените SP на

 CREATE PROC GetNextID
AS
BEGIN
    BEGIN TRAN

    UPDATE NextID SET ID = ID   1
    WAITFOR DELAY '00:00:05'
    SELECT Max(ID) FROM NextID

    COMMIT TRAN
END
  

и повторите вышеупомянутый тест. Вы увидите, что оба вызова вернут правильное значение. Кроме того, второй вызов (если выполнить его как можно скорее) вернет результат примерно через 10 секунд, поскольку обновление заблокировано и ему приходится ждать 5 секунд (для фиксации первого вызова), а затем его собственное 5-секундное ожидание.

Комментарии:

1. Аааа, я понял это с помощью этого. Я брал выходные данные вызова и вставлял их в хэш-таблицу. Поскольку вся инструкция (включая exec) является частью одной неявной транзакции для вставки, она должным образом защитила вызов внешней транзакцией. Очевидно, что вызов этого без встроенной вставки приведет к дублированию. Спасибо!