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

67 篇文章 52 订阅
20 篇文章 0 订阅

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

一、 等待与图

1. 等待与有向图

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

2. 实边与虚边

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

3. 死锁

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

二、 死锁检测定时器

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

以下在postinit.c文件InitPostgres函数

    /*
     * Also set up timeout handlers needed for backend operation.  We need these in every case except bootstrap.
     */
    if (!bootstrap)
    {
        RegisterTimeout(DEADLOCK_TIMEOUT, CheckDeadLockAlert);
        RegisterTimeout(STATEMENT_TIMEOUT, StatementTimeoutHandler);
        RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
        RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
                        IdleInTransactionSessionTimeoutHandler);
        RegisterTimeout(IDLE_SESSION_TIMEOUT, IdleSessionTimeoutHandler);
        RegisterTimeout(CLIENT_CONNECTION_CHECK_TIMEOUT, ClientCheckTimeoutHandler);
    }

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

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

/* check for deadlocks first, as that's probably log-worthy */
            if (got_deadlock_timeout)
            {
                CheckDeadLock();
                got_deadlock_timeout = false;
            }

CheckDeadLock是死锁检测入口函数。

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

三、 主要结构体

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

deadlock.c文件

typedef struct
{
    PGPROC     *waiter;         /* 等待者 */
    PGPROC     *blocker;        /* 阻塞者 */
    LOCK       *lock;           /* 等待的锁 */
    int         pred;           /* 拓扑排序使用的额外变量 */
    int         link;           /*拓扑排序使用的额外变量 */
} EDGE;

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

/* One potential reordering of a lock's wait queue */
typedef struct
{
    LOCK       *lock;           /* 调整的是哪个锁上的等待队列 */
    PGPROC    **procs;          /* 在该锁上新的等待队列 */
    int         nProcs;          /* procs数组长度 */
} 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章

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hehuyi_In

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值