Sql-Server

SQL Server 在送出之前是否允許(使可見)事務內部的 DDL 到事務?

  • May 13, 2019

在 PostgreSQL 中,我可以創建一個包含一些測試數據的表,然後在事務中將其遷移到不同類型的新列,從而導致一個表重寫COMMIT

CREATE TABLE foo ( a int );
INSERT INTO foo VALUES (1),(2),(3);

其次是,

BEGIN;
 ALTER TABLE foo ADD COLUMN b varchar;
 UPDATE foo SET b = CAST(a AS varchar);
 ALTER TABLE foo DROP COLUMN a;
COMMIT;

但是,在 Microsoft 的 SQL Server 中,同樣的事情似乎會產生錯誤。比較這個工作db fiddle,其中ADD(column) 命令在事務之外,

-- 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;

到這個不起作用的數據庫小提琴,

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

而是錯誤

Msg 207 Level 16 State 1 Line 2
Invalid column name 'b'.

關於 DDL,是否有讓這個事務可見的行為,就像 PostgreSQL 一樣?

一般來說,沒有。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;

您仍然無法b在同一批次和範圍內訪問重命名的列,但它確實完成了工作。

對於 SQL Server,有一種觀點認為在事務中混合 DDL 和 DML 並不是一個好主意。過去有一些錯誤,這樣做會導致不正確的日誌記錄和無法恢復的數據庫。儘管如此,人們還是會這樣做,尤其是使用臨時表。它可能會導致一些非常難以理解的程式碼。

引用自:https://dba.stackexchange.com/questions/237935