#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) является частью одной неявной транзакции для вставки, она должным образом защитила вызов внешней транзакцией. Очевидно, что вызов этого без встроенной вставки приведет к дублированию. Спасибо!