【问题标题】:Entity Framework Database.ExecuteSqlCommand update randomly dropsEntity Framework Database.ExecuteSqlCommand 更新随机掉落
【发布时间】:2017-05-03 08:24:26
【问题描述】:

我正在使用 EF6 Database.ExecuteSqlCommand 在数据库中执行一些原始 SQL 更新,这似乎是一个非常简单直接的任务。但是,有些更新语句不会随机执行的情况已经发生过几次。该应用程序每天都在积极使用,一天可以插入/更新超过 10,000 行,但有两次 ONE 更新似乎没有运行,一次有大约 20 次更新似乎没有运行。

考虑以下用例:

一个名为Trip的模型,同组中的每个Trip都需要有上一个Trip和下一个Trip的外键。

大约有 5000 次行程需要插入到数据库中。为了导入这些旅行,我需要尽快插入它们,所以我使用 SqlBulkCopy 来完成任务。结果,我无法在插入期间填充外键,因此我必须在之后更新每一行。为此,我在批量复制完成后从数据库中加载所有行程,然后循环遍历它们并执行更新以设置外键。

代码如下所示:

// ... after SqlBulkCopy is done
string tripGroup = null;
Trip previousTrip = null;

foreach (Trip trip in trips)
{
    if (tripGroup != trip.TripGroup)
    {
        tripGroup = trip.TripGroup;
        previousTrip = null;
    }

    if (previousTrip != null)
    {
        uow.Context.Database.ExecuteSqlCommand("UPDATE [Trips] SET PrevTripId = {0} WHERE Id = {1}", previousTrip.Id, trip.Id);
        uow.Context.Database.ExecuteSqlCommand("UPDATE [Trips] SET NextTripId = {0} WHERE Id = {1}", trip.Id, previousTrip.Id);
    }
    previousTrip = trip;
}

表格如下所示:

问题是有时不会填充 PrevTripId 和 NextTripId。这仅发生在服务器上,我无法在本地开发机器上重现此问题。值得指出的是,在生产中应用服务器和 sql 服务器是分开的。我尝试将代码循环运行 50 次,同时不断进行数据库备份、运行选择等,但我没有运气。

我现在唯一能想到的就是监控结果并做一些记录,这样我就可以知道它什么时候会发生。但我必须等到它再次发生。

非常感谢任何可能导致问题的想法或有关如何重新创建/调试此问题的任何建议。

【问题讨论】:

  • 如果要对数据库执行 SQL 命令,为什么要使用 ORM?事实上,您不应该使用 ORM 来执行批量更新。单个 UPDATE 语句将执行与 2*N 单独更新相同的工作,速度约为 2*N 倍
  • 如何确定 PrevTripID、NextTripId 的值?您可以使用LAGLEAD T-SQL 函数来访问旅程中的下一个/上一个行程/航段。例如LAG(Id,1,NULL) OVER (PARTITION BY TripGroup ORDER BY ...) as PrevTripId, LEAD(Id,1,NULL, OVER (PARTITION BY TripGroup ORDER BY ...) as NextTripId
  • 好吧,首先,考虑一下@PanagiotisKanavos 所说的话,100% 同意,然后假设您有充分的理由使用 ORM,看起来您必须实现一些日志记录才能了解发生了什么,我可以想到几件事,但引发警报的是通过 DbContext 和 SQL 混合更新,您可能会遇到“丢失更新”的情况(第一个覆盖后者,所以它似乎没有发生)。还要记住,这两个 SQL 更新是两个独立的事务,在我看来它们应该只是一个。

标签: .net sql-server entity-framework-6


【解决方案1】:

ORM 不适合批量操作,无论这些操作涉及批量加载还是批量更新。这段代码虽然绕过了 ORM 本身,每行发送 2 条 UPDATE 语句,导致 2N 次操作加上将所有数据加载到内存中。

使用 SQL 执行批处理操作更简单、更快且更安全。几个 SQL 语句可以更新单个事务中的所有行,而无需在客户端上加载任何内容。

在这种情况下,您可以利用LAGLEAD 分析函数来检索分区中的上一个/下一个值。

假设行程按 ID 排序并按 TripGroup 分区,您可以使用以下命令检索上一个/下一个 ID:

select 
    id,
    LAG(ID,1,null) OVER (Partition by TripGroup order by id) as PrevTripID,
    LEAD(ID,1,null) OVER (Partition by TripGroup order by id) as NextTripId
from Trips;

这将返回与图片中发布的结果相同的结果。

要更新 Trips,您需要使用 CTE,因为分析函数只能在 SELECT 语句中使用:

with ids as (
select 
    id,
    LAG(ID,1,null) OVER (Partition by TripGroup order by id) as PrevTripID,
    LEAD(ID,1,null) OVER (Partition by TripGroup order by id) as NextTripId
from Trips
)
update t
set
    PevTripId =ids.PrevTripID,
    NextTripId =ids.NextTripId 
from Trips t inner join ids on ids.Id=t.id;

您只需要一条语句,而不是加载 5000 行并执行 10000 条命令。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-09
    • 2011-09-06
    • 2013-02-26
    • 1970-01-01
    • 1970-01-01
    • 2012-11-11
    • 2011-12-18
    • 2020-04-28
    相关资源
    最近更新 更多