• postgresql源码学习(十五)—— 行锁③-死锁检测


    本篇主要学习下死锁检测的原理,不研究具体算法和代码,有兴趣请参考原书。

    一、 等待与图

    1. 等待与有向图

           事务T1在等待事务T2,可以用一个有向图表示。如果每个等待是一条“边”,那么死锁检测其实就是一个找“环”的过程。

    2. 实边与虚边

    • 实边(Hard Edge):等待队列中的事务等待持锁事务。假设事务A持有表的共享锁或排它锁,当事务B申请表的排他锁时,就要进入等待。pg称这种等待的边为“实边”。
    • 虚边(Soft Edge):均发生在等待队列中的事务。假设事务A持有表的共享锁,事务B申请表的排他锁被阻塞,事务C又想申请该表共享锁。此时事务C需要等待事务B,于是BC都在等待队列中,pg称这种等待的边为“虚边”。

    3. 死锁

           假设等待图中有环,且环全部由实边构成,那么此时只能中断某个事务来打破这个环,这就是死锁。若环还有虚边,说明有事务尚未真正持有锁,此时还可以通过调整事务顺序来避免死锁。

    二、 死锁检测定时器

    每个会话启动时都会注册一个死锁检测定时器,用于提示进行死锁检测。

    以下在postinit.c文件InitPostgres函数

    1. /*
    2. * Also set up timeout handlers needed for backend operation. We need these in every case except bootstrap.
    3. */
    4. if (!bootstrap)
    5. {
    6. RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLockAlert);
    7. RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
    8. RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
    9. RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
    10. IdleInTransactionSessionTimeoutHandler);
    11. RegisterTimeout(IDLE_SESSION_TIMEOUT, IdleSessionTimeoutHandler);
    12. RegisterTimeout(CLIENT_CONNECTION_CHECK_TIMEOUT, ClientCheckTimeoutHandler);
    13. }

    其中CheckDeadLockAlert作为定时任务,会定时设定一个死锁检测标志got_deadlock_timeout。

    当事务处于等待状态时,ProcSleep函数(proc.c文件)会根据此标志决定是否进行死锁检测。

    1. /* check for deadlocks first, as that's probably log-worthy */
    2. if (got_deadlock_timeout)
    3. {
    4. CheckDeadLock();
    5. got_deadlock_timeout = false;
    6. }

    CheckDeadLock是死锁检测入口函数。

    检测过程是互斥的,也就是说在检测开始时,主锁表就需要被保护起来,避免被修改(因为要检查锁持有和等待队列)。如果事务只通过本地锁表或fast path就持有锁,则它不在死锁检测范围内。

    三、 主要结构体

    等待图由PGPROC(会话或进程)和EDGE(边)两个结构体构成。

    deadlock.c文件

    1. typedef struct
    2. {
    3. PGPROC *waiter; /* 等待者 */
    4. PGPROC *blocker; /* 阻塞者 */
    5. LOCK *lock; /* 等待的锁 */
    6. int pred; /* 拓扑排序使用的额外变量 */
    7. int link; /*拓扑排序使用的额外变量 */
    8. } EDGE;

           如果环中包含虚边,可以尝试通过调整等待队列来消除环。新的等待队列保存在waitOrders数组中,每个数组元素但是一个等待队列,由WAIT_ORDER结构体保存该队列。

    1. /* One potential reordering of a lock's wait queue */
    2. typedef struct
    3. {
    4. LOCK *lock; /* 调整的是哪个锁上的等待队列 */
    5. PGPROC **procs; /* 在该锁上新的等待队列 */
    6. int nProcs; /* procs数组长度 */
    7. } WAIT_ORDER;

           在环的查找过程中,可以被调整的虚边记录在possibleConstraints数组中。每次向前推进都会从possibleConstraints找到一个虚边进行探索,被探索的边保存在curConstraints数组中。

           这两个数组长度要足够大,例如curConstraints数组长度是MaxBackends,被调整的虚边数不能超过该值。

    四、 主要函数及作用

    1. CheckDeadLock函数

    死锁检测入口函数

    2. DeadLockCheckRecurse函数

    不断被递归调用,检测死锁

    • 如果遇到实边构成的环,则停止探测并报出死锁错误
    • 如果遇到虚边构成的环,则尝试在curConstraints数组中推进探测的Configuration

    3. TestConfiguration函数

    • 根据当前Configuration(即curConstraints)产生新的等待队列
    • 使用新等待队列检测是否存在环。
    •        如果遇到实边构成的环,则停止探测并报出死锁错误
    •        如果遇到虚边构成的环,则将当前虚边保存到possibleConstraints数组中

    4. FindLockCycle系列函数

           FindLockCycle函数实现找环操作。具体而言,它不停调用FindLockCycleRecurse函数分别查找实边和虚边构成的环。

           FindLockCycleRecurse函数会再调用FindLockCycleRecurseMember函数,该函数还是两个功能:

    • 如果遇到实边构成的环,则停止探测并报出死锁错误
    • 如果遇到虚边构成的环,则将当前虚边保存到possibleConstraints数组中

           在查找环的过程中,它会优先从waitOrders数组中选择等待队列,如果没有,才会使用锁本身的等待队列。

    参考

    PostgreSQL技术内幕:事务处理深度探索》第2章

  • 相关阅读:
    Spring读书笔记——bean创建(上)
    【阅读笔记】《Effective C++》
    Django之Session
    【k8s部署elasticsearch】k8s环境下安装elasticsearch集群和kibana
    GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图
    【猿创征文】 Vue3 企业级优雅实战 - 组件库框架 - 4 组件库的 CSS 架构
    Python 的 Selenium 库进行元素定位时,XPath的详细用法
    JAVA中的“抽象接口”
    解决Thinkphp框架应用目录下数据库配置信息修改后依然按默认方式连接
    MindSpore,易用性提升的思考与实践
  • 原文地址:https://blog.csdn.net/Hehuyi_In/article/details/126912973