求助人:叶飞
报错异常
2023年11月10日 15点09分

同一个事务中,先Insert再Update,有时候会发生死锁

就是“一起帮”发布求助时出的问题,业务逻辑是:1、生成一个帮帮币记录;2、将该帮帮币记录Id挂到求助人身上——这些都应该是在同一个事务中完成的。
但是,就偶尔会报这种错。本地和ite环境都死活复现不了,生产环境好几次了(很频繁,但不绝对)

看到这样的说法:

如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。

但前面的Insert语句必然是已经执行成功了的呀? 生产的`MoneyLog_Id` 就是 71吧?13:00:38 UPDATE `User` SET `MoneyLog_Id`=102 WHERE (`Id` = 1) AND (`MoneyLog_Id` = 71) Error Code: 1205. Lock wait timeout exceeded; try restarting transaction 51.047 sec

SET SESSION sql_mode='ANSI';INSERT INTO `AidMoneyLogs`(
`BanlanceOfAvailable`, 
`BanlanceOfFrozen`, 
`BanlanceOfDebit`, 
`AmountOfExchange`, 
`AmountOfFrozen`, 
`AmountOfDebit`, 
`Kind`, 
`Comment`, 
`CreateTime`, 
`Owner_Id`) VALUES (
9, 
41, 
40, 
-30, 
30, 
0, 
2, 
@gp1, 
@gp2, 
1);
SELECT
`Id`
FROM `AidMoneyLogs`
 WHERE  row_count() > 0 AND `Id`=last_insert_id()
-- @gp1: '因求助<a class="text-info" href="/P/35">本地的修改,发布到生成环境,没有产生任何变化</a>被冻结' (Type = String, IsNullable = false, Size = 66)
-- @gp2: '2023/11/10 12:09:48' (Type = DateTime, IsNullable = false)
-- Executing at 2023/11/10 12:09:48 +08:00
-- Completed in 1 ms with result: EFMySqlDataReader

UPDATE `User` SET `MoneyLog_Id`=102 WHERE (`Id` = 1) AND (`MoneyLog_Id` = 71)
-- Executing at 2023/11/10 12:09:48 +08:00
-- Failed in 30505 ms with error: Fatal error encountered during command execution.

点击查看
关键字: MySQL web 死锁 Insert Update 事务
  • 悬赏了: 帮帮币 200 枚
  • 豪猪不挡道 处理中……
  • 工作中遇到的问题, 需要保密允许公共编辑
  • 演示/调试环境:分享桌面
  • 期望完成时间: 2023年11月10日 14点50分
协助进程

已支付
2023年11月10日 15点21分 邀请了帮主nottyjay
2023年11月10日 15点50分 邀请了帮主豪猪不挡道
2023年11月10日 16点02分 帮主豪猪不挡道接受了邀请,接头暗号已发送
2023年11月20日 15点07分 协助由帮主豪猪不挡道成功完成
2023年11月20日 15点10分 做出评价,并将悬赏支付给帮主豪猪不挡道
求助人评价:
解决方案
2023年11月20日 15点10分
作者 豪猪不挡道
已搞定。1.删除外键 2.调整代码,减少双重回滚
0 0 演示用,功能实现中……
2023年11月20日 15点50分
作者 叶飞

--- 2023年11月30日补充  ---

好像是transaction isolation level 默认是repeatable read的原因。



非常感谢豪猪不挡道的协助。但

  1. 没有删除外键(约束)。因为水平/人手都非常有限,难免会有bug,没有外键约束数据错误会出人命的。我知道很早阿里就这样干,但强烈建议童靴们有自己的判断,搞清楚自己和自己团队的实力,不要盲目跟风。
  2. 事务双重回滚本身也不是问题,因为调用事务回滚之前是做了判断的。

问题应该在这里:

public class AidEndDbContext : ActionFilterAttribute, IExceptionFilter
{
    public BaseService service { get; set; }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        if (!filterContext.IsChildAction)
        {
            service.Commit();

之前我们的架构是每一个Action(OnActionExecuted)结束的时候就提交事务,但这一次是在View呈现完成之后(OnActionExecuted())提交事务。

在代码的复制粘贴的过程中,忘记了用:

if (!filterContext.IsChildAction)
确保只有当一个完整的View/Action(而不是PartialView/ChildAction)呈现结束后,才提交事务。

所以就会造成:

  1. Action开启一个连接(并开始事务
  2. ChildAction-1结束时,就提交整个Action的事务;
  3. ChildAction-2仍然使用当前连接(因为依赖注入生命周期),
    builder.RegisterAssemblyTypes(typeof(BaseService).Assembly).InstancePerRequest().AsSelf();
    builder.RegisterType(typeof(MysqlDbContext)).InstancePerRequest().As<DbContext>();
    但该连接的事务对象如果还没来得及释放(transaction != null),就会悲剧了……
    using (DbContextTransaction transaction = dbContext.Database.CurrentTransaction)
    {
        if (transaction != null)
0 0 演示用,功能实现中……
上一个 下一个
@TA
回复
11月22日 09点52分 nottyjay
111
@TA
回复
11月22日 09点54分 叶飞
咦?
@TA
回复
11月22日 09点54分 叶飞
又一个bug,回复功能失效
@TA
回复
11月22日 10点16分 小薇
太棒了,能和大佬们在线交流了
@TA
回复
11月22日 10点21分 小薇
飞哥能不能将这个在线聊天用WebSocket协议改一下,直接服务端推送消息,不用客户端主动请求
@TA
回复
11月22日 10点25分 小薇
叶飞
又一个bug,回复功能失效...
试试回复功能
@TA
回复
11月22日 10点42分 nottyjay
emmm,这个挂着还会掉线?
@TA
回复
11月22日 18点00分 guanguan
我来了
@TA
回复
11月22日 18点01分 叶飞
懒得改,而且我怕WebSocket我这个破服务器吃不消
@TA
回复
11月22日 18点29分 guanguan
nottyjay
emmm,这个挂着还会掉线?...
我还是觉得要将按钮的鼠标样式改一改,

有新消息,知道了