2011-03-15 25 views
5

如果故障回滾到以前的保存點(而不是回滾整個事務),是否有任何方法在事務中添加檢查約束? ?在發生故障時將事務回滾到保存點ALTER TABLE ... ADD CONSTRAINT

在我的情況下,當ALTER TABLE ... ADD CONSTRAINT命令失敗時,事務不能回滾到保存點(嘗試這樣做會引發InvalidOperationException)。

概述演示的關鍵點:

SqlTransaction transaction = connection.BeginTransaction(); 

// ... execute SQL commands on the transaction ... 

// Create savepoint 
transaction.Save("mySavepoint"); 

try 
{ 
    // This will fail... 
    SqlCommand boom = new SqlCommand(
     "ALTER TABLE table WITH CHECK ADD CONSTRAINT ...", 
     connection, 
     transaction); 

    boom.ExecuteNonQuery(); 
} 
catch 
{ 
    // ...and should be rolled back to the savepoint, but can't. 
    try 
    { 
     transaction.Rollback("mySavepoint"); 
    } 
    catch (InvalidOperationException) 
    { 
     // Instead, an InvalidOperationException is thrown. 
     // The transaction is unusable and can only be rolled back entirely. 
     transaction.Rollback(); 
    } 
} 

而這裏的現成運行演示代碼來測試(你需要一個名爲datase「測試」):

public class Demo 
{ 
    private const string _connectionString = "Data Source=(local);Integrated security=true;Initial Catalog=test;"; 
    private const string _savepoint = "save"; 
    private static readonly string _tableName = DateTime.Now.ToString("hhmmss"); 
    private static readonly string _constraintName = "CK" + DateTime.Now.ToString("hhmmss"); 

    private static readonly string _createTable = "CREATE TABLE [dbo].[" + _tableName + "] ([one] [int] NULL,[two] [int] NULL) ON [PRIMARY]"; 
    private static readonly string _insert1 = "INSERT INTO [" + _tableName + "] VALUES (1,1)"; 
    private static readonly string _addConstraint = "ALTER TABLE [dbo].[" + _tableName + "] WITH CHECK ADD CONSTRAINT [" + _constraintName + "] CHECK (([one]>(1)))"; 
    private static readonly string _insert2 = "INSERT INTO [" + _tableName + "] VALUES (2,2)"; 


    public static void Main(string[] args) 
    { 
     // Example code! Please ignore missing using statements. 

     SqlConnection connection = new SqlConnection(_connectionString); 
     connection.Open(); 

     SqlTransaction transaction = connection.BeginTransaction(); 

     SqlCommand createTable = new SqlCommand(_createTable, connection, transaction); 
     createTable.ExecuteNonQuery(); 

     // Create savepoint 
     transaction.Save(_savepoint); 

     SqlCommand insert1 = new SqlCommand(_insert1, connection, transaction); 
     insert1.ExecuteNonQuery(); 

     try 
     { 
      // This will fail... 
      SqlCommand boom = new SqlCommand(_addConstraint, connection, transaction); 
      boom.ExecuteNonQuery(); 
     } 
     catch 
     { 
      // ...and should be rolled back to the savepoint, but can't 
      transaction.Rollback(_savepoint); 
     } 

     SqlCommand insert2 = new SqlCommand(_insert2, connection, transaction); 
     insert2.ExecuteNonQuery(); 

     transaction.Commit(); 
     connection.Close(); 
    } 
} 
+0

當我純粹在TSQL中嘗試時,出現錯誤「當前事務不能被提交併且不能被回滾到保存點,回滾整個事務。」 - 只要看看註定的交易。 – 2011-03-15 16:35:58

+0

沒有找到任何明確說明哪些錯誤導致事務被中止(或呈現爲不可承諾)的文檔,但是顯然這個錯誤似乎是其中的一個! – 2011-03-15 17:07:04

+0

事實上,缺乏關於該主題的具體文檔幾乎同樣令人討厭。 – nodots 2011-03-15 20:27:16

回答

0

我不不要以爲你可以在腳本和C#中混合使用保存點。我執行下列SQL:

BEGIN TRANSACTION 

INSERT INTO Foos (Fooname) 
VALUES ('Bar1') 

SAVE TRANSACTION MySavePoint; 

INSERT INTO Foos (FooName) 
VALUES ('Bar2') 

ROLLBACK TRANSACTION MySavePoint 

COMMIT TRANSACTION 

這將在SQL工作,將下面的代碼工作:

using (SqlConnection conn = new SqlConnection("connectionString")) 
{ 
    conn.Open(); 

    using (SqlTransaction trans = conn.BeginTransaction()) 
    using (SqlCommand comm = new SqlCommand("The Above SQL", conn, trans)) 
    { 
     comm.ExecuteNonQuery(); 
     trans.Commit(); 
    } 
} 

如果試圖trans.Rollback("MySavePoint");它會失敗,因爲trans對象不在控制的保存點 - 它不知道它。

如果拆分的SQL伸到兩個獨立的插入和使用下面的代碼:

using (SqlConnection conn = new SqlConnection("connectionString")) 
     { 
      conn.Open(); 

      using (SqlTransaction trans = conn.BeginTransaction()) 
      using (SqlCommand comm1 = new SqlCommand("INSERT INTO Foos(fooName) VALUES('Bar1')", conn, trans)) 
      using (SqlCommand comm2 = new SqlCommand("INSERT INTO Foos(fooName) VALUES('Bar2')", conn, trans)) 
      { 
       comm1.ExecuteNonQuery(); 
       trans.Save("MySavePoint"); 
       comm2.ExecuteNonQuery(); 
       trans.Rollback("MySavePoint"); 
       trans.Commit(); 
      } 
     } 

,你希望它會工作。

請注意,請務必處理實施IDisposable的對象 - 最好在using聲明中。

延伸閱讀:

http://www.davidhayden.com/blog/dave/archive/2005/10/15/2517.aspx

更新:這個faffing使用示例代碼一段時間後,它看起來像由於來自SQL來的錯誤,交易被回滾,變得無法使用。正如在另一個答案中已經指出的那樣,它看起來好像與SQL一起,由於某些錯誤,事務正在被強制回滾,無論保存點如何。唯一的辦法是重新排序對數據庫運行的命令,而不依賴於保存點,或者至少不要依賴保存點中的操作。

+0

對不起,我不明白爲什麼保存點的想法會破壞ACID。它實際上是一個事務機制,請參閱http://msdn.microsoft.com/en-us/library/ms188378.aspx – nodots 2011-03-15 16:20:41

+0

@nodots哦我看到,我是從一個未知的錯誤導致回滾的角度來看,而不是由於非關鍵行爲失敗或不需要,有條件地選擇回滾。我仍然認爲,在預先設計的條件之外發生的故障應該會使整個交易失敗 - 我認爲這些保存點是運行不影響交易的機會。 – 2011-03-15 16:22:35

+0

@亞當:我澄清了原來的帖子。沒有混合發生。問題是保存點機制__basically__有效,但並非總是如此(失敗的ALTER TABLE ... ADD CONSTRAINT語句會導致事務不可用)。另外,我找不到任何文檔可以將哪種錯誤回滾到保存點,哪些不能。 – nodots 2011-03-16 11:46:24

1

當我在TSQL中嘗試時,獲得相同的行爲。

BEGIN TRAN 

CREATE TABLE foo (col int) 

INSERT INTO foo values (1) 

SAVE TRANSACTION ProcedureSave; 

BEGIN TRY 
ALTER TABLE foo WITH CHECK ADD CONSTRAINT ck CHECK (col= 2) 
END TRY 
BEGIN CATCH 
    SELECT XACT_STATE() AS XACT_STATE 
    /*Returns -1, transaction is uncommittable. Next line will fail*/ 

    ROLLBACK TRANSACTION ProcedureSave 
    /*Msg 3931, Level 16, State 1: The current transaction cannot be committed and 
    cannot be rolled back to a savepoint. Roll back the entire transaction.*/ 
END CATCH 

GO 

SELECT @@TRANCOUNT AS [@@TRANCOUNT] /*Zero the transaction was rolled back*/ 

我沒有發現,指出該文檔哪些錯誤會導致交易的任何信息變得註定這個樣子。我認爲this connect item comment沒有這樣的文檔。

答案是,錯誤處理是 個案。它不僅取決於服務器的 ,還取決於錯誤類型 和上下文。不幸的是,有 沒有公佈的錯誤處理列表 行爲不同的錯誤。在 一般,只有服務器錯誤應該 殺死連接和極端 關閉服務器。但是當涉及到 語句中止與交易中止時, 很難總結規則 - ,即它是個案。

+0

我注意到,在運行示例代碼時,OP提供了事務將失去它的連接,但連接仍然保持打開 - 確實是非常奇怪的行爲。 – 2011-03-16 12:08:15

+0

感謝您挖掘MS Connect項目,所以看起來我可以停止查找詳盡的文檔。 :( – nodots 2011-03-16 12:34:13