Hum*_*ble 3 c# sql linq entity-framework
我有一个旧的存储过程我正在重写为EF Linq查询但是proc几乎快了3倍!
这是查询语法的示例:
public string GetStringByID(long ID)
{
return dataContext.Table2.FirstOrDefault(x => x.Table2ID == ID).Table1.StringValue;
}
Run Code Online (Sandbox Code Playgroud)
这是我正在使用的sproc代码以及调用它的方法.
sproc是:
PROCEDURE [dbo].[MyQuickerProc]
@ID bigint
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS(SELECT TOP 1 ID FROM Table2 WHERE Table2ID = @Id)
BEGIN
SELECT TOP 1 t1.StringValue
FROM Table2 t2
INNER JOIN Table1 t1 ON t1.Table1ID= Table2.Table1ID
WHERE Table2ID = @ID
END
ELSE
BEGIN
SELECT TOP 1 t1.StringValue
FROM Table2 t2
INNER JOIN Table1 t1 ON t1.Table1Id = Table2.Table1ID
WHERE Table2ID IS NULL
END
END
Run Code Online (Sandbox Code Playgroud)
我把这个叫做proc:
string myString = context.MyQuickerProc(127).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)
我已经使用了单元测试并且停止观察发现Linq呼叫需要1.3秒而且sproc呼叫需要0.5秒,令人震惊的长!我正在调查失踪的FK,因为我只能假设这就是这些电话花了这么长时间的原因.
无论如何,我需要加速这个Linq查询并添加sproc所缺少的功能,并且当前的Linq查询不包含(if/else逻辑).
任何有关这方面的帮助将非常感激.提前致谢 :)
Iai*_*way 10
我们需要做的第一件事就是问" 它需要多快? ",因为如果我们不知道它需要多快,我们就不知道什么时候完成.这不是技术决定,而是业务决策.您需要一个以利益相关者为中心的"快速足够"的衡量标准,并且您需要牢记快速足够快.我们不是在寻找"尽可能快",除非有商业原因.即便如此,我们通常也在寻找"尽可能快地在预算范围内".
由于您是我的利益相关者,并且您似乎对存储过程的性能不太感到沮丧,让我们将其作为基准!
接下来我们需要做的是测量我们的系统,看看我们是否足够快.
谢天谢地你已经测量过了(虽然我们稍后会详细讨论).您的存储过程在0.5秒内运行!这够快吗?是的!任务完成!
没有理由继续花时间(和老板的钱)修理一些没有破坏的东西.你可能有更好的事情去做,所以去做吧!:d
还在?那好吧.我不是时间,人们喜欢糟糕的技术,优化实体框架查询很有趣.接受挑战!
发生什么了?为什么我们的查询这么慢?
要回答这个问题,我需要对你的模型做一些假设: -
public class Foo
{
public int Id { get; set; }
public int BarId { get; set; }
public virtual Bar Bar { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Value { get; set; }
public virtual ICollection<Foo> Foos { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
既然我们已经这样做了,我们可以看一下Entity Framework为我们制作的可怕查询: -
using (var context = new FooContext())
{
context.Database.Log = s => Console.WriteLine(s);
var query = context.Foos.FirstOrDefault(x => x.Id == 1).Bar.Value;
}
Run Code Online (Sandbox Code Playgroud)
我可以从日志中看到正在运行两个查询: -
SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[BarId] AS [BarId]
FROM [dbo].[Foos] AS [Extent1]
WHERE 1 = [Extent1].[Id]
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Value] AS [Value]
FROM [dbo].[Bars] AS [Extent1]
WHERE [Extent1].[Id] = @EntityKeyValue1
Run Code Online (Sandbox Code Playgroud)
等等,什么?为什么当我们需要的是一个字符串时,愚蠢的实体框架会对数据库进行两次往返?
让我们退一步再看看我们的查询: -
var query = context.Foos.FirstOrDefault(x => x.Id == 1).Bar.Value;
Run Code Online (Sandbox Code Playgroud)
鉴于我们对延期执行的了解,我们可以推断出什么?
延迟执行基本上意味着只要你正在使用IQueryable,实际上没有任何事情发生 - 查询是在内存中构建的,直到以后才实际执行.这有很多原因 - 特别是它允许我们以模块化方式构建查询,然后运行组合查询一次.如果context.Foos将整个Foo表立即加载到内存中,实体框架将毫无用处!
我们的查询仅在我们请求除了an之外的其他内容时运行IQueryable,例如with .AsEnumerable(),.ToList()或者特别是.GetEnumerator()等等.在这种情况下.FirstOrDefault()不返回a IQueryable,因此这比我们想要的更早地触发数据库调用.
我们所做的查询基本上是说: -
Foo与Id == 1(或null如果没有任何)Foo的BarBar的Value哇!所以,我们不仅做两个往返到数据库中,我们也发送整个Foo和Bar跨线!当我们的实体像这里的人为实体一样微小时,这并不是那么糟糕,但如果它们是更大的现实实体呢?
正如你希望从上面得到的,前两个优化规则是1)" 不要 "和2)" 先测量 "优化的第三个规则是" 避免不必要的工作 ".额外的往返和一大堆虚假数据肯定算是"不必要的",所以让我们做点什么: -
尝试1
我们要做的第一件事是尝试声明式方法."找到第Bar一个Foo带有Id == 1" 的价值".
从可维护性的角度来看,这通常是最明智的选择; 程序员的意图显然是被捕获的.但是,记住我们想要尽可能延迟执行,让我们在以下.FirstOrDefault()之后弹出.Select(): -
var query = context.Bars.Where(x => x.Foos.Any(y => y.Id == 1))
.Select(x => x.Value)
.FirstOrDefault();
SELECT TOP (1)
[Extent1].[Value] AS [Value]
FROM [dbo].[Bars] AS [Extent1]
WHERE EXISTS (SELECT
1 AS [C1]
FROM [dbo].[Foos] AS [Extent2]
WHERE ([Extent1].[Id] = [Extent2].[BarId]) AND (1 = [Extent2].[Id])
)
Run Code Online (Sandbox Code Playgroud)
尝试2
在SQL和大多数O/RM中,一个有用的技巧是确保从任何给定关系的正确"结束"查询.当然,我们正在寻找一个Bar,但我们已经拿到了Id的Foo,所以我们可以用为起点重新编写查询:"查找我Value的Bar的Foo用Id == 1": -
var query = context.Foos.Where(x => x.Id == 1)
.Select(x => x.Bar.Value)
.FirstOrDefault();
SELECT TOP (1)
[Extent2].[Value] AS [Value]
FROM [dbo].[Foos] AS [Extent1]
INNER JOIN [dbo].[Bars] AS [Extent2] ON [Extent1].[BarId] = [Extent2].[Id]
WHERE 1 = [Extent1].[Id]
Run Code Online (Sandbox Code Playgroud)
好多了.Prima Facie看起来比原始的Entity-Framework生成的混乱和原始存储过程都要好.完成!
没有!等一下!我们怎么知道我们是否足够快?我们怎么知道我们是否更快?
我们衡量!
不幸的是,你必须自己做这一点.我可以告诉你,在我的机器上,在我的网络上,模拟我的应用程序的实际负载,INNER JOIN是最快的,然后是两个往返版本(!!),然后是WHERE EXISTS版本,接着是存储过程.我不能告诉你这将是最快的在你的硬件,在你的网络中,一个现实的负载下您的应用程序.
我可以告诉你,我做了这个确切的性能优化十几次,并根据网络,数据库服务器的特点和模式我已经看到了所有三个INNER JOIN,WHERE EXISTS和两个往返提供最佳性能.
但是,我甚至不能告诉你,如果任何这些都是足够快.根据您的需要,您可能需要手动滚动一些超级优化的SQL并调用存储过程.您甚至可能需要进一步使用非规范化读取优化读取存储.为数据库结果使用内存缓存怎么样?如何为您的网络服务器使用输出缓存?如果这个查询甚至不是瓶颈怎么办?
良好的性能不是关于加速实体框架查询.良好的性能,就像我们行业中的任何事情一样,是关于了解对客户重要的事情,并找出获得它的最佳方式.
| 归档时间: |
|
| 查看次数: |
980 次 |
| 最近记录: |