По-перше , ви завжди повинні мати належну обробку транзакцій у всіх своїх процедурах, так що не має значення, чи викликаються вони кодом програми, іншою процедурою, індивідуально за спеціальним запитом, завданням агента SQL чи іншим способом . Але окремим операторам DML або коду, який не вносить жодних змін, не потрібна явна транзакція. Отже, що я рекомендую:
- Завжди має структуру TRY / CATCH, щоб помилки могли бути належним чином розкриті
- Необов’язково включайте 3 фрагменти обробки транзакцій у наведений нижче код, якщо у вас є кілька операторів DML (оскільки один оператор сам по собі є транзакцією). ЗАРАЗ, за винятком додавання додаткового коду там, де він конкретно не потрібен, якщо хтось вважає за краще мати послідовний шаблон, то це не завадить триматись у 3 блоках IF, пов'язаних з транзакціями. Але в цьому випадку я все-таки рекомендую не зберігати 3 IF-пов'язані з транзакцією блоки IF для проектів SELECT (тобто лише для читання).
Виконуючи 2 або більше операторів DML, вам потрібно використовувати щось у відповідності з наступним (що також можна зробити для одиночних операцій DML, якщо один вважає за краще послідовним):
CREATE PROCEDURE [SchemaName].[ProcedureName]
(
@Param DataType
...
)
AS
SET NOCOUNT ON;
DECLARE @InNestedTransaction BIT;
BEGIN TRY
IF (@@TRANCOUNT = 0)
BEGIN
SET @InNestedTransaction = 0;
BEGIN TRAN; -- only start a transaction if not already in one
END;
ELSE
BEGIN
SET @InNestedTransaction = 1;
END;
-- { 2 or more DML statements (i.e. INSERT / UPDATE / DELETE) }
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
COMMIT;
END;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0 AND @InNestedTransaction = 0)
BEGIN
ROLLBACK;
END;
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
@ErrorState INT = ERROR_STATE(),
@ErrorSeverity INT = ERROR_SEVERITY();
-- optionally concatenate ERROR_NUMBER() and/or ERROR_LINE() into @ErrorMessage
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
RETURN;
END CATCH;
Виконуючи лише 1 оператор DML або просто SELECT, ви можете піти лише з наступного:
CREATE PROCEDURE [SchemaName].[ProcedureName]
(
@Param DataType
...
)
AS
SET NOCOUNT ON;
BEGIN TRY
-- { 0 or 1 DML statements (i.e. INSERT / UPDATE / DELETE) }
END TRY
BEGIN CATCH
DECLARE @ErrorMessage NVARCHAR(4000) = ERROR_MESSAGE(),
@ErrorState INT = ERROR_STATE(),
@ErrorSeverity INT = ERROR_SEVERITY();
-- optionally concatenate ERROR_NUMBER() and/or ERROR_LINE() into @ErrorMessage
RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState);
RETURN;
END CATCH;
По-друге , вам слід обробляти транзакції на рівні додатків, лише якщо вам потрібно виконати більше 1 запиту / збереженої процедури, і всі вони повинні бути згруповані в атомну операцію. Робити сингл SqlCommand.Execute___
потрібно лише в спробі / лові, але не в транзакції.
Але чи не боляче робити транзакцію на рівні програми, коли здійснюєте лише один дзвінок? Якщо для цього потрібен MSDTC (координатор розподілених транзакцій Microsoft), то в системі дещо важче це робити на рівні додатків, коли це прямо не потрібно. Особисто я вважаю за краще уникати трансакцій на основі додатків, якщо це абсолютно не потрібно, оскільки це зменшує потенціал для осиротілих транзакцій (якщо щось пішло не так з кодом програми перед тим, як здійснити комісію або відкат). Я також виявив, що іноді налагодження певних ситуацій стає дещо складнішим. Але, маючи на увазі , я не бачу нічого технічно неправильного в тому, щоб також обробляти транзакції на рівні додатків під час створення єдиної процедуридзвінок; знову ж таки, один оператор DML є власною транзакцією і не потребує явної обробки транзакцій на будь-якому рівні.