2010-05-06 68 views
3

首先,我喜歡LINQ TO SQL。它比直接查詢更容易使用。LINQ to SQL在高負載頁面上的替代方案

但是,有一個很大的問題:它不適用於高負載請求。我在ASP.NET MVC項目中有一些動作,每分鐘調用幾百次。

我曾經在那裏有LINQ to SQL,但由於請求數量巨大,LINQ TO SQL幾乎總是返回「行未找到或已更改」或「X更新失敗X」。這是可以理解的。例如,我必須在每次請求時增加一個值。

var stat = DB.Stats.First(); 
stat.Visits++; 
// .... 
DB.SubmitChanges(); 

但是,當ASP.NET正在處理這些// ...指令時,存儲在表中的stats.Visits值發生了變化。

我發現了一個解決方案,我創建一個存儲過程

UPDATE SET的統計資料的訪問的訪問= + 1

它工作得很好。

不幸的是,現在我越來越多了。並且它爲所有情況創建存儲過程。

所以我的問題是,如何解決這個問題?有什麼替代品可以在這裏工作嗎?

我聽說Stackoverflow與LINQ to SQL一起使用。它比我的網站更容易裝載。

回答

4

Linq to SQL本身並不是一個問題,它本身就是具有樂觀併發的預期結果,Linq to SQL默認使用該結果。

樂觀併發意味着當您更新記錄時,您會檢查數據庫中的當前版本與進行任何脫機更新之前最初檢索的副本;如果它們不匹配,則報告併發衝突(「找不到或更改的行」)。

here有更詳細的解釋。還有一個相當大的guide on handling concurrency errors。通常情況下,解決方案涉及簡單醒目ChangeConflictException和採摘的分辨率,如:

try 
{ 
    // Make changes 
    db.SubmitChanges(); 
} 
catch (ChangeConflictException) 
{ 
    foreach (var conflict in db.ChangeConflicts) 
    { 
     conflict.Resolve(RefreshMode.KeepCurrentValues); 
    } 
} 

上述版本將覆蓋任何與當前值的數據庫,無論其他什麼進行了更改。有關其他可能性,請參閱枚舉RefreshMode

您的其他選項是完全禁用您希望更新的字段的樂觀併發。您可以通過將UpdateCheck選項設置爲UpdateCheck.Never來完成此操作。這必須在實地一級完成;你不能在實體層面或全局層面上做到這一點。

也許我還應該提一下,你還沒有爲你想要解決的具體問題選擇一個非常好的設計。通過反覆更新單個行的單個列來增加「計數器」對於關係數據庫來說不是一個很好的/適當的使用。你應該做的是實際上維護一個歷史表 - 例如Visits - 並且如果你確實需要非規範化計數,那麼在數據庫本身中實現一個觸發器。試圖在應用程序級別實現一個沒有任何數據備份的站點計數器只是要求麻煩。

使用您的應用程序將實際的數據放入您的數據庫中,並讓數據庫處理聚合 - 這是數據庫擅長的之一。

+1

但是,這種解決方案不適用於這種計數訪問的特殊問題:無論採用何種解決方案策略,您最終都會丟失(即不計算)某些訪問。 – 2010-05-06 01:37:58

+0

@Fyodor:恰恰相反,你讀過最後兩段嗎?如果您的應用程序將數據附加到事務表中,則永遠不會丟失任何更新,因爲您從不更新,只能插入。 – Aaronaught 2010-05-06 01:40:13

+1

在我提取答案的初始版本後,最後兩段文字被添加,所以我沒有機會閱讀它們。現在我已經閱讀了他們,我必須反對:這是一個有問題的解決方案。儘管在某些情況下它可能是可以接受的,但保持全部操作歷史的總體策略也是要求麻煩的。當你必須實施某種歷史歸檔和彙總聚合時,時間將不可避免地到來。這意味着你必須事先考慮它,否則你註定要失敗。這大大增加了複雜性。 – 2010-05-06 01:50:18

3

使用生產者/消費者或消息隊列模型進行並非絕對必須立即發生的更新,尤其是狀態更新。不要試圖立即更新數據庫,而要保留一個更新隊列,以便將asp.net線程推送到該隊列中,然後使用寫入器進程/線程將隊列寫入數據庫。由於只有一個線程正在寫入,所以相關表/角色的爭用將少得多。

對於讀取,請使用緩存。對於大容量網站,甚至緩存數據幾秒鐘都可以產生影響。

+0

這增加了複雜性。不,我們不需要消息隊列來增加整數感謝。過度工程101總是會導致失敗。你會花95%的時間成爲消息隊列管理員解決其他/新的驚喜和隨機問題。 – Aaron 2011-03-26 05:12:26

+0

@Aaron,如果您認爲生產者/消費者隊列太複雜,無法執行,我感到抱歉。這是一種非常常見的模式,在許多情況下都有幫助。延遲寫入,尤其是如果您彙總寫入,是一個很好的例子。 – 2011-03-26 15:47:12

+0

沒有它不復雜,這是作爲一個瘋狂的教授或「讓你的產品在那裏」的區別。過多的工程造成太多的衛星系統反射。我整理出了一個很棒的優雅解決方案。由於我已經將每個視圖存儲爲一個單獨的行,所以在頁面加載時,我現在簡單地將它們整理並*嘗試*更新ViewCount字段。 9/10次,它優雅地發生,1/10次,LINQ to SQL將不允許更新(由於樂觀併發)。沒關係,下次更新時沒有併發衝突。無論如何,我正在存儲所有的行。 – Aaron 2011-04-01 01:24:01

1

首先,您可以在stats.Visits++之後立即致電DB.SubmitChanges(),這樣可以大大減少問題。

但是,這仍然不會使您免於併發衝突(即,通過兩個併發進程同時修改一段數據)。爲了對抗,你可以使用標準機制交易。隨着LINQ到SQL,您可以通過實例化一個TransactionScope類,正是如此使用事務:

using(TransactionScope t = new TransactionScope()) 
{ 
    var stats = DB.Stats.First(); 
    stats.Visits++; 
    DB.SubmitChanges(); 
} 

更新爲Aaronaught正確地指出,TransactionScope的是不會在這裏幫助,實際上。抱歉。但請繼續閱讀。

但要小心,不要讓事務的主體太長,因爲它會阻塞其他併發進程,從而顯着降低整體性能。

這讓我想到下一點:你的設計可能有缺陷。

處理高度共享數據的核心原則是設計您的應用程序,使得該數據的操作快速,簡單並且語義清晰,並且它們必須一個接一個地執行,而不是同時執行。

您描述的一項操作 - 計數訪問 - 非常簡單明瞭,所以添加事務後應該沒有問題。然而,我必須補充一點,雖然這將是明確的,類型安全的,否則「好」,但存儲過程的解決方案實際上是一個更受歡迎的解決方案。這實際上正是數據庫應用程序在過去的日子裏被設計的方式。想一想:如果在處理數據庫時沒有涉及業務邏輯,爲什麼需要從數據庫一直到您的應用程序(可能通過網絡!)獲取計數器?數據庫服務器也可以增加它,甚至不需要將任何東西發送迴應用程序。

現在,至於其他操作,隱藏在// ...後面,它似乎(按照您的描述)它們有點沉重且很長。我無法確定,因爲我沒有看到那裏有什麼,但是如果是這樣的話,你可能想把它們分成更小更快的,或者重新考慮你的設計。我真的無法用這些小小的信息告訴別人。

+0

對不起,但在這裏使用'TransactionScope'不會改變一件事情。您需要'SERIALIZABLE'隔離級別或'UPDLOCK'才能確保一次事務的讀取在另一次事務更新後發生;但即使你可以使用L2S(你不能這樣做),它也會完全破壞性能。 – Aaronaught 2010-05-06 01:47:48

+0

嗯......是的,你說得對,TransactionScope是不會做的。然而,第二個陳述並不重要:之後我會討論性能和設計。 – 2010-05-06 01:52:32

+0

對,或者只是使用日誌表,當你的客戶/僱主要求你備份你的電話號碼和/或對命中結果進行一些實際分析時,你會很高興的。因爲它會*發生,保證。 – Aaronaught 2010-05-06 02:00:12