Не используйте транзакцию для хранимой процедуры

18

У меня есть хранимая процедура, которая запускает несколько команд. Я не хочу, чтобы эти команды были включены в транзакцию хранимой процедуры. Если 4-я команда терпит неудачу, я хочу, чтобы 1-я, 2-я и 3-я команды остались, а не откатывались.

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

Мэтью Стиплз
источник

Ответы:

16

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

use TestDB;
go

if exists (select 1 from sys.tables where object_id = object_id('dbo.TestTranTable1'))
    drop table dbo.TestTranTable1;
create table dbo.TestTranTable1
(
    id int identity(1, 1) not null,
    some_int int not null
        default 1
);
go

insert into dbo.TestTranTable1
default values;
go 4

select *
from dbo.TestTranTable1;

if exists (select 1 from sys.sql_modules where object_id = object_id('dbo.ChangeValues'))
begin
    drop proc dbo.ChangeValues;
end
go
create proc dbo.ChangeValues
as
    update dbo.TestTranTable1
    set some_int = 11
    where id = 1;

    update dbo.TestTranTable1
    set some_int = 12
    where id = 2;

    update dbo.TestTranTable1
    set some_int = 13
    where id = 3;

    -- this will error out (arithmetic overflow)
    update dbo.TestTranTable1
    set some_int = 2147483648
    where id = 4;
go

exec dbo.ChangeValues;

select *
from dbo.TestTranTable1;

Вот вывод:

введите описание изображения здесь

Создавая сеанс расширенных событий для мониторинга этого sql_transactionсобытия, вы получите результат выполненияdbo.ChangeValues :

введите описание изображения здесь

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

Томас Стрингер
источник
16

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

Сделка является заявление или набор операторов , которые будут либо успеха или неудачи , как единое целое. Все операторы DDL находятся в самих транзакциях (то есть, если вы обновляете 100 строк, но строка 98 выдает ошибку, ни одна из строк не обновляется). Вы также можете заключить серию операторов в транзакцию, используя, BEGIN TRANSACTIONа затем либо COMMITили ROLLBACK.

Партия представляет собой последовательность операторов, которые выполняются вместе. Хранимая процедура является примером пакета. В хранимой процедуре, если один оператор завершается неудачно и происходит перехват ошибок (обычно TRY/CATCHблоки), последующие операторы не будут выполняться.

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

JNK
источник
Я не нашел ни одной статьи, в которой говорилось бы: «Процедура магазина - это пример партии». Я считаю, что хранимая процедура очень похожа на пакетную, но это не пакетная процедура. Основное отличие состоит в том, что SP гарантированно будет скомпилирован заранее и готов к выполнению несколько раз в отличие от пакетов. Сходства: - Они оба выполняют каждую команду одновременно. - Если одна команда завершилась неудачно, то все предыдущие команды фиксируются (если она не выполнялась в транзакции) - если одна команда не удалась, все последующие команды не выполняются.
Аши
6

Все на сервере sql содержится в транзакции.

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

Чтобы переключиться в какой режим вы используете, вы должны использовать

set implicit_transactions on

или

set implicit_transactions off

select @@OPTIONS & 2

если выше возвращает 2, вы находитесь в неявном режиме транзакции. Если он возвращает 0, вы находитесь в автокоммите.

Транзакция - это ВСЕ или ничего для поддержания базы данных в согласованном состоянии. Запомните свойства ACID.

CREATE TABLE [dbo].[Products](
    [ProductID] [int] NOT NULL,
    [ProductName] [varchar](25) NULL,
    [DatabaseName] [sysname] NOT NULL,
 CONSTRAINT [pk_Product_ID_ServerName] PRIMARY KEY CLUSTERED 
(
    [ProductID] ASC,
    [DatabaseName] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO


-- insert some data 
INSERT INTO [dbo].[Products]([ProductID], [ProductName], [DatabaseName])
SELECT 1, N'repl1_product1', N'repl1' UNION ALL
SELECT 1, N'repl2_product1_02', N'repl2' UNION ALL
SELECT 1, N'repl3_product1_03', N'repl3' UNION ALL
SELECT 2, N'repl1_product1_01', N'repl1' UNION ALL
SELECT 2, N'repl2_product1', N'repl2' UNION ALL
SELECT 2, N'repl3_product1_03', N'repl3' UNION ALL
SELECT 3, N'repl1_product1_01', N'repl1' UNION ALL
SELECT 3, N'repl2_product1_02', N'repl2' UNION ALL
SELECT 3, N'repl3_product1', N'repl3' UNION ALL
SELECT 4, N'repl1_product1_01', N'repl1' UNION ALL
SELECT 4, N'repl2_product1_02', N'repl2' UNION ALL
SELECT 5, N'repl1_product1_01', N'repl1' UNION ALL
SELECT 5, N'repl2_product1_02', N'repl2'

- создать SP сейчас - обратите внимание, что первые 3 завершатся успешно, а 4-й завершится неудачей из-за усечения строки ...

IF OBJECT_ID ('usp_UpdateProducts', 'P') IS NOT NULL
    DROP PROCEDURE usp_UpdateProducts;
GO
create procedure usp_UpdateProducts
as 
begin try
update Products 
set ProductName = 'repl1_product1'
where DatabaseName = 'repl1'and ProductID = 1;
update Products
set ProductName = 'repl2_product1'
where DatabaseName = 'repl2' and ProductID = 2;
update Products
set ProductName = 'repl3_product1'
where DatabaseName = 'repl3' and ProductID = 3;
update Products
set ProductName = 'repl3_product1_03&&&&&&&&&&39399338492w9924389234923482' -- this will fail ...
where DatabaseName = 'repl3' and ProductID = 4;
SELECT 1/0;
end try
begin catch
SELECT 
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() as ErrorState,
        ERROR_PROCEDURE() as ErrorProcedure,
        ERROR_LINE() as ErrorLine,
        ERROR_MESSAGE() as ErrorMessage;
end catch
go

Обратитесь к: Это плохая практика, чтобы всегда создавать транзакцию?

Кин Шах
источник
3

Вот как хранимые процедуры работают по умолчанию. Хранимая процедура не включается в транзакцию автоматически.

Если вы хотите, чтобы хранимая процедура остановилась при появлении первой ошибки, вы должны добавить туда логин TRY / CATCH для возврата в случае проблемы, например, с командой 2.

mrdenny
источник
2

Вам понадобятся отдельные транзакции для каждой команды. Вы также можете сделать это с помощью сохраненных транзакций:

Смотрите SAVE TRANSACTION (Transact-SQL)в документации по продукту.

Я хочу квалифицировать, что для отдельных транзакций это поведение по умолчанию для хранимых процедур, потому что все операторы заключены в неявные транзакции; однако, никто не должен полагаться на неявные транзакции, чтобы контролировать судьбу своего кода. Гораздо эффективнее явно контролировать способ обработки транзакций в производственном коде.

Адам Хейнс
источник
-2

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

Для получения дополнительной информации вы можете посмотреть по адресу: http://msdn.microsoft.com/en-us/library/ms188929.aspx

Тони Костелак
источник
1
Будет ли это создавать суб-транзакции в моей хранимой процедуре? В идеале я хотел бы избежать этого, если это возможно
Мэтью Стиплс
1
Если SP вызывается из транзакции, то ответом являются сохраненные транзакции. Если sp не вызывается с, тогда @mrdenny является правильным. Сервер Sql не поддерживает вложенные транзакции.
StrayCatDBA
@StrayCatDBA, просто чтобы уточнить ... в SQL Server есть вложенные транзакции, но они злые. SQL Server позволяет запускать транзакции внутри других транзакций, называемых вложенными транзакциями. Обратитесь к sqlskills.com/blogs/paul/… , msdn.microsoft.com/en-us/library/ms189336(v=sql.105).aspx и sqlblog.com/blogs/kalen_delaney/archive/2007/08/13 /…
Кин Шах
2
Чтобы быть понятным (и для ленивых, которые не хотят нажимать на ссылки), вы на самом деле не начинаете другую транзакцию. Ака заголовок на пост Павла: «Миф: Вложенные транзакции реальны». Это не настоящие сделки. COMMIT во вложенной транзакции ничего не делает, кроме декремента @@ TRANCOUNT. Это правда, что вы не получите ошибку, если вложите BEGIN TRAN / COMMIT, но это отличается от наличия реальных вложенных преобразований.
StrayCatDBA