内容提要
一、对EF框架的性能测试
增、删、改,查测试及性能优化
二、使用sql执行
增、删、改,查测试
三、对以上两种方式对比分析
一 对EF框架的测试
1插入操作测试
测试代码(关键部分)
List<Collection> list = new List<Collection>(); int i = 0; while (i < count) { Collection cll = new Collection { Author = "test", CitationNumber = 1, DiscNo = "CCNDTEMP", Downloads = 1, FileName = "JSJJ20170803A030", Period = "0", PublicationDate = DateTime.Now, PublicationName = "江苏经济报", PublisherUnit = "江苏经济报", ResourceType = "报纸", TableName = "CAINTEMP", Title = "无人驾驶汽车2020年将量产", Year = "0" }; list.Add(cll); i++; } Stopwatch stw = new Stopwatch(); stw.Start(); db.Collections.AddRange(list); stw.Stop(); var addTime = stw.ElapsedMilliseconds; Stopwatch stwS = new Stopwatch(); stwS.Start(); db.SaveChanges(); stwS.Stop(); var saveTime = stwS.ElapsedMilliseconds; Stopwatch stw = new Stopwatch(); stw.Start(); Parallel.ForEach(indexs, item => { List<Collection> list = new List<Collection>(); int i = 0; while (i < oneCounts) { Collection cll = new Collection { Author = "test" + item, CitationNumber = 1, DiscNo = "CCNDTEMP" + item, Downloads = 1, FileName = "JSJJ20170803A030" + item, Period = "0", PublicationDate = DateTime.Now, PublicationName = "江苏经济报" + item, PublisherUnit = "江苏经济报", ResourceType = "报纸", TableName = "CAINTEMP", Title = "无人驾驶汽车2020年将量产", Year = "0" }; list.Add(cll); i++; } using (CustomDbContext db = new CustomDbContext()) { db.Collections.AddRange(list); db.SaveChanges(); } }); stw.Stop(); var saveTime = stw.ElapsedMilliseconds
数据统计
单表(空表)单线程 | 插入数量(条) | AddRange(ms) | SaveChanges(ms) |
1000
| 741 | 2141 | |
752 | 1672 | ||
742 | 1873 | ||
745 | 2145 | ||
757 | 1513 | ||
781 | 2732 | ||
10000
| 835 | 12797 | |
826 | 14930 | ||
832 | 13421 | ||
835 | 11522 | ||
842 | 13963 | ||
832 | 11265 | ||
100000 | 4127 | 144663 | |
3132 | 137083 | ||
1804 | 121466 | ||
单表(空表)10线程(最大并发数2),每个线程操作100条数据 | 1000 | 未计算 | 2102 |
1845 | |||
1992 | |||
2017 | |||
2007 | |||
单表(空表)10线程(最大并发数2),每个线程操作1000条数据 | 10000
| 未计算
| 6322 |
5480 | |||
5285 | |||
单表(空表)10线程(最大并发数2),每个线程操作10000条数据 | 100000 | 30071 | |
28466 | |||
30113 | |||
单表(已有20万数据)10线程(最大并发数2),每个线程操作10000条数据 | 28638 | ||
单表(已有40万数据)10线程(最大并发数2),每个线程操作10000条数据 | 28932 | ||
单表(已有80万数据)10线程(最大并发数2),每个线程操作10000条数据 | 28982 | ||
单表(已有100万数据)10线程(最大并发数2),每个线程操作10000条数据 | 31049 |
2查询测试
测试代码(关键部分)
Stopwatch stw = new Stopwatch(); stw.Start(); var count = db.Collections.Where(m => m.Author == "test2").OrderBy(m => m.Id).ToList(); stw.Stop(); var time = stw.ElapsedMilliseconds;
数据统计
单表(已有200万数据),单条查找 | 查找说明 | 耗时(ms) |
执行DbSet<TEntity>.Find,查第100条 | 1728 | |
1665 | ||
DbSet<TEntity>.Find,查第10000条 | 1668 | |
1686 | ||
DbSet<TEntity>.Find,查第100000条 | 1664 | |
1677 | ||
Queryable.FirstOrDefault(m => m.TableName ==CAINTEMP1) | 4951 | |
4759 | ||
4771 | ||
Queryable.Where(m => m.Author ==test2) | 712 | |
719 | ||
696 | ||
Queryable.Where(m => m.Author ==test2).Count() | 3921 | |
3917 | ||
2957 | ||
3919 | ||
3734 | ||
Queryable.Where(m => m.Author ==test2).ToList() | 6841 | |
7188 | ||
6907 | ||
7351 | ||
7335 | ||
7300 | ||
Queryable.Where(m => m.Author ==test2).OrderBy(m =>m.Id) | 719 | |
711 | ||
706 | ||
Queryable.Where(m => m.Author ==test2).OrderBy(m =>m.Id).ToList() | 7823 | |
7290 | ||
7385 |
3更新操作
测试代码(关键部分)
Stopwatch stw = new Stopwatch(); using (CustomDbContext db = new CustomDbContext()) { var collection = db.Collections.Find(10000); stw.Start(); collection.Author = "修改了作者"; db.Entry<Collection>(collection).State = System.Data.Entity.EntityState.Modified; db.SaveChanges(); } stw.Stop(); var time = stw.ElapsedMilliseconds; Stopwatch stw = new Stopwatch(); using (CustomDbContext db = new CustomDbContext()) { collections = db.Collections.Where(c => c.FileName == "JSJJ20170803A0301").ToList(); collections.RemoveRange(0, 199900); stw.Start(); collections.ForEach(c => { c.Author = "修改了作者y"; db.Entry<Collection>(c).State = System.Data.Entity.EntityState.Modified; }); db.SaveChanges(); } stw.Stop(); var time = stw.ElapsedMilliseconds;
数据统计
单表(已有200万数据) | 操作 | 耗时(ms) |
更新一条 | 112 | |
115 | ||
113 | ||
更新100条 | 42140 | |
42520 | ||
更新1000条 | 407203 | |
424386 |
4删除
测试代码(关键部分)
Stopwatch stw = new Stopwatch(); stw.Start(); List<CollectionUser> collections = null; using (CustomDbContext db = new CustomDbContext()) { collections = db.CollectionUsers.Where(c => c.Collection.FileName == "JSJJ20170803A0301").ToList(); collections.RemoveRange(0, 2713); db.CollectionUsers.RemoveRange(collections); db.SaveChanges(); } stw.Stop(); var time = stw.ElapsedMilliseconds;
数据统计
单表(已有6万数据) | 操作 | 耗时(ms) |
删除1条记录 | 2080 | |
1993 | ||
1926 | ||
删除100条 | 2824 | |
3385 | ||
2480 | ||
删除400条 | 3005 | |
删除500条 | 3526 |
5针对各种优化方案的测试
贪婪加载与延迟加载
开启延迟加载要满足两个条件:
1)在定时实体时,使用virtual,public or protected修饰实体的导航属性,不能使用sealed修饰。
2)使用默认的DbContextConfiguration.LazyLoadingEnabled配置,或将其设置为true
3)使用默认的DbContextConfiguration.ProxyCreationEnabled配置,或将其设置为true
若不满足上述两个条件则为贪婪加载
查询数据统计:
加载类型及说明 | 数据量 | 耗时(ms) |
贪婪加载(未使用导航属性) | 4003 | 2128 |
2120 | ||
2181 | ||
延迟加载(未使用导航属性) | 2102 | |
2327 | ||
2064 | ||
延迟加载(使用导航属性) | 4003(关联导航属性在20000+) | >10s |
分析
在数据量小的情况下,两种数据加载模式耗时基本相同,但当数据量较大,例如本次试验中关联导航属性记录数在2万以上时,延迟加载模式耗时巨大,因此适当关闭延迟加载可提高性能;延迟加载可以实现按需获取数据,这样客户端与服务端的传输数据量有可能减小,且也会相应地减少服务器端的内存消耗。
使用AsNoTracking()
查询数据统计
说明 | 检索条件 | 耗时 |
200万的数据表 | Where(m => m.Author ==test2).OrderBy(m =>m.Id).ToList() | 9440 |
7232 | ||
9086 | ||
7435 | ||
7637 |
分析
使用AsNoTracking()第一次查询较慢,第二次比不使用AsNoTracking()快一点,不过性能提高不大。
设置IsUnicode
IsUnicode(false)则在code first模式下,string类型实体字段对应着varchar类型的表字段,
若不配置或IsUnicode(true),则对应着text类型的。
IsUnicode设置 | 检索条件 | 表字段类型 | 耗时(ms) |
true
| AsNoTracking(),Queryable.Where(m => m.Author ==test2).OrderBy(m =>m.Id).ToList() | varchar | 8407 |
10952 | |||
8528 | |||
8674 | |||
10492 | |||
11685 | |||
7659 |
分析
对于EF6来说,是否使用IsUnicode对查询速度基本没有影响。之前的版本会产生类型转换的问题,但实测来看EF6不会。
二 使用sql和MySql.Data.dll
1 添加
测试代码(关键部分)
Stopwatch stw = new Stopwatch(); stw.Start(); int loop = 10000; Collection cll = new Collection { Author = "test", CitationNumber = 1, DiscNo = "CCNDTEMP", Downloads = 1, FileName = "JSJJ20170803A030", Period = "0", PublicationDate = DateTime.Now, PublicationName = "江苏经济报", PublisherUnit = "江苏经济报", ResourceType = "报纸", TableName = "CAINTEMP", Title = "无人驾驶汽车2020年将量产", Year = "0" }; string values = "(@FileName0, @Title0, @TableName0, @DiscNo0, @ResourceType0, @Downloads0, @CitationNumber0, @Author0, @PublicationName0, @PublisherUnit0, @PublicationDate0, @Year0, @Period0)"; Parameters param = new Parameters(); for (int i = 0; i < loop; i++) { if (i < loop-1) { values = values + "," + string.Format("(@FileName{0}, @Title{0}, @TableName{0}, @DiscNo{0}, @ResourceType{0}, @Downloads{0}, @CitationNumber{0}, @Author{0}, @PublicationName{0}, @PublisherUnit{0}, @PublicationDate{0}, @Year{0}, @Period{0})" , i+1); } param.AddParameter("@FileName"+i, MySqlDbType.VarChar, 50, cll.FileName); param.AddParameter("@Title" + i, MySqlDbType.VarChar, 200, cll.Title); param.AddParameter("@TableName" + i, MySqlDbType.VarChar, 50, cll.TableName); param.AddParameter("@DiscNo" + i, MySqlDbType.VarChar, 50, cll.DiscNo); param.AddParameter("@ResourceType" + i, MySqlDbType.VarChar, 50, cll.ResourceType); param.AddParameter("@Downloads" + i, MySqlDbType.Int32, 11, cll.Downloads); param.AddParameter("@CitationNumber" + i, MySqlDbType.Int32, 11, cll.CitationNumber); param.AddParameter("@Author" + i, MySqlDbType.VarChar, 50, cll.Author); param.AddParameter("@PublicationName" + i, MySqlDbType.VarChar, 200, cll.PublicationName); param.AddParameter("@PublisherUnit" + i, MySqlDbType.VarChar, 200, cll.PublisherUnit); param.AddParameter("@PublicationDate" + i, MySqlDbType.DateTime, 50, cll.PublicationDate); param.AddParameter("@Year" + i, MySqlDbType.VarChar, 10, cll.Year); param.AddParameter("@Period" + i, MySqlDbType.VarChar, 10, cll.Period); } string sql = @"INSERT INTO collections (`FileName`, `Title`, `TableName`, `DiscNo`, `ResourceType`, `Downloads`, `CitationNumber`, `Author`, `PublicationName`, `PublisherUnit`, `PublicationDate`, `Year`, `Period`) VALUES"+values; stw.Stop(); var addTime = stw.ElapsedMilliseconds; Stopwatch stwS = new Stopwatch(); stwS.Start(); MySqlService service = new MySqlService("database=noef_testdb;server=192.168.107.13;uid=root;pwd=cnki2017;port=3306;Character Set=utf8;"); service.ExecuteNonQuery(sql, param); stwS.Stop(); var saveTime = stwS.ElapsedMilliseconds;
数据统计
单表(空表)单线程 | 插入数量(条) | 数据拼接(ms) | INSERT ExecuteNonQuery(ms) |
1000
| 93 | 235 | |
94 | 227 | ||
92 | 229 | ||
10000
| 14521 | 851 | |
14695 | 851 | ||
14825 | 857 | ||
单表(空表)10线程(最大并发数2),每个线程操作1000条数据 | 10000
| 14762(仅拼接一次的时间,即10000条) | 2870 |
2866 | |||
2682 | |||
单表(已有50万数据)10线程(最大并发数2),每个线程操作1000条数据 | 14808(仅拼接一次的时间,即10000条) | 2643 | |
单表(已有80万数据)10线程(最大并发数2),每个线程操作1000条数据 | 15066(仅拼接一次的时间,即10000条) | 2665 |
2 读取
测试代码(关键部分)
Stopwatch stwS = new Stopwatch(); stwS.Start(); string sql = @"select * from collections where Author ='test2' order by Id DESC"; MySqlService service = new MySqlService("database=ef_testdb;server=192.168.107.13;uid=root;pwd=cnki2017;port=3306;Character Set=utf8;"); service.ExecuteReader(sql); stwS.Stop(); var saveTime = stwS.ElapsedMilliseconds;
数据统计
单表(已有200万数据),单条查找 | 查找条件 | 耗时(ms) |
Id =100000 | 170 | |
159 | ||
158 | ||
单表(已有200万数据),查找多条 | FileName ='JSJJ20170803A0301' | 5403 |
4247 | ||
3613 | ||
FileName ='JSJJ20170803A0301' order by Id DESC | 10904 | |
8941 | ||
7265 | ||
6633 | ||
7048 | ||
Author ==test2 order by Id DESC | 12958 | |
8619 | ||
8481 | ||
8030 |
3 更新
测试代码(关键部分)
Stopwatch stwS = new Stopwatch(); stwS.Start(); string sql = "UPDATE collections SET Author = '不使用EF' WHERE Id =10000"; MySqlService service = new MySqlService("database=ef_testdb;server=192.168.107.13;uid=root;pwd=cnki2017;port=3306;Character Set=utf8;"); service.ExecuteNonQuery(sql); stwS.Stop(); var saveTime = stwS.ElapsedMilliseconds;
数据统计
单表(已有200万数据) | sql | 耗时(ms) |
(多条跟新)UPDATE SET Author = '不使用EF' WHERE FileName ='JSJJ20170803A0301' | 229 | |
171 | ||
172 | ||
(单条更新)UPDATE SET Author = '不使用EF' WHERE Id =10000 | 307 | |
194 | ||
218 | ||
197 |
4 删除
测试代码(关键部分)
Stopwatch stwS = new Stopwatch(); stwS.Start(); string sql = @"delete `collectionusers` from `collectionusers`,`collections` where `collections`.`Id` = `collectionusers`.`Collection_Id` and collections.FileName ='JSJJ20170803A0301'"; MySqlService service = new MySqlService("database=ef_testdb;server=192.168.107.13;uid=root;pwd=cnki2017;port=3306;Character Set=utf8;"); service.ExecuteNonQuery(sql); stwS.Stop(); var saveTime = stwS.ElapsedMilliseconds;
数据统计
单表(已有6万数据) | sql | 耗时(ms) |
delete from collectionusers where Id = 320 | 198 | |
175 | ||
221 | ||
(单条更新)UPDATE SET Author = '不使用EF' WHERE Id =10000 | 307 | |
194 | ||
218 | ||
197 | ||
UPDATE SET Author = '不使用EF' WHERE Id =10000(未找到,而未删除成功) | 195 | |
194 | ||
202 | ||
(删除2000+条记录)delete `collectionusers` from `collectionusers`,`collections` where `collections`.`Id` = `collectionusers`.`Collection_Id` and collections.FileName ='JSJJ20170803A0301' | 370 |
三 对比分析
测试环境:
两台机器A和B,A是测试程序运行机器,B是Mysql运行机器,A和B在局域网内。
A
B
AB及网络对结果的影响:
AB机器之间的网络通信耗费一定的时间,但局域网内一般很小,且不单纯看执行时间,单纯看执行时间意义不大,本测试目的是通过比较研究EF框架的性能,另外实际的系统部署中,也不会将应用与数据库部署到同一台机器。
每中操作执行3~6次左右,如果发现某次执行时间过长或过短会多执行几次,严格来讲,只有统计数据的数量达到一定程度才能得出比较接近事实的结论,但这里在满足一定条件的前提下,例如:保持网络状态良好,保持机器运行良好,保证测试程序正确,在这样的前提下减少测试次数也可以得出比较接近事实的结论;在统计分析中没有将所有数据加一对比,也没有采用取平均值等方式,因为只是想从数量级上来加以对比。
1 添加操作
数据量 | 使用EF框架 | Sql+MySql.Data.dll(简写NOEF) | 结论 | 说明 |
1000 | 741+2141 | 93+235 | 大致相差一个数量级 | 空表,单线程 |
10000 | 826+14930 | 14521+851 | 大致相等 |
分析
插入数据量是1000时相差了一个数量级,而数据量为10000为花费时间大致相等,由统计数据可见耗时主要是对待插入数据的处理,实际的数据库操作还是相当快的,所以在实际应用过程中,如果代码实现的不好,那么可能比使用EF框架的读写性能还差,好在对待插入数据的处理优化比较容易。
数据量 | 使用EF框架 | Sql+MySql.Data.dll(简写NOEF) | 结论 | 说明 |
10000 | 6322 | 14521+851 | 大致相差一个数量级,但实际使用不会这么大 | 空表,EF框架10线程,最大并发数2; NoEF单线程 |
分析
使用EF框架同时使用多线程改进插入速度,并发数为2时,性能大致提升一倍;相比NoEF单线程而言性能已相差无几,当然,并不是任何时候都可以使用多线程来提高读写速度。
数据量 | 使用EF框架 | Sql+MySql.Data.dll(简写NOEF) | 结论 | 说明 |
100000 | 28982 | 15066(一次)+2665 | 大致相差一个数量级,但实际使用不会这么大 | 表已有数据80万,10线程,最大并发数2; |
分析
两种方式都是都是10线程,数据插入速度大致相差一个数量级,考虑到NOEF方式下要处理数据的问题,那么性能相差就没有这么大了,其实实际的应用也与这种情况是相似的。
2 查找
数据量 | 使用EF框架 | Sql+MySql.Data.dll(简写NOEF) | 结论 | 说明 |
一条 | 1669 | 170 | 单纯的多条查找性能基本相同, | 表已有200万数据,检索条件相同 |
多条 | 7823 | 5403 | ||
719 | 12958 | 检索条件相同,但使用ToList() |
分析
当检索一条时并且使用Id值,检索速度相差一个数量级;而查找多条时,性能基本相同,然而会发现一个奇怪的现象,就是使用EF对检索结果ToList()与不转换,耗时相差较大。
3 更新
数据量 | 使用EF框架 | Sql+MySql.Data.dll(简写NOEF) | 结论 | 说明 |
一条 | 112 | 307 | 总体上EF更新性能比NOEF查得多 | 表已有200万数据 |
多条 | 407203 | 229 |
分析
更新一条数据EF反而比NOEF要快,但是相差也不多,可以判定性能基本一致;当更新多条时,NOEF性能明显比EF框架好,相差几个数量级。
4 删除
数据量 | 使用EF框架 | Sql+MySql.Data.dll(简写NOEF) | 结论 | 说明 |
一条 | 2080 | 221 | 删除操作EF耗时与NOEF相差一个数量级,然而多条操作 | 表已有6万数据 删除多条时,NOEF方式下一次删除2000+条记录,而EF方式下删除500条记录 |
多条 | 407203 | 370 |
分析
从NOEF方式下一次删除2000+条记录,而EF方式下删除500条记录这一结果来看,NOEF性能明显优于EF,且NOEF方式下,删除操作耗时随删除数据量平稳增长且增长率很小;但EF操作耗时随操作数据量增大而明显增大;另外,当NOEF方式下,没有找到数据而不能删除数据时,耗时202左右,也就是说数据量很小时,譬如删除几条或十几条操作时间基本等同于查询时间。
-----------------------------------------------------------------------------------------
转载与引用请注明出处。
时间仓促,水平有限,如有不当之处,欢迎指正。