【问题标题】:T-SQL transactions - is this begin/commit implementation sufficient?T-SQL 事务 - 这个开始/提交实现是否足够?
【发布时间】:2018-07-09 22:33:51
【问题描述】:

我正在创建一个 SQL Server 批处理文件来创建新的数据库对象、向表中插入数据等。如果其中一个操作失败,那么我不希望提交任何其他操作。

以下包装是否足以完成我正在尝试做的事情?:

BEGIN TRANSACTION

--script #1 - create Table1
--script #2 - create Sproc1
--script #3 - insert data load into Table1

COMMIT

因此,如果 SS 在嵌套在上面定义的事务中的任何脚本中遇到错误,则不会提交任何 SQL DDL 或数据加载,对吗?

我假设这里不需要明确的 ROLLBACK。在任何情况下我都需要显式包含 ROLLBACK 语句?

【问题讨论】:

  • 您使用的是 InnoDb 还是 MySQL? MySQL 将CREATE TABLE 作为单个事务处理。因此,即使您将其放在外部事务包装器中,它也无法撤消。
  • 我使用的是 SQL Server 2016
  • 你会想要使用 BEGIN TRY... CATCH 和明确的 ROLLBACK
  • 可以在事务中创建存储过程吗?
  • @Larnu - 如果在“BEGIN TRANSACTION”之后但在“COMMIT”之前发生错误,最新版本的 sql server 不会简单地在幕后回滚当前事务吗?

标签: sql sql-server tsql transactions acid


【解决方案1】:

http://sommarskog.se/error-handling-I.html#whathappens。有些错误只会终止正在运行的语句,并且会在下一条语句继续执行。那会很糟糕。

您可以通过将 XACT_ABORT 设置为 ON、确保在您的脚本中未将其设置为 OFF、并在出错时回滚或关闭连接来完成这项工作。例如:

SET XACT_ABORT ON
BEGIN TRANSACTION

--script #1 - create Table1
--script #2 - create Sproc1
--script #3 - insert data load into Table1

COMMIT

但正如@Larnu 所说,最好使用TRY..CATCH 并明确回滚交易。

请注意,某些 DDL 语句必须在它们自己的批处理中,或者是批处理中的第一条语句。因此,您必须在脚本中使用动态 SQL 或多个批处理。并且 TRY..CATCH 仅在单个批次中有效。

【讨论】:

  • 这是关于 XACT_ABORT 的有趣信息。我认为大多数非 sql 专家会假设当嵌套 sql 语句中发生错误时,标准的“BEGIN TRANSACTION / COMMIT”将实现完全回滚。你能想到在没有 XACT_ABORT ON 的情况下有人需要运行“BEGIN TRANSACTION / COMMIT”的任何现实场景吗?
  • 我不确定您所说的以下语句是什么意思:“请注意,某些 DDL 语句必须在它们自己的批处理中,或者是批处理中的第一个语句。”请详细说明一下?
【解决方案2】:

展示这一点的最简单方法是使用一些示例代码。让我们首先像你一样进行设置:

BEGIN TRANSACTION;

    DECLARE @SQL nvarchar(MAX);

    CREATE TABLE dbo.Table1 (ID int);
    --Needs to be dynamic, as CREATE PROC must be in its own batch
    SET @SQL = N'
CREATE PROC dbo.Proc1 @i int AS

    SELECT ID
    FROM dbo.Table1
    WHERE ID = @i;';
    EXEC sp_executesql @SQL;

    --Works fine
    INSERT INTO dbo.Table1 (ID)
    SELECT 1;
    --Fails
    INSERT INTO dbo.Table1 (ID)
    SELECT 0/0;   

COMMIT;

现在,这将出错,但是,如果我们尝试这样做:

SELECT *
FROM dbo.Table1;

您会注意到这是可行的,并且会返回数据。以下语句也不会返回任何错误:

EXEC dbo.Proc1 @i = 1;
GO
--Both work
DROP PROC dbo.Proc1;
DROP TABLE dbo.Table1;

没有其他语句在除以零错误时回滚,这不是您所追求的。就像我在 cmets 中所说,您需要使用 TRY...CATCH;因此您的批次看起来像:

BEGIN TRY
    BEGIN TRANSACTION Migration;

    DECLARE @SQL nvarchar(MAX);

    CREATE TABLE dbo.Table1 (ID int);

    --Needs to be dynamic, as CREATE PROC must be in its own batch
    SET @SQL = N'
CREATE PROC dbo.Proc1 @i int AS

    SELECT ID
    FROM dbo.Table1
    WHERE ID = @i;';
    EXEC sp_executesql @SQL;

    --Works fine
    INSERT INTO dbo.Table1 (ID)
    SELECT 1;
    --Fails
    INSERT INTO dbo.Table1 (ID)
    SELECT 0/0;   

    COMMIT;
END TRY
BEGIN CATCH
    ROLLBACK TRANSACTION Migration;
END CATCH

现在,如果我们尝试上述任何一种语句,它们都会失败:

--Doesn't work
SELECT *
FROM dbo.Table1;
GO
--Doesn't work
EXEC dbo.Proc1 @i = 1;
GO
--Deosn't work
DROP PROC dbo.Proc1;
DROP TABLE dbo.Table1;
GO

就像我在上面的 cmets 中所说,这意味着 SELECTEXECDROP 语句都失败了,因为对象不存在。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-25
    • 2011-04-13
    相关资源
    最近更新 更多