一、起因
個人還是比較喜歡EF的,畢竟不用寫Sql,開發效率高,操作簡單,不過總是聽人說EF的性能不是很好,也看過別人做的測試,但是看了就以為真的是那樣。但是實際上到底是怎么樣,說實話我真的不知道。我只知道選什么的框架是基於實際情況的,博主在一個創業公司上班,選的就是EF框架,剛做了一個項目,數據也就幾萬不到,感覺性能沒那么差勁。於是,就想多弄點數據測試一下。再說一遍,本着 求真務實的方針,是針對現實中的業務需求來測試的,不是來單比性能的。你要是做個ERP系統,都去考慮千萬級並發的架構,那當我沒說。畢竟不是基於實際項目的框架選擇都是耍流氓。
二、聲明
基於實際的項目,考慮到博主一般的遇到的上線項目對於數據的增刪改操作時,操作的數據一般都是一個,兩個,多了有十幾個,對於一下同時提交幾十個數據進行增刪改的,原諒博主還沒有見過,更有甚者,提交幾百個數據進行增刪改,博主想也是沒有想過。但是在這個數量級下的增刪改操作,我相信EF還是能夠勝任的,所以本文不再測試EF的增刪改性能,因為感覺完全能夠滿足一般項目的需要。本文只測試EF的單表查詢功能,之后有時間會做復雜的鏈接查詢的測試。
三、測試條件
老百姓的配置,自己的工作電腦。
Sql Server 2012,Entity Framework 6.1.3。
四、測試數據
鑒於以前看過的測試都是兩三個字段,且數據過於簡單,以防有這方面的影響,又因為實際項目中的字段可能較多,而且數據量也比較復雜,就模擬了一個較為接近的數據表,再說一遍,本着求真務實的革命主義方針,針對現實的項目來測試。
數據量100W:
五、開始測試
做了一個WinForm的測試,界面如下:
1.進行Find測試,隨機生成id,左邊顯示查詢用時,先上代碼。
1 private PortalContext db = new PortalContext(); 2 private int count = 0; 3 private TimeSpan ts = new TimeSpan(); 4 private void btnFind_Click(object sender, EventArgs e) 5 { 6 7 count++; 8 Random r = new Random(); 9 var id = r.Next(0, 1000000); 10 txtId.Text = id.ToString(); 11 12 Stopwatch sw = new Stopwatch(); 13 sw.Start(); 14 var user = db.Users.Find(id); 15 sw.Stop(); 16 17 txtUserInfo.Text = UserToString(user); 18 ts += sw.Elapsed; 19 string time = sw.Elapsed + "(" + sw.Elapsed.Seconds + "s" + sw.Elapsed.Milliseconds + "ms)"; 20 txtDisplay.AppendText("Find查詢id(" + id + ")用時:" + time + Environment.NewLine); 21 txtData.Text = "執行" + count + "次,平均耗時" + new TimeSpan((ts.Ticks / count)); 22 }
結果如下:
可以看出,在100w數據的情況下,利用Find根據主鍵id查詢根本無壓力,至於第一次很長時間,應該是連接數據花費了一些時間。
2.進行Where測試,代碼如下。
1 private void btnWhere_Click(object sender, EventArgs e) 2 { 3 4 5 bool[] valids = new bool[] { false, true }; 6 string[] works = new[] { "程序猿", "攻城獅", "產品汪", "鍵盤俠", "代碼狗" }; 7 UserType[] userTypes = new[] { UserType.合作方, UserType.普通用戶, UserType.律師 }; 8 Random r = new Random(); 9 10 int num = r.Next(0, 4680); 11 int num2 = r.Next(0, 4680); 12 13 int max = Math.Max(num, num2); 14 int min = Math.Min(num, num2); 15 16 bool isValid = valids[num % 2]; 17 string work = works[num % 5]; 18 UserType type = userTypes[num % 2]; 19 20 txtIsValid.Text = isValid.ToString(); 21 txtWork.Text = work; 22 txtUserType.Text = type.ToString(); 23 txtAmountMin.Text = min.ToString(); 24 txtAmountMax.Text = max.ToString(); 25 26 Stopwatch sw = new Stopwatch(); 27 sw.Start(); 28 var query = db.Users.Where(u => true); 29 var queryWhere = query.Where(u =>u.UserType == type &&u.IsValid == isValid && u.Work == work && (u.Amount >= min && u.Amount <= max)).Take(1000); 30 var list = queryWhere.ToList(); 31 sw.Stop(); 32 33 labelWhere.Text = string.Format("where(u=> u.UserType=={0} && u.IsValid =={1} && u.Work == {2} u.Amount >= {3} && u.Amount <={4}).Take(1000)", 34 type,isValid,work, min, max); 35 36 string time = sw.Elapsed + "(" + sw.Elapsed.Seconds + "s" + sw.Elapsed.Milliseconds + "ms)"; 37 txtDisplay.AppendText("Where查詢到"+list.Count()+"條數據,用時:" + time + Environment.NewLine); 38 39 }
在這里用Where獲取了前1000條數據,實際項目中基本不可能這樣來,或者全部ToList()出來,考慮到項目中有些情況下確實需要全部ToList()出來一些數據,但是取1000條應該足夠了,對於其他情況下來講,這項測試沒有太大的意義,我們等會看分頁的性能。
附上一些全部ToList()出來時的測試:
當然實際是不可能這樣玩的,也就看看,看了一下內存,3w多條數據也就30M左右。
附:Where查詢的一些優化,其實這個之前是知道的,忘了往上貼了,謝謝@搵中求勝 博友的提醒,再次接着機會又測試了一下。
1.200w的數據(數據大才能體現出來效果),在沒有AsNoTracking的情況下
2.加上了AsNoTracking(),一般我們的查詢基本上不用跟蹤只要數據就行了。可以看出來性能明顯提高,同樣的數據,將近提高了一般的性能。
1 var query = db.Users.AsNoTracking().Where(u => true); 2 var queryWhere = query.Where(u =>u.UserType == type &&u.IsValid == isValid && u.Work == work && (u.Amount >= min && u.Amount <= max));
3.還有,許多情況下我們不需要全部的數據,直接先用Select()選出來一些需要的字段,也會提高不少性能。
1 var query = db.Users.AsNoTracking().Where(u => true); 2 var queryWhere = query.Where(u =>u.UserType == type &&u.IsValid == isValid && u.Work == work && (u.Amount >= min && u.Amount <= max)) 3 .Select(u=>new 4 { 5 u.Id, 6 u.UserName 7 }); 8 var list = queryWhere.ToList();
3.Any,First ,Count的測試
代碼都基本一樣,這里只附上一些圖片參考。
上邊的都能查詢存在不存在,但是相比來說,Any,First 對於存在的情況下,性能很好,而count對於不存在時性能卻很好,我也不知道為什么的。感覺有時候真的可以用Count查詢存在不存在的,畢竟平均效果好。PS:以前看一篇文章說Count比Any差了不知道多少倍,查詢存在不存在推薦用Any。現在看來,也差不多啊。
4.分頁查詢。
從實際項目來看,用戶在看分頁數據時,一般都是翻看前10頁左右,而且每頁的數據量也大概在10-30個之間,太多了沒必要。所有分頁的pageIndex和pageSize都設置在了這些數據之間,可能頁碼的大小pageIndex,pageSize過大的時候也會影響性能,這個我們隨后再加以測試。
200ms左右吧,基本還說的過去,可能是在排序的問題上花費了太多的時間。
附上一張pageIndex比較大的測試結果(pageIndex在800-1000之間),果然頁碼比較大的時候花費時間變長了,pageSize就不用說了,肯定時間也會變長。
5.Contains查詢
這里代碼稍微做了改動,感覺也跟這個沒關系
private void btnContains_Click(object sender, EventArgs e) { string[] usernames = new[] { "zhao", "wang", "li", "san", "zhaoliu" }; bool[] valids = new bool[] { false, true }; string[] works = new[] { "序猿", "攻城", "產品汪", "盤俠", "代碼" }; .... //全名稱改成了部分名稱,能保證是模糊查詢吧。。[笑] }
感覺確實有點慢,500ms左右,畢竟Contains,畢竟like,畢竟100w數據吧,有些條件下還是可以接受的,畢竟方便,做個自己用的查詢還是可以的。
六、數據量加大
既然是百萬級別,也不能只有一百萬。
1.二百萬的數據
總結一下:
Find無壓力,沒區別,大概是因為主鍵索引的緣故。
Any,First,Count都還在100ms左右,還能用。
分頁已經到了400ms,感覺已經不能接受了。但是我真的還沒咋見過能分幾千頁的,這里可以先用Where過濾到一些老舊數據或者不要的數據再進行分頁應該還是不錯的。
Contains已經到了1s了,這對於用戶來說已經不能接受了,但是到了這個級別的數據,應該就用上檢索引擎了。這個就不考慮了。
2.三百萬的數據
總結一下:
Find無壓力,還是沒啥區別,大概是因為主鍵索引的緣故。
Any,First能查詢到結果時還是挺快了,Count感覺在這里更好用了。
分頁到了500ms,還是那句話,這里可以先用Where過濾到一些老舊數據或者不要的數據再進行分頁,可以看一下,分頁的總記錄數都是一,二百萬,算了自己想辦法優化吧。
Contains不說了。
4.四百萬的數據
總結一下:
Find無壓力,還是沒啥區別,大概是因為主鍵索引的緣故。
Any,First查不到就慢了,Count感覺在這里更好用了。
分頁不說了。
Contains不說了。
七、結語
當寫到這里的時候,我感覺我錯了,這些好像和EF沒有半毛錢關系,這么簡單的查詢,EF生成Sql語句應該不耗費什么時間。根本沒有發揮出EF的linq語法什么的,各種復雜查詢語句,各種連接語句的生成。納尼!!!
但是既然都到這個地步了,那就算了,就當做是對Sql Server性能的考驗吧。話說應該200w數據的情況下,EF應該還是可以隨便這樣用的,再說了,我的用的是自己的個人電腦,要是用服務器肯定無壓力的。
感覺EF快不快還是和程序員寫的語句有關吧,怎么獲取數據,怎么查詢,怎么拼接,畢竟到最后都是生成sql語句去查詢,所以瓶頸應該在如何快速的生成高效的Sql語句。
對於一個創業公司,剛開始做的項目,數據連幾十萬都不到,肯定果斷用EF啊,容易上手,開發方便,不用寫Sql是最重要的,畢竟微軟的東西,都迭代這么多版本了,應該優化的差不多了吧。
PS:第一次寫博客,不知道測試的姿勢對不對,方向對不對,有錯了大神指出來,請不要噴我,我會哭的[哈哈],我只是一個只會寫增刪改查的小碼農。