仅在发生未处理的错误时回滚sql事务

时间:2017-09-08 10:43:45

标签: sql-server tsql sql-server-2012 transactions

我正在创建一个在事务中运行多个sql脚本的数据库更新机制,如果所有这些脚本都成功,则会提交更新。

我面临的问题是,在每个脚本中都可能存在正在处理的错误,并且实际上并未将其视为错误。

示例:

脚本必须创建一个表并向表中插入一行,但该表已经存在。没关系,脚本应继续并尝试插入行,如果行存在 - 它也没关系,它应该完成没有错误并转移到下一个脚本。另一方面 - 如果出现意外错误(例如表存在,但列数较少,因此插入失败),则应抛出错误并回滚事务。

由于SQL Server在每个错误上回滚我的事务(无论是否处理),我都无法正确执行更新。

示例脚本如下所示:

USE [Database]

BEGIN TRY

-- CREATING TABLE
CREATE TABLE [dbo].[Users](
    [UserId] [int] UNIQUE NOT NULL,
    [UserFullName] [nvarchar](100) NOT NULL,
    [UserName] [nvarchar](100) NOT NULL,
    [UserShortName] [nvarchar](50) NOT NULL,
    [UserLogin] [varchar](50) NOT NULL
)

PRINT 'Created table'
END TRY
BEGIN CATCH
    IF(ERROR_NUMBER() = 2714)
    BEGIN
        PRINT 'Table exists, proceeding to insert'
    END
    ELSE
        THROW
END CATCH

-- INSERTING USER
BEGIN TRY
INSERT INTO [dbo].[Users](
    [UserId]
    ,[UserFullName]
    ,[UserName]
    ,[UserShortName]
    ,[UserLogin]
) VALUES (
    1,'System Administrator','Admin','SA',  'SA'
)
PRINT 'Inserted user'
END TRY
BEGIN CATCH
    IF(ERROR_NUMBER() = 2627)
    BEGIN
        PRINT 'User exists - nothing to insert'
    END
    ELSE
        THROW
END CATCH
GO
PRINT '-- FINISHED add_users_table.sql'

(该脚本只是一个例子,因此它可能不是100%正确)

如果存在表或行,则事务应继续,但如果抛出不同的错误,则应该回滚。

有办法做到这一点吗?

谢谢!

编辑:

我忘记了机制的关键部分。我从C#运行这些脚本(它更像是伪代码而不是实际代码,我也知道SqlCommand中的GO命令不起作用,但它与问题无关):

SqlTransaction transaction = 
connection.BeginTransaction(IsolationLevel.ReadUncommited);

SqlCommand command = new SqlCommand("");
command.Connection = connection;

foreach(string script in scripts)
{
    command.CommandText = script;
    try
    {
        command.ExecuteNonQuery();
    }
    catch()
    {
        transaction.Rollback();
    }
}
transaction.Commit();

2 个答案:

答案 0 :(得分:3)

在运行插入之前,仅检查对象是否存在或用户是否存在似乎更简单,例如:

dbfiddle.uk demo - if exists

if not exists (select 1 from sysobjects where name=N'Users' and xtype='U')
begin;
CREATE TABLE [dbo].[Users](
    [UserId] [int] UNIQUE NOT NULL,
    [UserFullName] [nvarchar](100) NOT NULL,
    [UserName] [nvarchar](100) NOT NULL,
    [UserShortName] [nvarchar](50) NOT NULL,
    [UserLogin] [varchar](50) NOT NULL
);
PRINT 'Created table'
end;
if not exists (select 1 from dbo.users where userlogin = 'SA')
begin;
INSERT INTO [dbo].[Users](
    [UserId]
    ,[UserFullName]
    ,[UserName]
    ,[UserShortName]
    ,[UserLogin]
) VALUES (
    1,'System Administrator','Admin','SA',  'SA'
)
PRINT 'Inserted user'
end;

答案 1 :(得分:2)

首先,我同意SqlZim建议的解决方案可能是要走的路。

我想从Microsoft's docs添加一些信息,我认为很好地解释了你不能这样做:

  

如果TRY块中生成的错误导致当前事务的状态无效,则该事务被分类为不可提交的事务。 [...]。不可提交的事务只能执行读操作或ROLLBACK TRANSACTION。事务不能执行任何会生成写操作或COMMIT TRANSACTION的Transact-SQL语句。

换句话说;当您的脚本失败时,它会导致事务进入不可提交的状态。必须回滚事务 - 它不能被提交。由于脚本中的所有语句都在同一事务中执行,因此必须全部回滚它们。