最近在處理表單引擎的Deadlock問題,由於引擎核心以物件導向方式開發,很難為了資料庫的更新問題去挪動物件的呼叫順序。但還是努力做了調整,每天Deadlock的發生次數終於壓到個位數,但要100%避免看來是不可能的(至少以我的能力來說是如此)。既然逃不掉,就乖乖面對吧!

一般處理Deadlock的準則是Wait And Retrial,換句話說,程式邏輯本身並沒有任何錯誤,純粹是運氣不好,跟其他Process的資料庫更新作業強碰且被SQL Server挑中變成犠牲品,在絕大部份的情況下,在我們接到Exception的同時,冤家Process也已完成它的作業,釋放Lock。所以只要再試一次,幾乎都會成功。

不過,由於Connection在歷經錯誤後,就無法再參與Tranascation,因此得重新建立Connection再跑。(我自己試的結果,連TransactionScope也要重來才會成功)

測試了好久,總算湊出Workable的演算法如下。其中DataProvider在建立時會開一條Connection,Dispose()時關閉,從頭到尾都用同一條連線做事,如此在TransactionScope可以用效率較佳的LTM機制,不必動用到OleTx(MSDTC)。重試的部分則利用SqlException.Number==-2(Lock timeout)或1205(Deadlock victim)來判定是否需要重試,若是則等待1秒後再試一次。若遇到的不是Lock或Deadlock問題,則沒有理由重試,直接throw Exception。

static void doUpdate()
{
    using (TransactionScope tx = new TransactionScope())
    {
        using (DataProvider dp = new DataProvider())
        {
            //接連新增資料到Emp及Cust兩個Table
            //如果同時有其他Process先新增Cust再新增Emp就會發生Deadlock
            dp.ExecCommand(
                "INSERT INTO Emp VALUES (999, 'Jeffrey', 1)");
            dp.ExecCommand(
                "INSERT INTO Cust VALUES (999, 'Darkthread', 1)");
        }
        tx.Complete();
    }
}
 
static void testDeadlock()
{
    for (int i = 0; i < 3; i++)
    {
        try
        {
            doUpdate();
            break;
        }
        catch (SqlException se)
        {
            if (
                se.Number == -2 //Lock Timeout
                || se.Number == 1205 //Deadlock
                )
            {
                //理論上要寫Log記下這個錯誤
                //這裡用Console.Write意思一下
                Console.WriteLine(se.Message);
                if (i == 2) //第三次不等了,丟Exception
                    throw new
                        ApplicationException("資料庫重試3次失敗!");
                else
                    //前兩次,先等待一小段時間再跑迴圈重試
                    Thread.Sleep(1000);
            }
            else
                throw;
        }
    }
}

Comments

Be the first to post a comment

Post a comment