openGauss源码解析(79)

openGauss源码解析:事务机制源码解析(10)

5.3.4 死锁检测机制

死锁主要是由于进程B要访问进程A所在的资源,而进程A又由于种种原因不释放掉其锁占用的资源,从而数据库就会一直处于阻塞状态。如图5-17中,T1使用资源R1去请求R2,而T2事务持有R2的资源去申请R1。

image

图5-17 死锁状态

形成死锁的必要条件是:资源的请求与保持。每一个进程都可以在使用一个资源的同时去申请访问另一个资源。打破死锁的常见处理方式:中断其中的一个事务的执行,打破环状的等待。openGauss提供了LWLock的死锁检测及常规锁的死锁检测机制,下面简单介绍一下相关原理及代码。

1. LWLock死锁检测自愈

openGauss使用一个独立的监控线程来完成轻量级锁的死锁探测、诊断和解除。工作线程在请求轻量级锁成功之前会写入一个时间戳数值,成功获得锁之后置该时间戳为0。监测线程可以通过快速对比时间戳数值来发现长时间获得不到锁资源的线程,这一过程是快速轻量的。只有发现长时间的锁等待,死锁检测的诊断才会触发。这么做的目的是防止频繁诊断影响业务正常执行能。一旦确定了死锁环的存在,监控线程首先会将死锁信息记录到日志中去,然后采取恢复措施使得死锁自愈,即选择死锁环中的一个线程报错退出。机制如图5-18所示。

图5-18 LWLock死锁检测自愈

因为检测死锁是否真正发生是一个重CPU操作,为了不影响数据库性能和运行稳定性,轻量级死锁检测使用了一种轻量式的探测,用来快速判断是否可能发生了死锁。采用看门狗(watchdog)的方法,利用时间戳来探测。工作线程在锁请求进入时会在全局内存上写入开始等待的时间戳;在锁请求成功后,将该时间戳清0。对于一个发生死锁的线程,它的锁请求是wait状态,时间戳也不会清0,且与当前运行时间戳数值的差值越来越大。由GUC参数fault_mon_timeout控制检测间隔时间,默认为5s。LWLock死锁检测每隔fault_mon_timeout去进行检测,如果当前发现有同样线程、同样锁id,且时间戳等待时间超过检测间隔时间值,则触发真正死锁检测。时间统计及轻量级检测函数如下。

(1) pgstat_read_light_detect:从统计信息结构体中读取线程及锁id相关的时间戳,并记录到指针队列中。
(2) lwm_compare_light_detect:跟几秒检测之前的时间对比,如果找到可能发生死锁的线程及锁id则返回true,否则返回false。

真正的LWLock死锁检测是一个有向无环图的判定过程,它的实现跟常规锁类似,这部分会在下面一小节中详细介绍。死锁检测需要两部分的信息:锁,包括请求和分配的信息;线程,包括等待和持有的信息,这些信息记录到相应的全局变量中,死锁监控线程可以访问并进行判断。相关的函数如下。

(1) lwm_heavy_diagnosis:检测是否有死锁。
(2) lwm_deadlock_report:报告死锁详细信息,方便定位诊断。
(3) lw_deadlock_auto_healing:治愈死锁,选择环中一个线程退出。

用于死锁检测的锁和线程相关数据结构如下。

(1) lock_entry_id记录线程信息,有thread_id及sessionid是为了适配线程池框架,可以准确的从统计信息中找到相应的信息。对应的代码如下:

typedef struct {

ThreadId thread_id;

uint64 st_sessionid;

} lock_entry_id;

(2) lwm_light_detect记录可能出现死锁的线程,最后用一个链表的形式将当前所有信息串联起来。对应的代码如下:

typedef struct {

/* 线程ID */

lock_entry_id entry_id;

/* 轻量级锁检测引用计数 */

int lw_count;

} lwm_light_detect;

(3) lwm_lwlocks 记录线程相关的锁信息,持有锁数量,以及等锁信息。对应的代码如下:

typedef struct {

lock_entry_id be_tid; /* 线程ID */

int be_idx; /* 后台线程的位置 */

LWLockAddr want_lwlock; /* 预获取锁的信息 */

int lwlocks_num; /* 线程持有的轻量级锁个数 */

lwlock_id_mode* held_lwlocks; /* 线程持有的轻量级锁数组 */

} lwm_lwlocks;

posted @ 2024-04-30 09:54  openGauss-bot  阅读(14)  评论(0编辑  收藏  举报