面试题答案
一键面试死锁产生场景
- 事务交叉依赖:假设有两个事务
T1
和T2
。T1
持有资源R1
并请求资源R2
,而T2
持有资源R2
并请求资源R1
,这种交叉依赖会导致死锁。例如,在多表更新场景中,T1
先更新表A
中的某行数据(持有A
表相关资源锁),接着准备更新表B
中的数据(请求B
表相关资源锁),而T2
先更新表B
中的某行数据(持有B
表相关资源锁),然后准备更新表A
中的数据(请求A
表相关资源锁),此时就可能产生死锁。 - 复杂嵌套事务依赖:存在多个事务,形成了一个循环依赖链。比如事务
T1
依赖T2
,T2
依赖T3
,而T3
又依赖T1
。在实际业务中,当处理涉及多个关联业务逻辑的事务时,不同事务对资源的获取顺序不一致,容易出现这种复杂的嵌套依赖导致死锁。
检测机制
- Wait - for - Graph(等待图):PostgreSQL使用等待图来检测死锁。等待图以事务为节点,边表示事务之间的等待关系。当一个事务请求另一个事务持有的资源时,就在等待图中创建一条从请求事务到持有事务的边。数据库定期检查这个等待图,看是否存在环。如果存在环,就意味着出现了死锁。例如,在上述事务
T1
、T2
的交叉依赖场景中,等待图会呈现T1 -> T2
和T2 -> T1
的边,形成一个环,从而检测到死锁。 - 死锁超时检测:除了等待图检测,PostgreSQL还设置了死锁超时机制。如果一个事务等待资源的时间超过了设定的超时时间(通过
deadlock_timeout
参数配置,默认值为1秒),系统会认为可能发生了死锁,并尝试进行处理。这种机制可以避免因等待图检测算法执行开销较大,而导致死锁不能及时被发现的问题。
解决机制
- 选择牺牲事务:一旦检测到死锁,PostgreSQL会选择一个事务作为牺牲者(通常选择执行时间较短、资源持有量较少的事务)。这个事务会被回滚,释放它持有的所有资源,从而打破死锁循环。例如,假设
T1
和T2
发生死锁,系统判断T1
执行时间短,就回滚T1
,T2
就可以获取到所需资源继续执行。 - 重新尝试:被回滚的牺牲事务可以由应用程序根据业务逻辑决定是否重新尝试执行。通常,应用程序会捕获事务回滚的异常,在适当的时间间隔后重新启动该事务,以完成其预期的操作。