2016-11-21 57 views
3

最近我們遇到了一個與SQL事務超時有關的非常有趣的問題。超時的聲明其實並不重要的問題的緣故,但它是一個INSERT語句W/O生成的GUID作爲重點與客戶明確的交易:防範SQL事務超時的方法

INSERT MyTable 
(id, ...) 
VALUES (<client-app-generated-guid>, ...) 

我們也有就地重試政策,所以如果命令失敗並返回SqlException,那麼它將被重試。 SQL Server(Azure SQL)有一天沒有正常運行,我們在重試期間遇到了很多奇怪的PK違例錯誤。他們是由重試實際上成功承諾上的SQL Server事務(以便導致插入與已採取的ID)。我知道SQL超時是purely client side concept,所以如果客戶認爲SqlCommand失敗 - 它可能或可能不是的意思。

我懷疑客戶端顯式事務控制通過例如包裝報表TransactionScope如下圖所示將修復99%的此類問題 - 因爲Commit其實很快&廉價操作。不過,我仍然在那裏看到警告 - 在提交階段也會發生超時。應用程序再次可能處於不可能猜測交易是否真正承諾的情況(以找出重試的必要性)。

問題是如何編寫防彈編碼(對於這種類型的問題)和通用時尚,並且只有在確認交易未提交時才重試。

using (var trx = new TransactionScope()) 
using (var con = GetOpenConnection(connectionString)) 
{ 
    con.Execute("<some-non-idempotent-query>"); 

    // what if Complete() times out?! 
    // to retry or not to retry?! 
    trx.Complete(); 
} 
+0

即使框架代碼確實有99%的報告提交事務的方式,如果您很快,仍然可以在客戶端從SQL Server獲得確認COMMIT TRAN實際上已成功執行之前拔出插件。我認爲這可能是SQL Azure中的一個問題,而僅僅因爲SQL Azure調用的SLA /延遲導致的on-premesis問題更少。 沒有辦法告訴您提交是否成功。即使是寫後讀也可能會讓某人在高容量環境中發生變化。 – PhillipH

+0

@PhillipH,謝謝!那麼如何克服這些問題呢? –

+0

如果您的提交失敗,在重試處理邏輯中,您可能需要執行條件重新插入,如在INSERT ... WHERE NOT EXISTS(SELECT ID FROM MyTable WHERE id = @id)中那樣' – Alex

回答

2

問題是,異常並不意味着交易失敗。對於任何補償行爲(如重試),你需要有一個明確的方式來告訴它是否失敗。我會建議可擴展性問題,但其技術是重要的,可擴展性問題可以通過其他方式解決。

我的解決方案;

  1. 在COMMIT之前的最後一個INSERT是將Guid寫入跟蹤表。
  2. 如果發生異常,則表示網絡故障SELECT @@ TRANCOUNT。如果它表示你仍然在交易中(大於0)(這可能永遠不會發生,但它的價值檢查),那麼你可以愉快地重新提交你的COMMIT
  3. 如果@@ TRANCOUNT返回0,那麼你不再處於交易。從跟蹤表中選擇你的Guid會告訴你你的COMMIT是否成功。
  4. 如果您的提交失敗(@@ TRANCOUNT == 0,並且您的Guid不在跟蹤表中),那麼請從BEGIN TRANSACTION開始重新提交您的整個批處理。
+0

謝謝!那些更通用的東西呢?我們能想出一種適用於任意非冪等代碼的方法嗎? –

+1

我的示例中的一般模式在技術上適用於所有SQL批處理。然而;所有SQL批處理通常取決於他們在事務批處理中發現的數據庫的狀態(以及在準備批處理時發現的先前狀態),一旦失去連接,您無法保證批處理邏輯將以相同的方式重複。您可能需要爲每個業務流程構建某種補償方案 - 重複批次可能不合適。考慮到補償與建立交易的代碼一樣重要。 – PhillipH

+0

如果交易如「更新賬戶設置餘額=餘額+100其中id = 123」那麼如何翻譯此類建議? –

2

一般的做法是:嘗試讀回剛剛嘗試插入的內容。

如果您可以讀回您嘗試插入的ID,則先前的事務成功提交,無需重試。

如果找不到您嘗試插入的ID,那麼您知道嘗試插入失敗,因此應該重試。


恐怕沒有辦法擁有適用於任何SQL語句的完全通用的模式。您的「檢查」代碼需要知道要查找什麼。

如果是帶有ID的INSERT - 那麼您正在查找該ID。

如果它是一些UPDATE,那麼檢查將是自定義的,並取決於該UPDATE的性質。

如果是DELETE,則檢查包括嘗試讀取要刪除的內容。


實際上,這裏是一個通用的圖案:任何數據修改批次具有一個或多個INSERTUPDATEDELETE語句應該具有插入一些GUID(數據修改的一些編號事務內的一個更INSERT語句交易本身)轉換爲專用審計表。然後您的檢查代碼嘗試從該專用審計表中讀取相同的GUID。如果找到GUID,那麼您知道以前的事務成功提交。如果未找到GUID,那麼您知道以前的事務已回滾並可以重試。

有了這個專用的審計表統一/標準化的檢查。檢查不再依賴於內部數據和你的數據改變代碼的細節。您的數據修改代碼和驗證代碼取決於同一個同意的界面 - 審計表。

+0

這第三部分正是我腦海中的東西!感謝您揭露這一點。無論如何,我們還是等待其他回覆。 –

+0

我已經將PhillipH的答案標記爲解決方案 - 它與您的建議一樣,但他只是首先回答。再次感謝你,弗拉基米爾! –