【问题标题】:EF 5 Timeout on Update; Possibly due to deadlockEF 5 更新超时;可能是因为死锁
【发布时间】:2014-01-06 21:55:32
【问题描述】:

我有一个命令行工具,用于通过实体框架将数据记录批量导入/导出到应用程序数据库中。该工具适用于将新记录插入数据库,但在尝试更新似乎与 EF 锁定相关的现有记录时遇到超时错误。

我已经阅读了somemany 其他posts 关于实体框架和死锁的内容,但似乎没有一个答案适用于这种情况。我尝试将我的导入代码包装在 TransactionScope 中以及执行 SET TRANSACTION ISOLATION LEVEL SQL 命令,但都没有解决超时问题。

无论在一次调用SaveChanges 中更新了多少实体,都会发生超时。在下面的示例代码中,我将批量大小设置为 1 到 500 之间的值,总是抛出相同的异常。

这里是更新代码的精简版,后面是异常详细信息和 SQL Server 活动监视器的屏幕截图。

我正在使用从 EDMX 模型(模型优先)初始化的 Entity Framework 5 DbContext 对象。

using(var readContext = new MySourceEntities())
using(var readWriteContext = new MyTargetEntities())
{
    var query = "SELECT ...";  // Determine which records to update
    var keys  = readContext.Database.SQLQuery<int>(query);

    // Group the update into batches to improve performance. Batch()
    // extension method from MoreLINQ
    foreach (var batch in keys.Batch(BATCH_SIZE))
    {
        var sourceRecords = readContext
            .AsNoTracking()
            .Where(x => batch.Contains(x.SharedId))
            .ToList();

        var targetRecords = readWriteContext
            .Where(x => batch.Contains(x.SharedId))
            .ToLookup(x => x.SharedId);

        foreach (var record in sourceRecords)
        {
            // Enforce a constraint on having only a single match
            var target = targetRecords[record.SharedId].Single();

            target.Field = record.Field;
        }

        readWriteContext.SaveChanges();  // <--- Timeout happens here
    }
}

我从命令行应用程序运行它,它抛出的具体堆栈跟踪如下:

An error occurred while updating the entries. See the inner exception for details.
   at System.Data.Entity.Internal.InternalContext.SaveChanges()
   at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
   at System.Data.Entity.DbContext.SaveChanges()
   at <snip>

An error occurred while updating the entries. See the inner exception for details.
   at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)
   at System.Data.EntityClient.EntityAdapter.Update(IEntityStateManager entityCache)
   at System.Data.Objects.ObjectContext.SaveChanges(SaveOptions options)
   at System.Data.Entity.Internal.InternalContext.SaveChanges()

Timeout expired.  The timeout period elapsed prior to completion of the operation or the server is not responding. The statement has been terminated.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, String methodName, Boolean sendToPipe, Int32 timeout, Boolean asyncWrite)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at System.Data.Mapping.Update.Internal.DynamicUpdateCommand.Execute(UpdateTranslator translator, EntityConnection connection, Dictionary`2 identifierValues, List`1 generatedValues)
   at System.Data.Mapping.Update.Internal.UpdateTranslator.Update(IEntityStateManager stateManager, IEntityAdapter adapter)

SaveChanges 方法挂起时,SQL Server 活动监视器显示以下查询,全部处于SUSPENDED 状态。红色查询来自readContext 所针对的数据库,蓝色查询来自readWriteContext 所针对的数据库。

另外,被挂起的查询自己看起来并不可疑,只是简单的 SELECT 和 UPDATE 命令。我可以手动运行它们而不会出错。

编辑

这里是运行的query 子句的详细信息,因为它似乎是相关的。该查询跨数据库执行连接,以将记录与SharedId 匹配。从this page 运行sys.dm_os_waiting_tasks 查询,得到下表。

session_id wait_duration_ms wait_type         blocking_session_id resource_description program_name    text
55         15               ASYNC_NETWORK_IO  NULL                NULL                 EntityFramework <cross-db join query>
54         29310            LCK_M_IX          55                  pagelock fileid=1... EntityFramework <update query>

查询的内容是这样的

SELECT DB1.dbo.Table1.SharedId 
FROM DB2.dbo.Table2 INNER JOIN DB1.dbo.Table1.SharedId
ON DB1.dbo.Table1.SharedId = DB2.dbo.Table2.SharedId
WHERE (
    (DB1.dbo.Table1.Field1 <> DB2.dbo.Table2.Field1) OR
    (DB1.dbo.Table1.Field2 <> DB2.dbo.Table2.Field2)
)

对我来说最令人惊讶的观察是查询仍然处于活动状态。为什么readContext.Database.SQLQuery() 调用无法完成查询的任何想法?听起来这种等待类型通常表示应用程序错误,但我不确定我是如何触发这种行为的。

【问题讨论】:

    标签: c# sql-server entity-framework database-deadlocks


    【解决方案1】:

    解决方案是使用ToList() 扩展方法显式实现SQLQuery() 的结果。这允许在尝试 UPDATE 基础表之前完全使用结果。

    (对我来说)直接 SQL 命令没有立即返回完整结果令人惊讶——特别是因为查询返回的是原始类型而不是实体对象。仔细阅读MSDN documentation,它确实指出SQLQuery 方法返回的IEnumerable&lt;T&gt;“将在枚举时执行查询”。

    我喜欢在 EF6 中,这个方法返回一个 DbRawSqlQuery&lt;T&gt; 对象,这可能会帮助像我这样的人下次停下来更仔细地阅读文档。

    【讨论】:

      猜你喜欢
      • 2020-11-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-18
      • 2017-03-31
      • 1970-01-01
      • 1970-01-01
      • 2018-07-08
      相关资源
      最近更新 更多