首页 > 解决方案 > 超出 EF Core 异步存储过程锁定等待超时

问题描述

在我的上下文中,我正在使用 EF Core 异步运行 2 个存储过程。这导致我出现死锁和超时问题。

下面我展示了调用其他 2 个调用存储过程的方法的代码:

public PortfolioPublishJobStep...

private async Task DoExecuteAsync(ProcessingContext context)
    {
        var (startDate, endDate) = GetInterval(context);

        var portfolioApiId = context.Message.ManagedPortfolioApiId;

        using var transactionScope = TransactionScopeFactory.CreateTransactionScope(timeout: Timeout, transactionScopeAsyncFlowOption: TransactionScopeAsyncFlowOption.Enabled);

        var asyncTasks = new List<Task>();

        foreach (var publishableService in _publishableServices)
        {
            var asyncTask = publishableService.PublishAsync(portfolioApiId, startDate, endDate);

            asyncTasks.Add(asyncTask);
        }

        await Task.WhenAll(asyncTasks.ToArray()).ConfigureAwait(continueOnCapturedContext: false);

        transactionScope.Complete();
    }

在下面调用它们各自过程的类/方法......

PortfolioFinancialBenchmarkDataService...

public async Task PublishAsync(string portfolioApiId, DateTime startDate, DateTime endDate)
        {
            if (string.IsNullOrWhiteSpace(portfolioApiId))
            {
                throw new ArgumentException(nameof(portfolioApiId));
            }

            var repository = UnitOfWork.Repository<PortfolioFinancialBenchmarkData>();

            await repository.RemoveAsync(x => x.PortfolioApiId == portfolioApiId && x.ReferenceDate >= startDate && x.ReferenceDate <= endDate).ConfigureAwait(continueOnCapturedContext: false);

            var parameters = new[]
            {
                DbParameterFactory.CreateDbParameter<MySqlParameter>("@PortfolioApiId", portfolioApiId),
                DbParameterFactory.CreateDbParameter<MySqlParameter>("@StartDate", startDate),
                DbParameterFactory.CreateDbParameter<MySqlParameter>("@EndDate", endDate)
            };

            await repository.ExecuteSqlCommandAsync("CALL PublishPortfolioFinancialBenchmarkData(@PortfolioApiId, @StartDate, @EndDate);", parameters).ConfigureAwait(continueOnCapturedContext: false);
        }

和这个:

PortfolioFinancialDataService...

public async Task PublishAsync(string portfolioApiId, DateTime startDate, DateTime endDate)
        {
            if (string.IsNullOrWhiteSpace(portfolioApiId))
            {
                throw new ArgumentException(nameof(portfolioApiId));
            }

            var repository = UnitOfWork.Repository<PortfolioFinancialData>();

            await repository.RemoveAsync(x => x.PortfolioApiId == portfolioApiId && x.ReferenceDate >= startDate && x.ReferenceDate <= endDate).ConfigureAwait(continueOnCapturedContext: false);

            var parameters = new[]
            {
                DbParameterFactory.CreateDbParameter<MySqlParameter>("@PortfolioApiId", portfolioApiId),
                DbParameterFactory.CreateDbParameter<MySqlParameter>("@StartDate", startDate),
                DbParameterFactory.CreateDbParameter<MySqlParameter>("@EndDate", endDate)
            };

            await repository.ExecuteSqlCommandAsync("CALL PublishPortfolioFinancialData(@PortfolioApiId, @StartDate, @EndDate);", parameters).ConfigureAwait(continueOnCapturedContext: false);
        }

我相信问题是同时连接到数据库。

正如我在其他地方看到的那样,我认为我使用 TransactionScopeAsyncFlowOption 缓解了这种情况,但问题仍然存在。

在过程执行期间,其中一个过程提供的表之一发生死锁,并出现超时错误。

和异常消息:

MySqlConnector.MySqlException (0x80004005): Lock wait timeout exceeded; try restarting transaction

在某些时候,我还收到以下消息:

MySqlConnector.MySqlException (0x80004005): XA_RBDEADLOCK: Transaction branch was rolled back: deadlock was detected

我进行的测试:

因此,我得出结论,问题可能出在 2 笔交易的开启中,我相信其中一笔交易可能正在等待另一笔交易的结束……

标签: mysqlasp.net-coreasynchronousstored-proceduresentity-framework-core

解决方案


Mysql导致锁的情况有很多。例如:

  1. 在不提交的情况下执行 DML 操作,然后执行删除操作将锁定表。
  2. 在同一事务中插入和更新同一条数据。
  3. 表索引设计不当导致数据库死锁。
  4. 长东西,阻塞DDL,然后阻塞同一张表的所有后续操作。

解决方案

  1. 应急方法:
    1. 显示完整的进程列表;killdrop 有问题的进程。
      • Show full processlist; kill x;
    2. 有时候通过processlist,是看不到哪里有锁在等待的。当两个事务处于提交阶段时,无法反映在进程列表中。
  2. 治疗方法
    1. 从innodb_trx中选择*;
    2. 检查哪些事务占用表资源。
      1. 通过这个方法,你需要对innodb有所了解才能处理。
      2. innodb_lock_wait_timeout:InnoDB的dml操作的行级锁的等待时间
        1. innodb_lock_wait_timeout 是指事务等待获取资源的最长时间。
          • 如果超过这个时间没有分配资源,会返回申请失败;当锁等待超过设定的时间时,会报如下错误 ERROR 1205 (HY000): Lock wait timeout exceeded; 尝试重新启动事务;。
          • 参数的时间单位是秒,最小值可以设置为1s(一般不会设置这么小),最大值可以设置为1073741824秒,默认安装值为50s(默认参数设置)。
        2. 如何修改innode lock wait timeout的值?
          • set innodb_lock_wait_timeout=100; set global innodb_lock_wait_timeout=100;
          • 或者:修改参数文件/etc/my.cnfinnodb_lock_wait_timeout = 50

推荐阅读