背景:订单处在等待第三方回调的阶段,需要等待多个第三方全部回调完成后拉起订单往下执行。此时现网出现部分异常订单,所有第三方回调以后订单仍然不继续执行。拉取现网当天的日志后发现父事务超过最大时间限制,自动销毁,导致回调进程结束。
查看日志与走读代码,发现超时的父事务中存在两个独立子事务,这两个子事务中存在两次更新订单表状态的操作,但是阅读日志发现存在三次更新表语句,原来是因为hibernate的autoFlush机制导致父事务中查询订单表时也生成了一次update语句:
恰恰是这次update语句导致父事务将订单表锁住了,而这个锁操作只有在父事务结束时才会释放。但是问题就出在了接下来的独立子事务中又更新了订单表状态,那么在子事务B结束尝试提交时就会陷入死锁,一直等到父事务最大等待时间到了之后整个进程结束,订单卡死。
那么,为什么父事务会生成一条update语句呢?
在hibernate中,默认的提交机制为auto-flush,也就是在事务结束或查询时会检查Session中的实体对象和数据库中的实体对象是否一致,如果不一致则提交update或insert语句。在我们这个场景中,由于父子事务共享的是一个hibernate的session,所以子事务更新订单表实例后也同时更新了共享session中的订单实例(脏数据),所以在父事务中查询时识别到这个是脏数据就生成了update语句。最终导致了后面的问题。
要解决这个问题,有三种方案。第一是将父子事务的session隔离,这样做的坏处是由于没有共享session查询效率会降低。第二种方案则是将两个独立事务都合并到父事务中变成一个事务,但是这个会导致如果中间执行失败,会回滚所有更新语句,导致状态丢失。第三种则是将hibernate的提交机制改为FlushMode.COMMIT
:仅事务结束时或FlushMode.MANUAL
:必须显式调用 flush(),来避免auto-flush生成的update语句。