我目前正在使用实体框架来访问数据( SQL Server 数据库)(首先是数据库)的 WPF 应用程序。
过去,数据库位于内部服务器上,即使数据库实现得很差(仅表,无视图,无索引或存储过程),我也没有注意到有关应用程序性能的任何问题。我是创建它的人,但这是我的第一份工作,我对数据库不是很好,所以我觉得实体框架是主要关注代码的最佳方法。
但是,数据库现在位于另一台服务器上,这确实很慢。正如您所猜到的那样,该应用程序现在遇到了大性能问题(加载十几行,插入新行相同的时间超过10秒)。
时间在这里不是问题,我可以花所有的时间来改善应用程序,但似乎无法决定使应用程序发展的最佳方式。< / p>
数据库非常简单(大约10个表),唯一使事情复杂化的是我将文件存储在其中。所以我不确定我是否真的可以使用我想要的任何东西。而且我不知道这是否重要,但是我需要显示很多计算字段。 有什么建议吗?
随时提出任何相关问题。
答案 0 :(得分:0)
首先,您需要运行性能分析器并在此处找到瓶颈,它可以是数据库,实体框架配置,实体框架查询等
以我的经验,实体框架是此类应用程序的不错选择,但您需要了解其工作原理。
此外,您使用的是什么实体框架?最新版本是6.2,并且在性能上有一些较旧版本没有的改进,因此,如果您使用的是旧版本,我建议您对其进行更新
答案 1 :(得分:0)
基于这些评论,我可能会猜到这主要是带宽问题。
您的应用程序在同一位置时运行正常,也许是一个交换机,千兆以太网和200m的电缆。
现在,该应用程序正试图通过未知数量的内部代理通过公用互联网通过远程服务器向远程服务器发送数据或从远程服务器检索数据,这与谁知道其他流量有关,并且它的性能也不尽人意。
您还提到过,您将文件存储在数据库中,并且模式具有Attachment.data
和Doc.file_content
之类的字段。这表明您可能试图通过一个简单的查询传输大量(也许是兆字节)的数据,而这正是您的失败之处。
一些通用指针:
答案 2 :(得分:0)
对于性能分析,我建议首先查找的是SQL事件探查器。这可以捕获EF正在运行的确切SQL语句,并帮助识别可能的性能问题。我将介绍其中的一些here。模式问题可能是最相关的起点。标题以MVC为目标,但大多数项目与WPF和任何应用程序有关。
ExpressProfiler是我用于SQL Server的一个很好的简单分析器。 (https://github.com/OleksiiKovalov/expressprofiler)
随着迁移到新服务器,它现在通过网络发送数据,而不是从本地数据库中提取数据,您注意到的性能问题很可能属于“也加载过多”的类别经常”。现在,您不仅要等待数据库加载数据,还要等待它打包并通过网络发送数据。另外,新数据库是否表示相同的数据量,仅服务于单个客户端,还是现在服务于多个客户端?对于开发人员来说,其他问题是“在我的机器上工作”,其中本地测试数据库较小,并且不处理来自服务器的并发查询。 (其中的锁等会影响性能)
从这里,使用隔离器运行该应用程序的副本(没有其他客户端点击它以减少“噪音”)。要注意的事情:
惰性加载-在这种情况下,您有查询来加载数据,但随后又发现了很多(数十个到数百个)其他查询。您的代码可能会说“运行此查询并填充此数据”,您期望它应该是1个SQL查询,但是通过触摸延迟加载的属性,这可以衍生出许多其他查询。
延迟加载的解决方案:如果需要额外的数据,请急于使用.Include()
进行加载。如果只需要一些数据,可以考虑使用.Select()
选择所需数据的视图模型/ DTO,而不要依赖完整的实体。这将消除延迟加载的情况,但是可能需要对代码进行一些重大更改才能使用视图模型/ dto。像Automapper这样的工具可以在这里大有帮助。阅读.ProjectTo()
,了解Automapper如何与IQueryable
配合使用,以消除延迟加载问题。
读取过多-加载实体可能会非常昂贵,尤其是在您不需要所有数据的情况下。性能的罪魁祸首包括过度使用.ToList()
,这会在需要数据子集或简单的存在检查或计数就足够的情况下实现整个实体集。例如,我看过执行以下代码的代码:
var data = context.MyObjects.SingleOrDefault(x => x.IsActive && x.Id = someId);
return (data != null);
这应该是:
var isData = context.MyObjects.Where(x => x.IsActive && x.Id = someId).Any();
return isData;
两者之间的区别在于,在第一个示例中,EF将有效地执行SELECT *操作,因此,在存在数据的情况下,它将把所有列返回到实体中,仅在以后检查该实体是否为当下。第二条语句将运行更快的查询,以简单地返回是否存在行。
var myDtos = context.MoyObjects.Where(x => x.IsActive && x.ParentId == parentId)
.ToList()
.Select( x => new ObjectDto
{
Id = x.Id,
Name = x.FirstName + " " + x.LastName,
Balance = calculateBalance(x.OrderItems.ToList()),
Children = x.Children.ToList()
.Select( c => new ChildDto
{
Id = c.Id,
Name = c.Name
}).ToList()
}).ToList();
像这样的语句可能会继续并变得相当复杂,但是真正的问题是.Select()之前的.ToList()。这些通常是由于开发人员尝试执行EF无法理解的事情(例如调用方法)而引起的。 (即calculateBalance()),并通过首先调用.ToList()来“运行”。这里的问题是,此时您要实现整个实体并切换到Linq2Object。这意味着相关数据(例如.Children)上的任何“接触”现在都将触发延迟加载,并且再进一步的.ToList()
调用可以使更多数据饱和到内存中,否则可以在查询中减少这些数据。要寻找的罪魁祸首是.ToList()
个电话,并尝试将其删除。在调用.ToList()之前,选择更简单的值,然后将该数据输入视图模型中,以便视图模型可以计算出结果数据。
我见过的最糟糕的罪魁祸首是由于开发人员想要在Where子句中使用函数:
var data = context.MyObjects.ToList().Where(x => calculateBalance(x) > 0).ToList();
第一个ToList()
语句将尝试使整个表饱和到内存中的实体。除了加载所有这些数据所需的时间/内存/带宽以外,对性能的重大影响还只是数据库为可靠地读取/写入数据而必须进行的锁定次数。您“触摸”的行越少,触摸的行越短,则来自多个客户端的并发操作的查询效果就更好。随着系统过渡到更多用户使用,这些问题会大大放大。
假设您已经消除了额外的延迟负载和不必要的查询,接下来要看的是查询性能。对于似乎很慢的操作,请从分析器中复制SQL语句,然后在数据库中运行该语句,同时查看执行计划。这可以提供有关可以添加以加快查询的索引的提示。同样,使用.Select()
可以通过更有效地使用索引并减少服务器需要拉回的数据量来大大提高查询性能。
对于文件存储:这些文件是作为列存储在相关表中还是在与相关记录链接的单独表中存储的?我的意思是,如果您有发票记录,并且还具有数据库中保存的发票文件的副本,是吗?
发票
或
发票
发票文件
将大型的,很少使用的数据保留在单独的表中,而不是与常用数据合并,这是一个更好的结构。这样一来,查询就可以以较小的速度快速加载实体,在需要时可以按需提取昂贵的数据。
如果您使用GUID作为键(而不是整数/长整数),您是否在利用newsequentialid()? (假设SQL Server)将密钥设置为使用newid()或在代码中使用Guid.New()会导致索引碎片和性能下降。如果通过数据库默认值填充ID,请切换它们以使用newsequentialid()来帮助减少碎片。如果您通过代码填充ID,请看一下如何编写一个模仿newsequentialid()(SQL Server)或适合您数据库的模式的Guid生成器。 SQL Server与Oracle的存储/索引GUID值有所不同,因此,将UUID字节的“类静态”部分放在数据的高位字节与低位字节之间将有助于索引性能。还要考虑索引维护和其他数据库维护作业,以帮助保持数据库服务器有效运行。
在进行索引调优时,数据库服务器报告是您的朋友。在您从代码中消除了大部分或至少一些严重的性能违规者之后,接下来的事情就是查看系统的实际使用情况。了解数据库/代码调查目标的最佳方法是数据库服务器识别出的最常用和问题查询。在这些是EF查询的情况下,通常可以根据所命中的表(由EF查询负责)进行逆向工程。抓住这些查询并通过执行计划将它们送入,以查看是否存在可能有帮助的索引。索引是开发人员忘记或过早担心的事情。索引过多可能太少而坏。我发现最好先确定实际使用情况,然后再决定要添加哪些索引。
希望这可以让您开始寻找并开始提升系统速度的事物。 :)