openGauss的锁机制源码分析

开黑吗?我半藏贼6 2021-12-13 16:54:29
加精

锁机制

数据库对公共资源的并发控制是通过锁来实现的,在openGauss中,根据锁的用途不同,通常可以分为3种:自旋锁(spinlock)、轻量级锁(LWLock,light weight lock)和常规锁(或基于这3种锁的进一步封装),使用锁管理器lmgr提供常规锁的调用。使用锁的一般操作流程可以简述为3步:加锁、临界区操作、放锁。

文件目录

头文件目录: src/include/storage/lock/
文件目录: src/gausskernel/storage/lmgr/

s_lock.cpp                    # 自旋锁的硬件相关实现
spin.cpp                    # 自旋锁的硬件独立实现

lwlock_be.cpp                # 轻量级锁和pgstat之间的桥梁
lwlock.cpp                    # 轻量级锁管理器
lwlocknames.txt                # 轻量级锁名及编号 115个
generate-lwlocknames.pl        # 从lwlocknames.txt生成lwlocknames.h和lwlocknames.cpp

lock.cpp                    # 常规锁
lmgr.cpp                    # 锁管理器
deadlock.cpp                # 死锁检测

predicate.cpp                # postgres谓词锁
proc.cpp                    # 管理每个进程共享内存数据结构的例程

自旋锁(SpinLock)

自旋锁在openGauss中的使用场合为加锁非常短的场合,如修改或读取标志字段,使用CPU的TAS指令实现。由编码来保证不会出现死锁,没有死锁检测。

x86_64的TAS定义如下:

// src/include/storage/lock/s_lock.h
#ifdef __x86_64__ /* AMD Opteron, Intel EM64T */
#define HAS_TEST_AND_SET

typedef unsigned char slock_t;

#define TAS(lock) tas(lock)

static __inline__ int tas(volatile slock_t* lock)
{
    register slock_t _res = 1;

    /*
     * On Opteron, using a non-locking test before the locking instruction
     * is a huge loss.  On EM64T, it appears to be a wash or small loss,
     * so we needn't bother to try to distinguish the sub-architectures.
     */
    __asm__ __volatile__("    lock            \n"
                         "    xchgb    %0,%1    \n"
                         : "+q"(_res), "+m"(*lock)
                         :
                         : "memory", "cc");
    return (int)_res;
}
......
//  TAS_SPIN 为在等待锁是调用的TAS指令,在有些架构如IA64中不同于TAS
#define TAS_SPIN(lock) TAS(lock)

自旋锁的主要接口函数有:

// src/include/storage/spin.h
#define SpinLockInit(lock) S_INIT_LOCK(lock)

#define SpinLockAcquire(lock) S_LOCK(lock)

#define SpinLockRelease(lock) S_UNLOCK(lock)

#define SpinLockFree(lock) S_LOCK_FREE(lock)

请求锁:

可以看到使用while和TAS来阻塞

// src/include/storage/lock/s_lock.h
#define TAS(lock) tas_sema(lock)
#define S_LOCK(lock)                            \
    do {                                        \
        if (TAS(lock))                          \
            s_lock((lock), __FILE__, __LINE__); \
    } while (0)
// src/gausskernel/storage/lmgr/s_lock.cpp
int s_lock(volatile slock_t* lock, const char* file, int line)
{
    SpinDelayStatus delayStatus = init_spin_delay((void*)lock);
    // 等待锁时会阻塞在这里
    while (TAS_SPIN(lock)) {
        perform_spin_delay(&delayStatus);//延时
    }

    finish_spin_delay(&delayStatus);

    return delayStatus.delays;
}

释放锁:

// src/gausskernel/storage/lmgr/s_lock.cpp
void s_unlock(volatile slock_t* lock)
{
#ifdef TAS_ACTIVE_WORD
    /* HP's PA-RISC */
    *TAS_ACTIVE_WORD(lock) = -1;
#else
    *lock = 0;
#endif
}

无锁原子操作

openGauss中还封装了一些32、64、128位的原子操作,用于实现简单变量的原子更新。

这里以32、64位的加法和128位的交换作例子:

// src/include/utils/atomic.h
static inline int32 gs_atomic_add_32(volatile int32* ptr, int32 inc)
{
    return __sync_fetch_and_add(ptr, inc) + inc;
}
static inline int64 gs_atomic_add_64(int64* ptr, int64 inc)
{
    return __sync_fetch_and_add(ptr, inc) + inc;
}
static inline bool gs_compare_and_swap_32(int32* dest, int32 oldval, int32 newval)
{
    if (oldval == newval)
        return true;
    volatile bool res = __sync_bool_compare_and_swap(dest, oldval, newval);
    return res;
}
// src/include/utils/atomic_lse.h
static inline uint32 __lse_atomic_fetch_add_u32(volatile uint32 *ptr, uint32 val)
{
    register uint32 w0 asm ("w0") = val;                                        \
    register uint32 *x1 asm ("x1") = (uint32 *)(unsigned long)ptr;              \
                                                                                \
    asm volatile(".arch_extension lse\n"                                        \
        "       ldaddal  %w[val], %w[val], %[v]\n"                              \
        : [val] "+r" (w0), [v] "+Q" (*ptr)                                      \
        : "r" (x1)                                                              \
        : "x16", "x17", "x30", "memory");                                       \
    return w0;                                                                  \
}

轻量级锁(LWLock)

轻量级锁在openGauss中主要用于内部临界区操作较久的场合,存在共享锁和排他锁两种类型。也应该由编码保证不会出现死锁,但openGauss也提供了死锁检测机制。

一些常用的轻量级锁定义在lwlocknames.txt中,使用generate-lwlocknames.pl生成lwlocknames.h和lwlocknames.cpp

lwlocknames.h 中有一个宏定义 NUM_INDIVIDUAL_LWLOCKS 定义锁的数量

和对应每一个锁名的宏定义

#define $lockname (&t_thrd.shemem_ptr_cxt.mainLWLockArray[$lockidx].lock)

lwlocknames.cpp 中有字符串常量数组 MainLWLockNames

使用GetMainLWLockByIndex(i)这一接口来调用这些常用的锁

// src/include/storage/lock/lwlock.h
#define GetMainLWLockByIndex(i) \
    (&t_thrd.shemem_ptr_cxt.mainLWLockArray[i].lock)

轻量级锁的数据结构:

//src/include/storage/lock/lwlock.h

typedef enum LWLockMode {
    LW_EXCLUSIVE,        // 排他锁
    LW_SHARED,            // 共享锁
    LW_WAIT_UNTIL_FREE /* A special mode used in PGPROC->lwlockMode,
                        * when waiting for lock to become free. Not
                        * to be used as LWLockAcquire argument */
} LWLockMode;

typedef struct LWLock {
    uint16      tranche;            /* 锁标识 ID */
    pg_atomic_uint32 state; /* 状态位*/
    dlist_head waiters;     /* 等待线程的列表*/
#ifdef LOCK_DEBUG
    pg_atomic_uint32 nwaiters; /* 等待线程的个数 */
    struct PGPROC* owner;      /* 最后独占的线程 */
#endif
#ifdef ENABLE_THREAD_CHECK
    pg_atomic_uint32 rwlock;
    pg_atomic_uint32 listlock;
#endif
} LWLock;

请求锁:

// src/gausskernel/storage/lmgr/lwlock.cpp
bool LWLockAcquire(LWLock *lock, LWLockMode mode, bool need_update_lockid)
……
for (;;) {
        bool mustwait = false;
        mustwait = LWLockAttemptLock(lock, mode); /* 第一次尝试 */
        if (!mustwait) {
            LOG_LWDEBUG("LWLockAcquire", lock, "immediately acquired lock");
            break; /* got the lock */
        }
        instr_stmt_report_lock(LWLOCK_WAIT_START, mode, NULL, lock->tranche);
        pgstat_report_waitevent(PG_WAIT_LWLOCK | lock->tranche);

        LWLockQueueSelf(lock, mode); /* 加入队列等待解锁 */
        mustwait = LWLockAttemptLock(lock, mode);/* ok, grabbed the lock the second time round, need to undo queueing */
……
}

释放锁:

// src/gausskernel/storage/lmgr/lwlock.cpp
void LWLockRelease(LWLock *lock)
{
……
    /* We're still waiting for backends to get scheduled, don't wake them up again. */
    check_waiters =
        ((oldstate & (LW_FLAG_HAS_WAITERS | LW_FLAG_RELEASE_OK)) == (LW_FLAG_HAS_WAITERS | LW_FLAG_RELEASE_OK))
        && ((oldstate & LW_LOCK_MASK) == 0);
    /* As waking up waiters requires the spinlock to be acquired, only do so
     * if necessary. */
    if (check_waiters) {
        /* XXX: remove before commit? */
        LOG_LWDEBUG("LWLockRelease", lock, "releasing waiters");
        // 唤醒一个线程
        LWLockWakeup(lock);
    }
……
}

死锁检测:

openGauss使用一个独立的监控线程来完成轻量级锁的死锁探测、诊断和解除。轻量级锁通常不需要进行死锁检测,只有等待时间过长时才进行检测,因为死锁检测是一个重CPU操作,这样可以提高性能。

// src/gausskernel/process/postmaster/lwlockmonitor.cpp

NON_EXEC_STATIC void FaultMonitorMain()
{
……
for (;;) {
……
if (u_sess->attr.attr_common.fault_mon_timeout > 0) {
     if (NULL != prev_snapshot) {
        ……
         /* phase 1: light-weight detect using fast changcount */

        // 从统计信息结构体中读取线程及锁id相关的时间戳,并记录到指针队列中。
           curr_snapshot = pgstat_read_light_detect(); 
        // 跟几秒检测之前的时间对比,如果找到可能发生死锁的线程及锁id则返回true,否则返回false。
           continue_next = lwm_compare_light_detect(prev_snapshot, curr_snapshot);
        if (continue_next) {
         /* phase 2 if needed: heavy-weight diagnosis for lwlock deadlock */
               ......
        }
         if (continue_next) {
                    /* phase 3 if needed: auto healing for lwlock deadlock */
                    lw_deadlock_auto_healing(&deadlock);
           }

……
}

void lw_deadlock_auto_healing(lwm_deadlock* deadlock)
{
    /* choose one thread to be victim */
    int info_idx = 0;
    int backend_victim = choose_one_victim(deadlock, &info_idx);
    if (backend_victim >= 0) {
        if (backend_victim >= MAX_BACKEND_SLOT) {
            ereport(PANIC, (errmsg("process suicides because the victim of lwlock deadlock is an auxiliary thread")));
            return;
        }
        /* wake up this victim */
        lw_deadlock_info* info = deadlock->info + info_idx;
    // 处理方法为找一个 线程wakeup
        wakeup_victim(info->lock, info->waiter.thread_id);
    } else {
        /* LOG, maybe deadlock disappear */
        ereport(LOG, (errmsg("victim not found, maybe lwlock deadlock disappear")));
    }
}

常规锁(Lock)

常规锁主要用于对业务访问的数据库对象加锁,支持多种锁模式,使用tag哈希来找到锁。有死锁检测。

常规锁有8个锁级别,1级锁一般用于SELECT查询操作;3级锁一般用于基本的INSERT、UPDATE、DELETE操作;4级锁用于VACUUM、analyze等操作;8级锁一般用于各类DDL语句。

/* NoLock is not a lock mode, but a flag value meaning "don't get a lock" */
#define NoLock 0
#define AccessShareLock 1  /* SELECT */
#define RowShareLock 2     /* SELECT FOR UPDATE/FOR SHARE */
#define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */
#define ShareUpdateExclusiveLock                         \
    4               /* VACUUM (non-FULL),ANALYZE, CREATE \
                     * INDEX CONCURRENTLY */
#define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */
#define ShareRowExclusiveLock                \
    6 /* like EXCLUSIVE MODE, but allows ROW \
       * SHARE */
#define ExclusiveLock                  \
    7 /* blocks ROW SHARE/SELECT...FOR \
       * UPDATE */
#define AccessExclusiveLock              \
    8 /* ALTER TABLE, DROP TABLE, VACUUM \
       * FULL, and unqualified LOCK TABLE */

常规锁的数据结构:

// src/include/storage/lock/lock.h
typedef struct LOCK {
    /* hash key */
    LOCKTAG tag; /* 锁对象的唯一标识 */
    /* data */
    LOCKMASK grantMask;           /* 已经获取锁对象的位掩码 */
    LOCKMASK waitMask;            /* 等待锁对象的位掩码 */
    SHM_QUEUE procLocks;          /* 与锁关联的PROCLOCK对象链表 */
    PROC_QUEUE waitProcs;         /* 等待锁的PGPROC对象链表 */
    int requested[MAX_LOCKMODES]; /* counts of requested locks */
    int nRequested;               /* total of requested[] array */
    int granted[MAX_LOCKMODES];   /* counts of granted locks */
    int nGranted;                 /* total of granted[] array */
} LOCK;

// PROCLOCK结构,主要是将同一锁对象等待和持有者的线程信息串联起来的结构体。
typedef struct PROCLOCK {
    /* tag */
    PROCLOCKTAG tag; /* proclock对象的唯一标识 */
    /* data */
    PGPROC  *groupLeader; /* group leader, or NULL if no lock group */  
    LOCKMASK holdMask;    /* 已获取锁类型的位掩码 */
    LOCKMASK releaseMask; /* 预释放锁类型的位掩码 */
    SHM_QUEUE lockLink;   /* 指向锁对象链表的指针 */
    SHM_QUEUE procLink;   /* 指向PGPROC链表的指针 */
} PROCLOCK;

这里以请求释放一个元组的常规锁为例子。

请求锁:

// src/gausskernel/storage/lmgr/lmgr.cpp
void LockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode, bool allow_con_update)
{
    LOCKTAG tag;
    SET_LOCKTAG_TUPLE(tag,
                      relation->rd_lockInfo.lockRelId.dbId,
                      relation->rd_lockInfo.lockRelId.relId,
                      relation->rd_lockInfo.lockRelId.bktId,
                      ItemPointerGetBlockNumber(tid),
                      ItemPointerGetOffsetNumber(tid));
    // 请求一个常规锁
    (void)LockAcquire(&tag, lockmode, false, false, allow_con_update);
}
// src/gausskernel/storage/lmgr/lock.cpp
// LockAcquire 函数中调用了 LockAcquireExtended ,LockAcquireExtended 中调用了 LockAcquireExtendedXC
static LockAcquireResult LockAcquireExtendedXC(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock, bool dontWait, bool reportMemoryError, bool only_increment,bool allow_con_update)
{
……
    localtag.lock = *locktag;
    localtag.mode = lockmode;
    // 以tag作哈希,寻找需要的lock
    locallock = (LOCALLOCK *)hash_search(t_thrd.storage_cxt.LockMethodLocalHash, (void *)&localtag, HASH_ENTER, &found);
    if (!found) { // 如果找不到锁
    //初始化
    }
    else {
    //添加自己为锁的拥有者之一
        ...
    }
    if (locallock->nLocks > 0) { // 如果自己已经持有该锁
        ...
    // 加锁,报告,return
    }
    // 多个if语句 根据lockmode等各种情况做分支
……
}

释放锁:

// src/gausskernel/storage/lmgr/lmgr.cpp
void UnlockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode)
{
    LOCKTAG tag;
    SET_LOCKTAG_TUPLE(tag,
                      relation->rd_lockInfo.lockRelId.dbId,
                      relation->rd_lockInfo.lockRelId.relId,
                      relation->rd_lockInfo.lockRelId.bktId,
                      ItemPointerGetBlockNumber(tid),
                      ItemPointerGetOffsetNumber(tid));
    (void)LockRelease(&tag, lockmode, false);
}
// src/gausskernel/storage/lmgr/lock.cpp
bool LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
{
……
        /*
     * Do the releasing.  CleanUpLock will waken any now-wakable waiters.
     */
    wakeupNeeded = UnGrantLock(lock, lockmode, proclock, lockMethodTable);
    // 在CleanUpLock中唤醒线程
    CleanUpLock(lock, proclock, lockMethodTable, locallock->hashcode, wakeupNeeded);
    LWLockRelease(partitionLock);
    instr_stmt_report_lock(LOCK_RELEASE, lockmode, locktag);
    RemoveLocalLock(locallock);
……
}
static void CleanUpLock(LOCK *lock, PROCLOCK *proclock, LockMethod lockMethodTable, uint32 hashcode, bool wakeupNeeded)
{
……
    if (lock->nRequested == 0) {
         ……//The caller just released the last lock, so garbage-collect the lockobject.
    } else if (wakeupNeeded) {
        /* There are waiters on this lock, so wake them up. */
        // 唤醒一个线程
        ProcLockWakeup(lockMethodTable, lock, proclock);
    }
……
}

死锁检测:

常规锁在获取时若没有冲突直接上锁,弱有冲突则设置一个定时器,过一段时间重新唤醒作死锁检测。

// src/gausskernel/storage/lmgr/proc.cpp
// 由信号量,函数 void handle_sig_alarm(SIGNAL_ARGS) 调用 内判断是否超时
static void CheckDeadLock(void)
{
    int i;
    // 获取对整个共享锁数据结构的排他锁。
    for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
        (void)LWLockAcquire(GetMainLWLockByIndex(FirstLockMgrLock + i), LW_EXCLUSIVE);
    // 二次检测是否可以继续运行
    if (t_thrd.proc->links.prev == NULL || t_thrd.proc->links.next == NULL)
        goto check_done;
#ifdef LOCK_DEBUG
    if (u_sess->attr.attr_storage.Debug_deadlocks)
        DumpAllLocks();
#endif
    // 执行死锁检测, 返回死锁的类型
    t_thrd.storage_cxt.deadlock_state = DeadLockCheck(t_thrd.proc);
     if (t_thrd.storage_cxt.deadlock_state == DS_HARD_DEADLOCK) { // 是一个hard死锁
        Assert(t_thrd.proc->waitLock != NULL); 
        // 从等待队列中移除再睡眠
        RemoveFromWaitQueue(t_thrd.proc, LockTagHashCode(&(t_thrd.proc->waitLock->tag)));
        PGSemaphoreUnlock(&t_thrd.proc->sem);
    } else if (u_sess->attr.attr_storage.log_lock_waits ||
               t_thrd.storage_cxt.deadlock_state == DS_BLOCKED_BY_AUTOVACUUM ||
               t_thrd.storage_cxt.deadlock_state == DS_BLOCKED_BY_REDISTRIBUTION) {
        PGSemaphoreUnlock(&t_thrd.proc->sem); // 发送睡眠信号量
    } else if (u_sess->attr.attr_storage.LockWaitTimeout > 0) {
        PGSemaphoreUnlock(&t_thrd.proc->sem); // 发送睡眠信号量
    }
}
//  src/gausskernel/storage/lmgr/deadlock.cpp
DeadLockState DeadLockCheck(PGPROC *proc)
{
……
    /* 搜索死锁和是否存在解决方案, 不存在解决方案返回 True, 无死锁返回 Fasle, 如果True是一个Hard死锁*/
    if (DeadLockCheckRecurse(proc)) {
        /*
         * Call FindLockCycle one more time, to record the correct
         * deadlockDetails[] for the basic state with no rearrangements.
         */
        int nSoftEdges;
        TRACE_POSTGRESQL_DEADLOCK_FOUND();
        t_thrd.storage_cxt.nWaitOrders = 0;
        if (!FindLockCycle(proc, t_thrd.storage_cxt.possibleConstraints, &nSoftEdges)) {
            elog(FATAL, "deadlock seems to have disappeared");
        }
        return DS_HARD_DEADLOCK; /* cannot find a non-deadlocked state */
    }
    // 之后判断具体是哪种死锁
……
}

参考资料

openGauss源码

https://gitee.com/opengauss/openGauss-server

openGauss数据库源码解析系列文章—— 事务机制源码解析(二)
https://blog.csdn.net/GaussDB/article/details/119532011

openGauss数据库源码解析系列文章—— 事务机制源码解析(一)
https://blog.csdn.net/GaussDB/article/details/119388841

...全文
1157 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

67,833

社区成员

发帖
与我相关
我的任务
社区描述
汇集数据库的爱好者和关注者,大家共同学习、探索、分享数据库前沿知识和技术,像松鼠一样剥开科学的坚果;交流Gauss及其他数据库的使用心得和经验,互助解决问题,共建数据库技术交流圈。
数据库数据仓库 企业社区 北京·海淀区
社区管理员
  • Gauss松鼠会
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

欢迎大家同时关注Gauss松鼠会专家酷哥。

https://www.zhihu.com/people/ku-ge-78-98

试试用AI创作助手写篇文章吧