本文章是由機器翻譯。
資料點
使用 Entity Framework 降低 SQL Azure 網路延遲
Julie Lerman
從本地管理的 SQL Server 資料庫切換到 Microsoft 基於雲的 SQL Azure 資料庫乍一看有些困難,實際上卻易如反掌:只需切換連接字串,便大功告成!正如我們這些開發人員在類似情況下願意看到的那樣,這種方法“確實行得通”。
但是,進行這種切換會引入網路延遲,進而大大影回應用程式總體性能。幸運的是,充分瞭解網路延遲的影響將有利於您在 SQL Azure 環境中使用 Entity Framework 降低這種影響。
分析資料存取碼
我使用 Visual Studio 2010 探查器工具 (msdn.microsoft.com/library/ms182372) 對本地網路和 SQL Azure 帳戶中的 AdventureWorksDW 資料庫的不同資料訪問活動進行了比較,並使用該探察器研究了使用 Entity Framework 從資料庫中載入一些客戶的調用。一個測試只查詢客戶,事後再通過 Entity Framework 4.0 的延遲載入功能檢索客戶銷售資訊。後面一個測試使用 Include 方法隨客戶預先載入客戶銷售資料。图 1 顯示了我用來執行這些查詢並枚舉結果的主控台應用程式。
圖 1:旨在探索性能的查詢
{
using (var context = new AdventureWorksDWEntities())
{
var warmupquery = context.Accounts.FirstOrDefault();
}
DoTheRealQuery();
}
private static void DoTheRealQuery()
{
using ( var context=new AdventureWorksDWEntities())
{
var query = context.Customers.Where(c => c.InternetSales.Any()).Take(100);
var customers = query.ToList();
EnumerateCustomers(customers);
}
}
private static void EnumerateCustomers(List<Customer> customers)
{
foreach (var c in customers)
{
WriteCustomers (c);
}
}
private static void WriteCustomer(Customer c)
{
Console.WriteLine
("CustomerName: {0} First Purchase: {1} # Orders: {2}",
c.FirstName.Trim() + "" + c.LastName, c.DateFirstPurchase, c.InternetSales.Count);
}
首先執行一個熱身查詢,目的是消耗掉將實體資料模型 (EDM) 中繼資料載入到記憶體中、預編譯視圖以及其他一次性操作的成本。然後,DoTheRealQuery 方法會查詢 Customers 實體的子集,將查詢結果放入一個清單並枚舉結果。在枚舉期間將訪問客戶的銷售資料,在此示例中,這會強制延遲載入功能返回資料庫以通過反覆運算獲取每個客戶的銷售資料。
查看本地網路中的性能
在本地網路中針對內部部署的 SQL Server 資料庫運行此代碼時,執行查詢的第一次調用用時 233 毫秒。用時不多,原因是我只檢索了客戶。當代碼運行強制延遲載入的枚舉時,將用時 195 毫秒。
現在,我將更改查詢,使其隨 Customers 預先載入 InternetSales:
context.Customers.Include("InternetSales").Where(c => c.InternetSales.Any()).Take(100);
現在,將從資料庫中返回這 100 個客戶及其全部銷售記錄。其中包含的資料要多很多。
現在,query.ToList 調用用時約 350 毫秒,比只返回 100 個客戶的用時多了近 33%。
此更改還有另一個效果:隨著代碼枚舉客戶,銷售資料已經放入記憶體。這意味著 Entity Framework 不必對資料庫進行額外的 100 次迴圈訪問。枚舉以及輸出詳細資訊只占延遲載入工作所需時間的 70%。
考慮資料量、電腦和本地網路速度以後,此特定情況下的延遲載入模式要比預先載入資料時略快一些。但是,速度仍舊足夠快,以致于無法注意到二者之間的差別。多虧有了 Entity Framework,兩種情況下的運行速度都很快。
图 2 顯示了本地網路環境中的預先載入與延遲載入之比較。ToList 列測量查詢執行時間,即代碼行 var customers = query.ToList(); 的執行時間。Enumeration 測量 EnumerateCustomers 方法的執行時間。最後,Query &Enumeration 列測量完整的 DoTheRealQuery 方法的執行時間,其中包括執行、枚舉、上下文的產生實體以及查詢本身的聲明。
圖 2 比較本地資料庫的預先載入與延遲載入
切換到 SQL Azure 資料庫
現在,我將連接字串切換到雲中的 SQL Azure 資料庫。毫不意外,我的電腦與雲資料庫之間的網路延遲開始出現,致使查詢時間長於本地資料庫操作。在此情況下無法避免延遲。但是,值得注意的是,不同任務的時間延長並不相同。某些類型請求的延遲會比另外一些類型嚴重得多。请看一下图 3。
圖 3 比較 SQL Azure 的預先載入與延遲載入
預先載入圖形仍然要比只提前載入客戶速度慢。但是,預先載入在本地比延遲載入速度慢 30%,而在 SQL Azure 中卻需要相當於延遲載入三倍的時間。
但如圖 3 所示,受網路延遲影響最大的是延遲載入。在通過預先載入將 InternetSales 資料放入記憶體中之後,枚舉資料會像第一組測試一樣快。但延遲載入會導致對雲資料庫進行 100 次額外的往返。由於存在網路延遲,每次往返時間也會加長,這兩種因素導致檢索結果時間的增加非常明顯。
枚舉所需時間比記憶體中枚舉所需時間多出若干個數量級。每次訪問資料庫獲取單個客戶的 InternetSales 資料會花費大量時間。總體時間 – 雖然只提前載入 Customers 必然會快得多,在此環境下使用延遲載入時,檢索所有資料要多花近六倍的時間。
這些分析的目的不是要指責 SQL Azure(它實際上非常高效),而是為了指出您選擇的 Entity Framework 查詢機制由於延遲問題可能會對總體性能產生負面影響。
此演示中的特定使用案例並不是典型的應用方案,因為您通常不會對較大的物件系列延遲載入相關資料。該演示要強調的是:在此情況下,一次返回大量資料(通過預先載入)要比通過多次往返資料庫來返回相同資料量高效得多。使用本地資料庫時,差別可能沒有資料在雲中時那樣明顯,所以在從本地資料庫切換到 SQL Azure 時,這種差別值得仔細調查。
根據所返回資料的形式(例如,可能是包含大量 Include 的複雜得多的查詢),有時候預先載入成本更高,有時候延遲載入成本更高。有時候甚至適合進行負載分佈:根據通過性能分析獲得的資訊,預先載入一些資料,延遲載入另一些資料。在許多情況下,最終的解決方案是將應用程式也移入雲中。Windows Azure 和 SQL Azure 在設計上可作為一個團隊協同工作。通過將應用程式移至 Windows Azure 並讓該應用程式從 SQL Azure 獲取資料,可以最大程度地提高性能。
使用投影優化查詢結果
使用本地運行的應用程式時,有時可以修改查詢,以進一步優化從資料庫返回的資料量。可以考慮的一種方法是使用投影,該方法可對返回什麼相關資料進行更多控制。Entity Framework 中的預先載入和延遲載入都不能對返回的相關資料進行篩選或排序。但通過投影可以進行此類操作。
例如,以下修改版 TheRealQuery 方法中的查詢只返回 InternetSales 實體的子集:大於或等於 1,000 的實體:
private static void TheRealQuery()
{
using ( var context=new AdventureWorksDWEntities())
{
Decimal salesMinimum = 1000;
var query =
from customer in context.Customers.Where(c =>
c.InternetSales.Any()).Take(100)
select new { customer, Sales = customer.InternetSales };
IEnumerable customers = query.ToList();
context.ContextOptions.LazyLoadingEnabled = false;
EnumerateCustomers(customers);
}
}
該查詢與前一查詢一樣返回 100 個客戶,但返回總共 155 條 InternetSales 記錄,而在不使用 SalesAmount 篩選器時則返回 661 條銷售記錄。
請記住有關投影和延遲載入的一點重要事項:投影資料時,上下文不會將相關資料識別為已載入資料。只有通過 Include 方法、Load 方法或延遲載入進行載入時,才會識別為已載入資料。因此,像我在 TheRealQuery 方法中所示,在枚舉之前禁用延遲載入是非常重要的。否則,即使 InternetSales 資料已在記憶體中,上下文也會延遲載入該資料,導致枚舉所用時間超出所需。
修改後的枚舉方法考慮了這一點:
private static void EnumerateCustomers(IEnumerable customers)
{
foreach (var item in customers)
{
dynamic dynamicItem=item;
WriteCustomer((Customer)dynamicItem.customer);
}
}
該方法還利用 C# 4 中的動態 類型執行後期綁定。
图 4 演示了通過優化投影實現的顯著性能改進。
圖 4 比較 SQL Azure 的預先載入與篩選投影
很明顯,篩選的查詢比返回更多資料的預先載入查詢速度更快。更有趣的比較是,SQL Azure 資料庫處理篩選投影的速度快大約 70%,而在本地資料庫中,篩選投影只比預先載入查詢快大約 15%。我懷疑,與投影集合相比,預先載入的 InternetSales 集合由於 Entity Framework 在內部訪問它的方式會使得記憶體中枚舉更快。但由於在此情況下的枚舉完全在記憶體中進行,因此不存在任何網路延遲。總體來說,通過投影獲得的改進優勢超出了使用投影時所付出的小小枚舉代價。
在您的網路中,切換到投影以優化查詢結果可能看起來沒有必要,但對於 SQL Azure 而言,這種優化可以顯著改進應用程式的性能。
全部轉入雲中
我討論的方案涉及本地託管並使用 SQL Azure 的應用程式或服務。您也可以改為在 Windows Azure 雲中託管應用程式或服務。例如,最終使用者可能使用 Silverlight 命中一個 Windows Azure Web 角色,該角色運行 Windows Communication Foundation,Windows Communication Foundation 反過來又訪問 SQL Azure 中的資料。在此情況下,基於雲的服務與 SQL Azure 之間將不存在網路延遲。
無論採用哪種組合,都應該記住一點:即使您的應用程式一直按預期工作,網路延遲也可影響它的性能。
Julie Lerman 是 Microsoft MVP、.NET 導師和顧問,住在佛蒙特州的山區。.您可以在全球的使用者組和會議中看到她對資料訪問和其他 Microsoft .NET 主題的演示。Lerman 是《Programming Entity Framework》(O'Reilly Media,2010)一書的作者,該書受到廣泛稱讚,她的博客位址是 thedatafarm.com/blog。 請關注她的 Twitter: julielerman.
衷心感謝以下技術專家對本文的審閱:Wayne Berry、Kraig Brockschmidt、Tim Laverty 和 Steve Yi