在 Azure Synapse 的 SQL 池中使用事务

本文包含有关在 SQL 池中实现事务和开发解决方案的技巧。

期望

如你所料,SQL 池支持将事务作为数据仓库工作负载的一部分。 但是,为了确保 SQL 池保持在一定规模,相比于 SQL Server,某些功能会受到限制。 本文重点介绍了两者的差异。

事务隔离级别

SQL 池实现了 ACID 事务。 事务支持的隔离级别默认为 READ UNCOMMITTED。 在连接到 master 数据库时,可以通过打开用户 SQL 池的 READ_COMMITTED_SNAPSHOT 数据库选项,将其更改为 READ COMMITTED SNAPSHOT ISOLATION。

启用后,此数据库中的所有事务都将在 READ COMMITTED SNAPSHOT ISOLATION 下执行,并且将不接受在会话级别设置 READ UNCOMMITTED。 有关详细信息,请查看 ALTER DATABASE SET 选项 (Transact-SQL)

事务大小

单个数据修改事务有大小限制。 限制按每个分发进行应用。 因此,通过将限制乘以分发数,可得总分配额。

要预计事务中的最大行数,请将分发上限除以每一行的总大小。 对于可变长度列,考虑采用平均的列长度而不使用最大大小。

下表中做了两个假设:

  • 出现平均数据分布
  • 平均行长度为 250 个字节

Gen2

DWU 每个分布的上限 (GB) 分布的数量 最大事务大小 (GB) 每个分布的行数 每个事务的最大行数
DW100c 1 60 60 4,000,000 240,000,000
DW200c 1.5 60 90 6,000,000 360,000,000
DW300c 2.25 60 135 9,000,000 540,000,000
DW400c 3 60 180 12,000,000 720,000,000
DW500c 3.75 60 225 15,000,000 900,000,000
DW1000c 7.5 60 450 30,000,000 1,800,000,000
DW1500c 11.25 60 675 45,000,000 2,700,000,000
DW2000c 15 60 900 60,000,000 3,600,000,000
DW2500c 18.75 60 1125 75,000,000 4,500,000,000
DW3000c 22.5 60 1,350 90,000,000 5,400,000,000
DW5000c 37.5 60 2,250 150,000,000 9,000,000,000
DW6000c 45 60 2,700 180,000,000 10,800,000,000
DW7500c 56.25 60 3,375 225,000,000 13,500,000,000
DW10000c 75 60 4,500 300,000,000 18,000,000,000
DW15000c 112.5 60 6,750 450,000,000 27,000,000,000
DW30000c 225 60 13,500 900,000,000 54,000,000,000

Gen1

DWU 每个分布的上限 (GB) 分布的数量 最大事务大小 (GB) 每个分布的行数 每个事务的最大行数
DW100 1 60 60 4,000,000 240,000,000
DW200 1.5 60 90 6,000,000 360,000,000
DW300 2.25 60 135 9,000,000 540,000,000
DW400 3 60 180 12,000,000 720,000,000
DW500 3.75 60 225 15,000,000 900,000,000
DW600 4.5 60 270 18,000,000 1,080,000,000
DW1000 7.5 60 450 30,000,000 1,800,000,000
DW1200 9 60 540 36,000,000 2,160,000,000
DW1500 11.25 60 675 45,000,000 2,700,000,000
DW2000 15 60 900 60,000,000 3,600,000,000
DW3000 22.5 60 1,350 90,000,000 5,400,000,000
DW6000 45 60 2,700 180,000,000 10,800,000,000

事务大小限制按每个事务或操作进行应用。 不会跨所有当前事务进行应用。 因此,允许每个事务向日志写入此数量的数据。

为优化和最大程度减少写入到日志中的数据量,请参阅事务最佳做法一文。

警告

最大事务大小仅可在哈希或者 ROUND_ROBIN 分布式表(其中数据均匀分布)中实现。 如果事务以偏斜方式向分布写入数据,那么更有可能在达到最大事务大小之前达到该限制。

事务状态

SQL 池使用 XACT_STATE() 函数(采用值 -2)来报告失败的事务。 此值表示事务已失败并标记为仅可回滚。

注意

XACT_STATE 函数使用 -2 表示失败的事务,以代表 SQL Server 中不同的行为。 SQL Server 使用值 -1 来代表无法提交的事务。 SQL Server 可以容忍事务内的某些错误,而无需将其标记为无法提交。 例如,SELECT 1/0 会导致错误,但不强制事务进入无法提交状态。

SQL Server 还允许读取无法提交的事务。 但是,SQL 池不允许执行此操作。 如果 SQL 池事务内部发生错误,它会自动进入 -2 状态,并且在该语句回退之前,你无法执行任何 Select 语句。

因此,必须查看应用程序代码是否使用 XACT_STATE(),因为你可能需要修改代码。

例如,在 SQL Server 中,可能会看到如下所示的事务:

SET NOCOUNT ON;
DECLARE @xact_state smallint = 0;

BEGIN TRAN
    BEGIN TRY
        DECLARE @i INT;
        SET     @i = CONVERT(INT,'ABC');
    END TRY
    BEGIN CATCH
        SET @xact_state = XACT_STATE();

        SELECT  ERROR_NUMBER()    AS ErrNumber
        ,       ERROR_SEVERITY()  AS ErrSeverity
        ,       ERROR_STATE()     AS ErrState
        ,       ERROR_PROCEDURE() AS ErrProcedure
        ,       ERROR_MESSAGE()   AS ErrMessage
        ;

        IF @@TRANCOUNT > 0
        BEGIN
            ROLLBACK TRAN;
            PRINT 'ROLLBACK';
        END

    END CATCH;

IF @@TRANCOUNT >0
BEGIN
    PRINT 'COMMIT';
    COMMIT TRAN;
END

SELECT @xact_state AS TransactionState;

前面的代码提供以下错误消息:

Msg 111233, Level 16, State 1, Line 1 111233;当前事务已中止,所有挂起的更改都已回退。 此问题的原因:处于仅回退状态的事务未在 DDL、DML 或 SELECT 语句之前显式回退。

不会获得 ERROR_* 函数的输出值。

在 SQL 池中,该代码需要稍做更改:

SET NOCOUNT ON;
DECLARE @xact_state smallint = 0;

BEGIN TRAN
    BEGIN TRY
        DECLARE @i INT;
        SET     @i = CONVERT(INT,'ABC');
    END TRY
    BEGIN CATCH
        SET @xact_state = XACT_STATE();

        IF @@TRANCOUNT > 0
        BEGIN
            ROLLBACK TRAN;
            PRINT 'ROLLBACK';
        END

        SELECT  ERROR_NUMBER()    AS ErrNumber
        ,       ERROR_SEVERITY()  AS ErrSeverity
        ,       ERROR_STATE()     AS ErrState
        ,       ERROR_PROCEDURE() AS ErrProcedure
        ,       ERROR_MESSAGE()   AS ErrMessage
        ;
    END CATCH;

IF @@TRANCOUNT >0
BEGIN
    PRINT 'COMMIT';
    COMMIT TRAN;
END

SELECT @xact_state AS TransactionState;

现在观察到了预期行为。 事务中的错误得到了管理,并且 ERROR_* 函数提供了预期值。

所做的一切改变是事务的 ROLLBACK 必须发生于在 CATCH 块中读取错误信息之前。

Error_Line() 函数

另外值得注意的是,SQL 池未实现或不支持 ERROR_LINE() 函数。 如果代码中包含此函数,需要将它删除才能符合 SQL 池的要求。

请在代码中使用查询标签,而不是实现等效的功能。 有关详细信息,请参阅 LABEL 一文。

使用 THROW 和 RAISERROR

THROW 是在 SQL 池中引发异常的新式做法,但也支持 RAISERROR。 不过,有些值得注意的差异。

  • 对于 THROW,用户定义的错误消息数目不能在 100,000 - 150,000 的范围内
  • RAISERROR 错误消息固定为 50,000
  • 不支持 sys.messages

限制

SQL 池有一些与事务相关的其他限制。

这些限制如下:

  • 无分布式事务
  • 不允许嵌套事务
  • 不允许保存点
  • 无已命名事务
  • 无已标记事务
  • 不支持 DDL,例如用户定义的事务内的 CREATE TABLE