我们的工作涉及更新产品,我们有一个很大的产品表,价格和其他相关信息每小时更新一次。假设它是一家亚马逊商店,我们正在谈论亚马逊产品,我们必须更新销售价格,buy box 价格等。我们每小时从亚马逊提取信息到我们的程序中并更新数据库中的数据。
我的工作流程是,将数据库中的所有产品拉入程序中(我们使用 C# 和 EF Core),更新相关产品,并将更新发送回数据库。
这种方式的代价是从数据库中读取很多信息到程序中,但是我觉得这样很高效,因为 EF Core 有变化检测,所以即使我为所有产品分配了进货产品的价格,如果有is no change EF核心不会改变任何东西,它只会为那些信息发生变化的产品生成更新语句。
此外,它不会生成大的更新语句,它会生成小的、有针对性的更新语句,例如
update products, set BuyBoxPrice = 12.23 where productid = 23345
.
我正在和一个非常有天赋的开发人员一起工作,他对 SQL 非常自然,他认为这种方式是错误的,我宁愿将所有传入的信息放在一个名为 #products 的临时表中,并将其发送到数据库中,然后运行一个应该这样做的存储过程,
update products, set BuyBoxPrice = #products.buyboxprice from products inner join #products on products.produtid = #products.productid
.
因此,这种方法避免了从数据库中进行大量读取。
我不是那么有经验,我的问题是,读取会创建锁或降低数据库性能,可能是吗?
下面是我对他的方法不满意的原因。
它创建了很多不必要的更新,这在我看来是非常浪费的,因为只有 25% 的信息发生了变化,所以为什么要更新所有列。
我的同事反驳说,我可以通过添加 where 语句来解决这个问题,比如
update products where products.buyboxprice <> #products.buyboxprice
我不认为这会减少你支付的罚款,我认为它仍然是相同的效果。
另一个主要担心是大型更新会创建锁,仅此一项就应该避免。现在我当然可以将更新分解成小于 3000 的块等。
第三点,当SQL肚子疼的时候,它会全身而退,然后开始发生奇怪的事情,客户大喊大叫老板生气,我对发生的事情几乎没有了解,但是在C#中,只要有什么崩溃就对了对我来说很清楚。
所以我的问题是,谁是对的,是通过读取和 EF 核心还是通过 SQL 进行更新的性能更高
在数据库的上下文中,做关系的事情将是最快的。即使 Entity Framework 高效地做事,在苹果对苹果的比较中,它总是会有更多的开销。
话虽如此,最大的性能差异在于您如何实现以数据库为中心的解决方案或以 C# 为中心的解决方案。我不会评论你同事推荐的替代方案,因为这似乎有点超出我的建议。我认为可能有更好的解决方案,具体取决于情况,例如使用 EF Core 的
DBContext.Find()
方法WHERE
在主键值上应用谓词,但是您的帖子提出了很多需要更多思考才能回答的重要问题,所以我会更新我的等我有时间再回答。一个快速的方法是,是的,读取确实会导致锁定表中的写入,并且将整个表拉入像 C# 应用程序这样的消费者中以仅更新几条记录通常是不高效的。相反,您应该
WHERE
在数据拉取上使用谓词来仅访问应用程序当前上下文的生命周期所需的行。(假设您的表已正确编入索引。)为了解决您提出的观点/您提出的其他问题:
“而不是将所有传入的信息放在一个名为 #products 的临时表中”:虽然使用临时表来临时保存要更新的记录的主键是一种以潜在有效的方式做事的方式,但听起来有点迂回。如果您已经拥有要更新的记录的主键 (ID),那么您可以将这些 ID 传递给存储过程(通过参数数据类型的几个不同选项),或者更好的是只编写更新使用这些 ID 声明本身并执行它。(在许多其他解决方案中只是几个想法)。同样,如果您想要一个纯 C# 解决方案,您应该能够使用 EF Core 的
DBContext.Find()
方法仅拉入需要更改的行,然后更新它们。(可能是最好的中间解决方案。“读取会创建锁还是会降低数据库性能? ”:是的,如果您经常读取大量数据,它们会导致资源争用,从而影响服务器的性能。从表中读取也会获取该表上的锁,这可能会阻止写查询并导致争用。有关锁定的更多详细信息,请参阅前面链接的 DBA.StackExchange 答案。
"造成了很多不必要的更新,在我看来是很浪费的,因为只有 25% 的信息发生了变化,所以为什么要更新所有的列。 ":我不完全理解你这里的说法,并不是所有的列都在更新基于您的同事建议的 SQL 解决方案。假设他的意思是只发送临时表中需要更新的记录的 ID,那么只有需要更新的确切行数被更新。
“我的同事反驳说,我可以通过添加 where 语句来解决这个问题…… ”:这向我表明,您之前的语句是假设所有行都应该始终传递到临时表中。根据我之前的回复,您应该只传入需要更新的行。
“我认为这不会减少您支付的罚款,我认为它仍然具有相同的效果。 ”:
WHERE
如果您的临时表仅包含需要更新的行的 ID,则无需添加您同事建议的语句. (如果您的临时表确实包含表中的每一行,那么如果不查看查询的执行计划,很难说它是否会更有效。可能它仍然比 EF Core 中拉入所有的过程更有效记录到应用程序中,然后更新它们。)另外,如果您采用不使用临时表的不同路线,并且涉及在逻辑上等同于您想要完成的 SQL 过滤(例如update products set buyboxprice = 'someValue' where someOtherField = 'someOtherValue'
) 那么过滤将比 EF Core 中的当前进程更有效(假设您有正确的索引)。“另一个主要担心是大更新创建锁”:苹果对苹果,如果您在两种情况下都有相同的查询计划,那么由于您的更新将发生相同数量的锁定。(尽管一次需要更新的行数会影响查询计划。)您可以使用分块(批处理),但通常在您一次更新大约 100,000 条记录之前实际上并不需要. 但是批处理是一种设计选择,与 C# 和 SQL 无关,您可以在任何地方完成此操作,因此这是一个有争议的问题。
“第三点,当 SQL 出现肚子问题时,它会引发全身症状......我对正在发生的事情知之甚少,但在 C# 中,每当发生崩溃时我都清楚。 ”:这不是 SQL 的问题对可用的框架和工具缺乏经验(我并不是说负面的,事实上,作为几年前处于同一状态的人)。可能有一段时间您不知道如何在 Visual Studio中使用调试器,对吗?...和断点...同样使用
try/catch
? 在您精通 C# 工具之前,您可能很难调试崩溃和错误。SQL 也是如此,此外还有性能问题!= 崩溃和错误。他们需要一种不同的思维方式和过程来进行调试。如果您的 C# 应用程序中存在与数据库无关的性能问题(例如,如果您的应用程序在将文件保存到磁盘时速度很慢),那么使用您正在使用的工具调试问题就不会像崩溃那样清晰熟悉调试崩溃。有很多很好的工具和脚本可用于调试 SQL Server 中的性能问题,请参阅下面的列表:总而言之,作为一名 8 年的 C# 开发人员和大约 3 年的 DBA,我的经验是,当结构良好且具有最大化数据库关系能力的良好流程时,管理和操作数据是最有效的(而不是试图用函数式语言完成相同的任务)。
您应该通过测试自己回答这个问题。
以一种方式或另一种方式来做更多的工作吗?是否存在有意义的性能差异?