[英]SQL Sever 2008 and Entity Framework — Improving Performance
背景:
我有一個Windows服務,該服務從SQL表(使用EF Code First方法創建)中提取記錄。 記錄經常由2個客戶端添加(每秒約10-20個),然后將其從數據庫中剝離並由我的服務處理。 為了實現冗余,有兩個客戶端監視相同的系統,並且可能正在創建重復的記錄。 我正在尋找一種方法來改善正在處理新記錄的程序的性能。
問題:
步驟1:刪除重復的條目:
// get duplicate entries
var duplicateEntities = context.OpcTagValueLogs.GroupBy(x => new { x.OpcTagId, x.SourceTimeStamp }).SelectMany(x => x.OrderBy(y => y.Id).Skip(1));
foreach (var duplicateEntry in duplicateEntries)
{
// remove duplicate entries
context.OpcTagValueLogs.Remove(duplicateEntry );
}
步驟2:獲取剩余的日志條目
var logs = context.OpcTagValueLogs.Include(x => x.OpcTag).Include(x => x.OpcTag.RuleSets).ToList();
步驟3:檢查相關規則並執行事件以處理新值
我正在嘗試盡可能優化我的程序,因為現在正在處理數據的Windows服務運行的速度幾乎比創建記錄快。 如果創建記錄的速度增加,我擔心該服務將無法跟上。
這些是我正在此表上運行的唯一查詢(除了創建記錄)。 表結構為:
有什么方法可以修改索引以提高查詢性能?
編輯:這是刪除重復項后處理日志的方式:
foreach (var log in logs.ToList()) // because items will be removed from the list during the loop, it is important to update the list on
{ // each iteration, hence the .ToList()
if (log.PriorValue == log.Value) // check to see if the prior value equals to new value
{ // I am only interested in changing values, so delete the log entry
// remove the entity
_context.OpcTagValueLogs.Remove(log);
logs.Remove(log);
_context.SaveChanges();
}
else
{
// check rules and perform actions
var ruleSets = log.OpcTag.RuleSets.ToList();
foreach (var ruleSet in ruleSets)
{
if (ruleSet.CheckRule(log.PriorValue, log.Value))
{
// perform action
// convert source timestamp to datetime
DateTime srcTS = new DateTime(1970, 1, 1).AddSeconds(log.SourceTimeStamp);
var action = ActionFactory.CreateAction(ruleSet.Action, log.PriorValue, log.Value, log.OpcTag, srcTS);
action.Execute();
}
}
// remove entity from database
_context.OpcTagValueLogs.Remove(log);
_context.SaveChanges();
logs.Remove(log); // remove the entity from the local list as well
}
}
編輯2:當前方法
var ruleSets = _context.RuleSets.ToList(); // Get entire rulesets once into memory
var logsLocal = logs.ToList(); // bring all the logs into local memory
var maxIndex = logsLocal.Max(x => x.Id); // the last index of the local logs
foreach (var log in logsLocal)
{
if (log.PriorValue != log.Value)
{
foreach (var ruleSet in ruleSets.Where(x => x.OpcTagId == log.OpcTagId))
{
if (ruleSet.CheckRule(log.PriorValue, log.Value))
{
// perform action
var action = ActionFactory.CreateAction(ruleSet.Action, log.PriorValue, log.Value, log.OpcTag, srcTS);
action.Execute();
}
}
}
}
_context.OpcTagValueLogs.Where(x=>x.Id <= maxIndex).Delete(); // batch delete only the logs that were processed on this program loop
編輯3:操作對象是由靜態ActionFactory類根據ruleSet.Action值生成的。
public static Action CreateAction(ActionId pActionId, string pPrior, string pNew, OpcTag pOpcTag, DateTime pSourceTimestamp)
{
Action evt = null;
switch (pActionId)
{
case ActionId.A1000: evt = new A1000(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
case ActionId.A1001: evt = new A1001(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
case ActionId.A1002: evt = new A1002(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
case ActionId.A1003: evt = new A1003(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
case ActionId.A1004: evt = new A1004(pActionId, pPrior, pNew, pOpcTag, pSourceTimestamp);
break;
}
return evt;
}
這些動作中的每個動作代表一個不同的機器事件,每個動作可能是幾百行代碼(這就是為什么省略了它的原因)。
首先,您的循環可能會導致N + 1問題。 您正在循環中並從數據庫中查詢項目。 您想要的是減少I / O(對數據庫的調用),因為這是一項昂貴的操作。 如果服務器足夠強大,那么將對象預先存儲在內存中是一個更好的選擇。 您甚至可以開始使用緩存技術,但是現在可能會有點先進。
這是我想幫助解決N + 1問題的代碼(未經測試):
// check rules and perform actions
// Get entire rulesets once into memory
var ruleSets = OpcTagValueLogs.OpcTag.RuleSets.ToList();
foreach (var log in logs.ToList()) // because items will be removed from the list during the loop, it is important to update the list on
{ // each iteration, hence the .ToList()
if (log.PriorValue != log.Value) // check to see if the prior value equals to new value
{ // I am only interested in changing values, so delete the log entry
foreach (var ruleSet in ruleSets.Where(x => x.OpcTags.Logs.OpcTagValueLogs.OpcTagId == log.OpcTagId))
{
if (ruleSet.CheckRule(log.PriorValue, log.Value))
{
// perform action
// convert source timestamp to datetime
DateTime srcTS = new DateTime(1970, 1, 1).AddSeconds(log.SourceTimeStamp);
var action = ActionFactory.CreateAction(ruleSet.Action, log.PriorValue, log.Value, log.OpcTag, srcTS);
action.Execute();
}
}
}
// The below was common to both the if and else condition, hence it is moved at the end of the conditional
// remove the entity
_context.OpcTagValueLogs.Remove(log);
logs.Remove(log);
}
// Call save changes once (less I/O)
_context.SaveChanges();
我不知道類的定義,因此您將必須相應地修改代碼,尤其是對於foreach (var ruleSet in ruleSets.Where(x => x.OpcTags.Logs.OpcTagValueLogs.OpcTagId == log.OpcTagId))
。
我也重構了一些通用代碼,因為盡管您可以感覺到它是錯誤的,但可以更正它,但它沒有任何意義。
這就是Stan提到的框架 。 這是一個很好的框架,可幫助您優化EF的使用。
另外,確定正在發生的事情的最佳方法是在服務運行時查找不良查詢時運行SQL Server Profiler。
我認為,當可以在單個事務中處理數據庫時,浪費大量時間進行所有這些檢查並多次訪問數據庫。
幾個選擇。 我知道您正在使用EF,但是您的環境中是否存在使用Stored Procedure
的選項? 如果是這樣,您可以使用MERGE
語句,這樣您就只能訪問數據庫一次。
另一個選擇是在您的DbContext
上創建一個擴展方法,其作用類似於UPSERT
(閱讀: MERGE
)。 我剛剛遇到了這個類,它在尋找是否可以讓EF做upsert時為您設置。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.