Являются ли операторы CREATE TABLE в SQL Server невосприимчивыми к ROLLBACK?

У меня есть база данных, которую я «выпускаю» еженедельно, то есть я публикую резервную копию, с которой люди могут начать, и я публикую сценарий обновления, который они могут использовать для обновления с прошлой недели (чтобы они могли максимально сохранить свои текущие данные). Этот скрипт, конечно, содержит много DDL — CREATE TABLE, ALTER TABLE и так далее. Базовая структура его такова:

/*

HOW TO USE THIS SCRIPT

1.  Run it against your existing DB
2.  Check whether there were any errors
3.  If there were, issue a rollback by highlighting this:
        ROLLBACK
    and executing it
4.  If there weren't, issue a commit by highlighting this:
        COMMIT
    and executing it
5.  !!! Not doing either of these will leave a transaction open, which will
    probably cause all further queries to time out till you do !!!

*/

SET XACT_ABORT ON;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;
GO

-- Boilerplate checking and whatnot goes here
GO

-- Guts of operation, part 1
GO

-- Guts of operation, part 2
GO

-- Guts of operation, part 3
GO

-- . . .
GO

-- Guts of operation, part N
GO

-- Boilerplate cleanup stuff here
GO

Вы заметите, что я оставил транзакцию открытой и сказал им завершить ее вручную, в зависимости от того, что произойдет. Я бы предпочел, чтобы это было автоматически, но я отчаялся в этом, учитывая, что все это всегда представляет собой длинную серию из нескольких пакетов, а блоки TRY, конечно, не могут охватывать пакеты. Таким образом, недавнее добавление SET XACT_ABORT ON; немного облегчило боль. Несмотря на это, я пробовал это сам, так как без него, и это не имеет значения для сценария. Тем не мение.

Недавно в одном из этих сценариев были операторы для создания таблиц и другие операторы для добавления проверочных ограничений к существующим таблицам. Один из моих пользователей запустил скрипт и столкнулся с ошибкой ограничения; оказывается, у него были ранее существовавшие данные, нарушающие ограничение. Хорошо, нет проблем, сделайте ROLLBACK. Верно, не нужно было этого делать, XACT_ABORT уже сделал это. Идите и исправьте строки данных… готово. Теперь попробовать еще раз! Э-э... что? На этот раз ошибки в ограничении нет, но в операторах CREATE TABLE возникают ошибки, говорящие, что таблицы уже существуют…! Хм? Не откатился?

Мы закончили восстановление из резервной копии, повторное исправление данных и повторный запуск. Но это ни здесь, ни там.

Итак, уважаемые читатели: Как создание этих таблиц пережило откат транзакции? Как я могу заставить их этого не делать?


РЕДАКТИРОВАТЬ: Хорошо, вот пример, который вы можете запустить.

USE tempdb;
GO

CREATE DATABASE example;
GO

USE example;
GO

CREATE TABLE foo (a INT);
GO

INSERT INTO foo
VALUES (100);
GO

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;
GO

ALTER TABLE foo ADD CHECK (a < 10);-- Gives error "The ALTER TABLE statement conflicted with the CHECK constraint…", as expected
GO

CREATE TABLE bar (b INT);
GO

ROLLBACK;-- Gives error "The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION."  Huh?  Where did our transaction go?
GO

SELECT *
FROM bar;-- Gives no error.  Table still exists!  NOT expected!
GO

USE tempdb;
GO

DROP DATABASE example;

person Atario    schedule 03.09.2014    source источник
comment
Нет. Они не застрахованы от отката, трудно понять, что произошло в вашем случае из описания.   -  person Martin Smith    schedule 04.09.2014
comment
Попробуйте найти самый простой способ воспроизвести проблему. Это помогло бы нам проанализировать проблему.   -  person Andomar    schedule 04.09.2014


Ответы (1)


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

В вашем случае пакет и транзакция были прерваны из-за XACT_ABORT. Но GO — это разделитель пакетов. У вас есть несколько пакетов. Более поздние партии продолжают работать.

Используйте одну партию или заставьте более поздние партии проверять, выполнялась ли предыдущая партия (возможно, проверив @@TRANCOUNT).

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

person usr    schedule 03.09.2014
comment
Ах, вот что случилось! Режим SQLcmd и :on error exit могут помочь. - person Martin Smith; 04.09.2014
comment
Хм, я не могу обойтись одной партией, потому что она не пройдет проверку синтаксиса на объектах, которых еще не существует. Я должен сделать GO между, например, CREATE TABLE и чем-то, что что-то делает с результирующей таблицей, иначе он даже не будет пытаться выполнить. И у меня мало надежды объяснить пользователям режим SQLCmd… :/ - person Atario; 04.09.2014
comment
@Atario, вы можете обернуть все в try ... catch, а затем вызвать ошибку прерывания соединения или set noexec on в блоке catch. - person Martin Smith; 04.09.2014