【问题标题】:Rollback sql transaction only if unhandled error occurs仅当发生未处理的错误时才回滚 sql 事务
【发布时间】:2017-09-08 10:43:44
【问题描述】:

我正在创建一个数据库更新机制,它在一个事务中运行多个 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();

【问题讨论】:

  • 您是否有任何特殊原因不想采用 SqlZim 建议的解决方案,或者将您的 sql 脚本拆分为每个 try-catch 块的一个脚本?

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


【解决方案1】:

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

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;

【讨论】:

  • 代码本身可以工作,但问题是当你把它放在事务中时。即使我正在处理错误并显示消息,事务仍然回滚。确实,我可以检查我想要处理的所有内容,但在我看来它更脏。
  • 许多桑格利亚汽酒在争论处理错误的正确方法时被击倒:) 但是,鉴于 Rasmus 以下关于不可提交交易的信息,我同意预先检查您可以预见是在这种情况下要走的路。我个人也更喜欢这种方法。
  • 好吧,这似乎确实是要走的路。我只是觉得很烦人,微软没有提供任何选项来只回滚抛出的错误而不是捕获的错误,但我想这就是 cookie 崩溃的方式。
【解决方案2】:

首先,我同意 SqlZim 建议的解决方案可能是可行的方法。

我想补充一点来自Microsoft's docs 的信息,我认为这很好地解释了你不能这样做:

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

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

【讨论】:

    猜你喜欢
    • 2010-11-01
    • 2013-05-17
    • 1970-01-01
    • 2010-12-17
    • 2019-08-24
    • 1970-01-01
    • 2021-02-17
    • 1970-01-01
    相关资源
    最近更新 更多