在提交之前,SQL Server 是否允许(使可见)在事务中的 DDL 到事务?

Eva*_*oll 9 sql-server transaction ddl sql-server-2017

在 PostgreSQL 中,我可以创建一个包含一些测试数据的表,然后在事务中将其迁移到不同类型的新列,从而导致一个表重写COMMIT

CREATE TABLE foo ( a int );
INSERT INTO foo VALUES (1),(2),(3);
Run Code Online (Sandbox Code Playgroud)

其次是,

BEGIN;
  ALTER TABLE foo ADD COLUMN b varchar;
  UPDATE foo SET b = CAST(a AS varchar);
  ALTER TABLE foo DROP COLUMN a;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

但是,Microsoft 的 SQL Server 中的相同内容似乎会产生错误。比较这个工作db fiddle,其中ADD(列)命令在事务之外,

-- txn1
BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
COMMIT;

-- txn2
BEGIN TRANSACTION;
  UPDATE foo SET b = CAST( a AS varchar );
  ALTER TABLE foo DROP COLUMN a;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

对于这个不起作用的数据库小提琴

-- txn1
BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
  UPDATE foo SET b = CAST( a AS varchar );
  ALTER TABLE foo DROP COLUMN a;
COMMIT;
Run Code Online (Sandbox Code Playgroud)

而是错误

Msg 207 Level 16 State 1 Line 2
Invalid column name 'b'.
Run Code Online (Sandbox Code Playgroud)

有没有办法使这个事务可见,就 DDL 而言,表现得像 PostgreSQL?

Pau*_*ite 17

一般来说,没有。SQL Server 编译整在执行之前在当前范围内,因此引用的实体必须存在(语句级重新编译也可能稍后发生)。主要的例外是延迟名称解析,但这适用于表,而不是列:

延迟名称解析只能在您引用不存在的表对象时使用。所有其他对象在创建存储过程时必须存在。例如,当您在存储过程中引用现有表时,您不能列出该表不存在的列。

常见的解决方法涉及动态代码(如 Joe 的回答),或将 DML 和 DDL 分成单独的批次。

对于这种特定情况,您还可以编写:

BEGIN TRANSACTION;

    ALTER TABLE dbo.foo
        ALTER COLUMN a varchar(11) NOT NULL
        WITH (ONLINE = ON);

    EXECUTE sys.sp_rename
        @objname = N'dbo.foo.a',
        @newname = N'b',
        @objtype = 'COLUMN';

COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

您仍然无法b在同一批次和范围内访问重命名的列,但它确实完成了工作。

关于 SQL Server,有一种观点认为在事务中混合 DDL 和 DML 并不是一个好主意。过去曾出现错误,这样做会导致错误的日志记录和不可恢复的数据库。尽管如此,人们还是会这样做,尤其是使用临时表时。它可能会导致一些非常难以遵循的代码。


Joe*_*ish 12

这是你要找的吗?

BEGIN TRANSACTION;
  ALTER TABLE foo ADD b varchar;
  EXEC sp_executesql N'UPDATE foo SET b = CAST( a AS varchar )';
  ALTER TABLE foo DROP COLUMN a;
COMMIT;
Run Code Online (Sandbox Code Playgroud)