sql-server - Entity Framework 6 vs Dapper vs SQL Server 中的存储过程 vs 请求
问题描述
很长一段时间以来,我的项目中都有 EF with LINQ with SQL Server。
我正在寻找一种方法来提高我对 DB 的请求的性能。我读了很多关于 Dapper 和 Procedures 的文章,它比 EF 更快。我将 Dapper 添加到项目中,我添加了程序....但是我的测试显示了奇怪的结果。EF 和 Dapper 以及存储过程的结果几乎相同——没有任何好处。
首先,我检查了一个有很多加入的请求。我在 Dapper 和 Procedure 和 EF 之间得到了几乎相同的结果。然后我决定用一个没有关系的简单表进行测试。
我有表 ZipCodes。有43200条记录。
我使用 EF、Dapper、存储过程和 SQL Server 中的请求对 1 000 条记录、10 000 条记录和 43200 条记录进行了测试。
小巧玲珑
string query =
"SELECT TOP (43200) [Zip]\r\n ,[City]\r\n ,[State]\r\n ,[Latitude]\r\n ,[Longitude]\r\n ,[TimeZone]\r\n ,[DST]\r\n FROM [dbo].[ZipCodes]";
using (IDbConnection connection = new SqlConnection(_connectionString))
{
var result = connection.QueryAsync<ZipCodes>(query).Result.ToList();
return result;
}
英孚
var zip = db.ZipCodes.AsNoTracking().Take(43200).ToList();
存储过程
ALTER PROCEDURE [dbo].[ZIPTest]
AS
BEGIN
SELECT TOP (43200)
[Zip], [City], [State], [Latitude], [Longitude], [TimeZone], [DST]
FROM
[dbo].[ZipCodes]
END
SQL Server 中的请求与时间
SELECT GETDATE();
SELECT TOP (43200)
[Zip], [City], [State], [Latitude], [Longitude], [TimeZone], [DST]
FROM
[dbo].[ZipCodes]
SELECT GETDATE();
在我使用秒表的代码中
string first = "", second = "", third="";
System.Diagnostics.Stopwatch swatch = new System.Diagnostics.Stopwatch();
swatch = new Stopwatch();
swatch.Start(); Dapper request;
然后
swatch.Stop();
first = swatch.Elapsed.ToString(@"m\:ss\.fff");
swatch = new Stopwatch();
swatch.Start();
等等
结果:(以毫秒为单位)
1000 10000 43200
-------------------------------------------------
EF 107 1085 4527
Dapper 139 1084 4036
Stored procedure 129 1089 4519
SQL query 8 17 60
EF、Dapper 和存储过程之间的区别非常小。为什么会这样?
为什么 SQL Server 中的查询如此之快,而来自代码的请求却慢了 15-70 倍?
好还是不好?
解决方案
使用 EF 表现出性能问题的代码不会神奇地使用 Dapper 或 ADO + Sprocs 运行得更快。要深入了解性能问题,您需要调查并消除性能问题的原因。
在顶层,这些性能问题源于两个核心问题。
- 加载太多数据。
- 加载数据过于频繁。
我要注意的关键事项:(作为开始,还有更多项目,但这些是最大的胜利)
延迟加载:这是从一组相关实体加载代码的地方,但这样做,代码在初始加载后访问这些相关实体,导致这些相关实体中的每一个被单独加载。
- 狩猎方法:将 SQL 分析器与仅运行应用程序的调试实例的数据库挂钩。(即本地数据库)
- 症状:在加载单个实体或集合的主查询之后看到大量“SELECT TOP (1)...”查询。
- 修复:快速修复是引入急切加载 (
.Include()
) 来加载这些集合。更好的解决方法是使用.Select()
仅加载相关代码所需的属性。
.ToList()
:.ToList()
随着系统的成熟,错误的调用可能会导致巨大的性能问题,因为开发人员遇到了 EF 的问题,通过调用.ToList
..Where()
通常,当开发人员尝试在or.Select()
表达式中调用方法时,这些会出现。EF 无法理解这些以传递等效的 SQL,因此添加将其.ToList()
转换为 Linq2Object 和“ta-da”就可以了!- 狩猎方法:查找并标记在 a之前
.ToList()
找到a或etc的任何案例。.ToList()
.Select()
.Where()
- 症状:删除多余的
.ToList()
会导致 EF 错误。 - 修复:检查有问题的函数是否具有等效的 DbFunctions。一个常见问题是使用 DbFunctions 中的 DateTime 函数。在其他情况下,找到有问题的函数并为正在选择的预期数据创建一个视图模型,然后创建一个属性以在视图模型中运行该函数。
- 狩猎方法:查找并标记在 a之前
客户端分页 + 实体:没有适当样本数据的开发的另一个罪过。编写的查询可以有效地返回所有数据,而不考虑总记录数。数据在带有分页的网格中显示在客户端,这“有效”但真的很慢。当数据库中只有 200 行时它工作得很好,但现在爬了 50,000 行。(而且只会变得更糟)
- Hunting Method:查看任何返回集合的 API/Controller 方法。这些查询是否使用
.ToList()
or.Skip()
+.Take()
?这些方法是否返回实体或视图模型? - 症状:分页列表加载速度很慢。加载后,切换页面很快。
- 修复:应修改来自分页控件的调用以使用服务器端分页。这意味着将排序、页面大小和页面# 信息发送到服务器调用。这样,可以使 EF 查询更有效地仅加载控件需要显示的行数。代码还应该返回仅显示显示列的搜索结果的视图模型,以及按需加载完整实体所需的键。(例如当用户单击打开记录时。)当您只需要显示少数字段时,实体可以是重量级的。
- Hunting Method:查看任何返回集合的 API/Controller 方法。这些查询是否使用
数据库索引:是否对数据库进行了监控和维护?是否有索引和索引维护?对于 SQL Server,是否正在备份数据库并使其 Tx Log 缩小?Code First 实现充斥着这些问题,即系统在不考虑后备数据库的情况下启动。随着系统的发展,支持它的数据库不再受到关注。
- 狩猎方法:您是否有专门的 DBA 负责管理数据库并明确说明它的性能达到标准?
- 症状:没有对数据库进行 DBA 或审查。该应用程序正在使用例如设置为 NEWID() 或
Guid.New()
没有任何索引维护的 GUID PK。数据库没有设置索引。事务日志 (.LDF) 比数据库文件 (.MDF) 等大 15 倍等等等等。 - 修复:聘请 DBA。如果您使用 GUID 键,请切换到 NEWSEQUENTIALID() 并建立一些计划的索引维护作业。
推荐阅读
- java - Spinner 可以很好地保存所选项目,但是当您重新打开应用程序时显示为 null
- c# - 为什么不是所有的 EnvDTE 程序集都是 Primary Interop……我很混乱?
- angular - 尝试使用变量创建 AngularFireStorageReference 时出现问题
- powershell - 自动 PowerShell 响应提示
- winapi - 短名称和并行文件副本
- c++ - Eclipse CDT 语法错误但仍可编译
- python - 链表方法与函数
- angular - 在 Angular 6 中使用 FormData 上传文件时出现问题
- javascript - nuxt build (SPA) 生成 ./nuxt/dist/**
- php - PHP PDO:在布尔值上调用成员函数 fetch()