一、 强锁与弱锁

根据兼容性表,彼此相容的3个锁(1-3级,AccessShareLock、RowShareLock、RowExclusiveLock)是弱锁,4级锁ShareUpdateExclusiveLock比较特殊,既不是强锁也不是弱锁,5-8级的4个则是强锁。

弱锁只保存在当前会话,从而避免频繁访问共享内存的主锁表,提高数据库的性能。虽然判断是否有强锁也需要访问共享内存中的FastPathStrongRelationLocks,但这种访问粒度比较小。

二、 Fast Path检查

按照常规锁获取步骤,如果本地锁表中没有找到这个锁对象和锁模式,就需要进到第二步Fast Path检查。

1. 检查条件

Fast Path检查分两部分:弱锁部分与强锁部分,pg分别使用两个宏来判断(lock.c)。

// 弱锁需要满足4个条件:
// 1. 默认lock method;2. 对表加锁;3. 当前db;4. 锁模式小于ShareUpdateExclusiveLock(4级锁)
#define EligibleForRelationFastPath(locktag, mode) \((locktag)->locktag_lockmethodid == DEFAULT_LOCKMETHOD && \(locktag)->locktag_type == LOCKTAG_RELATION && \(locktag)->locktag_field1 == MyDatabaseId && \MyDatabaseId != InvalidOid && \(mode) < ShareUpdateExclusiveLock)
// 强锁也需要满足4个条件:
// 只有第4个为锁模式大于ShareUpdateExclusiveLock(4级锁),其余3个与弱锁相同
#define ConflictsWithRelationFastPath(locktag, mode) \((locktag)->locktag_lockmethodid == DEFAULT_LOCKMETHOD && \(locktag)->locktag_type == LOCKTAG_RELATION && \(locktag)->locktag_field1 != InvalidOid && \(mode) > ShareUpdateExclusiveLock)

注意判断的时候都没有mode = ShareUpdateExclusiveLock,因为它既不是强锁也不是弱锁。

2. 弱锁申请时

一个弱锁能够直接获得而不经过主锁表,需要判断两个条件:

  • 共享内存中是否设置了强锁标记

如果共享内存中没有设置强锁标记,说明当前没有其他事务获得过这个锁对象的强锁模式。因此事务在这个对象间不会有冲突,此时可以考虑获得这个弱锁。

  • 当前进程是否还有空间可以保存弱锁

如果弱锁保存在本事务的fast path中,实际上它是保存在PGPROC中的(以下代码在proc.h)。

#define      FP_LOCK_SLOTS_PER_BACKEND 16/* Lock manager data, recording fast-path locks taken by this backend. */LWLock     fpInfoLock;     /* protects per-backend fast-path state */uint64        fpLockBits;     /* lock modes held for each fast-path slot */Oid            fpRelId[FP_LOCK_SLOTS_PER_BACKEND]; /* slots for rel oids */

PGPROC->fpRelId是一个长度为16的数组,里面保存的是表的oid,因此最多保存16个弱锁。

PGPROC->fpLockBits保存的是一个位图,目前64位中(uint64)只用了48位。每3位组成一个槽(因为共有3种弱锁),每个槽都与PGPROC->fpRelId数组中的表对应(所以3*16=48)。

/* Macros for manipulating proc->fpLockBits,3种弱锁所以每3位组成一个槽*/
#define FAST_PATH_BITS_PER_SLOT     3
#define FAST_PATH_LOCKNUMBER_OFFSET     1
//形式为111的掩码
#define FAST_PATH_MASK                  ((1 << FAST_PATH_BITS_PER_SLOT) - 1)
//得到第n个槽中已有的锁模式
#define FAST_PATH_GET_BITS(proc, n) \(((proc)->fpLockBits >> (FAST_PATH_BITS_PER_SLOT * n)) & FAST_PATH_MASK)

所以当一个事务要对弱锁使用fast path时,就尝试在PGPROC->fpLockBits中记录当前的锁模式,事务就能够获得锁了。

当然,如果记录的弱锁数超过了16,那也需要去主锁表中检查。

3. 强锁申请时

如果要申请的强锁模式,需要做的事如下:

  • 设置强锁标记,即 FastPathStrongRelationLocks->count(也是一个数组)的引用计数加1
  • 把其他事务保存的对应弱锁转移到主锁表中,方便死锁检测(因为有强锁之后就会有等待)

源码实现部分较长,为了结构清晰一些,我们单独放下面。

三、 强锁申请时的fast path操作

1. 设置强锁标记

  • FastPathStrongRelationLocks的定义
typedef struct
{slock_t        mutex;uint32        count[FAST_PATH_STRONG_LOCK_HASH_PARTITIONS]; //数组最大长度1024
} FastPathStrongRelationLockData;static volatile FastPathStrongRelationLockData *FastPathStrongRelationLocks;
  • 强锁获取函数

核心是FastPathStrongRelationLocks->count[fasthashcode]++; 以及 locallock->holdsStrongLockCount = true;

/** BeginStrongLockAcquire - inhibit use of fastpath for a given LOCALLOCK,* and arrange for error cleanup if it fails*/
static void
BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode)
{Assert(StrongLockInProgress == NULL);Assert(locallock->holdsStrongLockCount == false);/** Adding to a memory location is not atomic, so we take a spinlock to* ensure we don't collide with someone else trying to bump the count at* the same time.** XXX: It might be worth considering using an atomic fetch-and-add* instruction here, on architectures where that is supported.*/SpinLockAcquire(&FastPathStrongRelationLocks->mutex);FastPathStrongRelationLocks->count[fasthashcode]++;  //注意这里,强锁引用计数加1locallock->holdsStrongLockCount = true;StrongLockInProgress = locallock;SpinLockRelease(&FastPathStrongRelationLocks->mutex);
}

2. 弱锁转移到主锁表中

把其他事务保存的对应弱锁转移到主锁表中。整体就是3个大循环:检查每个进程中的、每个表的、3种弱锁模式是否设置过。

/** FastPathTransferRelationLocks*       Transfer locks matching the given lock tag from per-backend fast-path*      arrays to the shared hash table.** Returns true if successful, false if ran out of shared memory.*/
static bool
FastPathTransferRelationLocks(LockMethod lockMethodTable, const LOCKTAG *locktag,uint32 hashcode)
{LWLock    *partitionLock = LockHashPartitionLock(hashcode);Oid            relid = locktag->locktag_field2;uint32      i;

/** Every PGPROC that can potentially hold a fast-path lock is present in* ProcGlobal->allProcs.  Prepared transactions are not, but any* outstanding fast-path locks held by prepared transactions are* transferred to the main lock table.
* 每个PGPROC都有可能持有fast-path锁(在ProcGlobal->allProcs数组中),因此需要循环遍历所有PGPROC。所有prepared阶段之后的事务需要将fast-path locks转移到主锁表。*/for (i = 0; i < ProcGlobal->allProcCount; i++){PGPROC       *proc = &ProcGlobal->allProcs[i];uint32      f;LWLockAcquire(&proc->fpInfoLock, LW_EXCLUSIVE);/** If the target backend isn't referencing the same database as the* lock, then we needn't examine the individual relation IDs at all;* none of them can be relevant.
如果进程引用的库跟要求加锁的对象都不是同一个,那么不需再检查其中的表,直接跳过本次循环,换下一个检查
*/ if (proc->databaseId != locktag->locktag_field1){LWLockRelease(&proc->fpInfoLock);continue;}/* * 如果是同一个库,那么遍历16个针对表的槽*/for (f = 0; f < FP_LOCK_SLOTS_PER_BACKEND; f++){uint32     lockmode;/* 如果不是同一个表,或者当前没有弱锁,则跳出循环,换下一个表 */if (relid != proc->fpRelId[f] || FAST_PATH_GET_BITS(proc, f) == 0)continue;/* 否则,继续按lockmode循环检查3类弱锁。FAST_PATH_LOCKNUMBER_OFFSET=1,FAST_PATH_BITS_PER_SLOT=3,所以其实就是for (lockmode = 1; lockmode < 1+3; ++lockmode)*/LWLockAcquire(partitionLock, LW_EXCLUSIVE);for (lockmode = FAST_PATH_LOCKNUMBER_OFFSET;lockmode < FAST_PATH_LOCKNUMBER_OFFSET + FAST_PATH_BITS_PER_SLOT;++lockmode){PROCLOCK   *proclock;/* 检查3个弱锁的标记是否被设置过,如果没有,代表fast path中没出现过这个锁模式,换下一个 */if (!FAST_PATH_CHECK_LOCKMODE(proc, f, lockmode))continue;/* 否则,把这个锁模式挪到共享内存。SetupLockInTable函数的主要作用就是在主锁表和进程锁表中查找对应的锁,如果该锁不存在,则在主锁表和进程锁表中申请内存保存锁并初始化锁信息。我们会在下篇中重点学习这个函数。*/proclock = SetupLockInTable(lockMethodTable, proc, locktag,hashcode, lockmode);if (!proclock) // Returns false if ran out of shared memory{LWLockRelease(partitionLock);LWLockRelease(&proc->fpInfoLock);return false;}// 增加本地锁的计数 GrantLock(proclock->tag.myLock, proclock, lockmode);// 在PGPROC中去掉对应锁模式,业务PGPROC已转移到主锁表FAST_PATH_CLEAR_LOCKMODE(proc, f, lockmode);}LWLockRelease(partitionLock);/* No need to examine remaining slots. */break;}LWLockRelease(&proc->fpInfoLock);}return true;
}

参考

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

postgresql源码学习(九)—— 常规锁②-强弱锁与Fast Path相关推荐

  1. postgresql源码学习(十三)—— 行锁①-行锁模式与xmax

    一. 四种行锁 1. 简介与兼容性分析 pg采用元组级常规锁+xmax结合的方式实现行锁.我们曾经提到过常规锁是有很多类TAG的(typedef enum LockTagType),其中 LOCKTA ...

  2. postgresql源码学习(27)—— 事务日志⑦-日志落盘上层函数 XLogFlush

    一. 预备知识 1. XLOG什么时候需要落盘 事务commit之前 log buffer被覆盖之前 后台进程定期落盘 2. 两个核心结构体 这两个结构体定义代码在xlog.c,它们在日志落盘过程中非 ...

  3. postgresql源码学习(51)—— 提交日志CLOG 原理 用途 管理函数

    一. CLOG是什么 CLOG(commit log)记录事务的最终状态. 物理上,是$PGDATA/pg_xact目录下的一些文件 逻辑上,是一个数组,下标为事务id,值为事务最终状态 1. 事务最 ...

  4. postgresql源码学习(49)—— MVCC⑤-cmin与cmax 同事务内的可见性判断

    一. 难以理解的场景 postgresql源码学习(十九)-- MVCC④-可见性判断 HeapTupleSatisfiesMVCC函数_Hehuyi_In的博客-CSDN博客 在前篇的可见性判断中有 ...

  5. PostgreSQL源码学习(1)--PG13代码结构

    PostgreSQL源码学习(1)–PG13代码结构 PostgreSQL代码结构 Bootstrap:用于支持Bootstrap运行模式,该模式主要用来创建初始的模板数据库. Main:主程序模块, ...

  6. PostgreSQL源码学习(一)编译安装与GDB入门

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 PostgreSQL源码学习(一)编译安装与GDB入门 前言 一.安装PostgreSQL 1.获取源码 2.配置 3.编译 3.安装 ...

  7. postgresql源码学习(52)—— vacuum①-准备工作与主要流程

    关于vacuum的基础知识,参考,本篇从源码层继续学习 https://blog.csdn.net/Hehuyi_In/article/details/102992065 https://blog.c ...

  8. postgresql源码学习(53)—— vacuum②-lazy vacuum之heap_vacuum_rel函数

    一. table_relation_vacuum函数 1. 函数定义 前篇最后(https://blog.csdn.net/Hehuyi_In/article/details/128749517),我 ...

  9. postgresql源码学习(57)—— pg中的四种动态库加载方法

    一. 基础知识 1. 什么是库 库其实就是一些通用代码,可以在程序中重复使用,比如一些数学函数,可以不需要自己编写,直接调用相关函数即可实现,避免重复造轮子. 在linux中,支持两种类型的库: 1. ...

最新文章

  1. JSP第二次作业_8小题
  2. Java 内存模型 与 高效并发
  3. 2019面试跳槽回顾和一点感想
  4. Linux下samba服务的错误处理
  5. 中兴a2018拆机图片_中兴天机拆机步骤详解【图文】
  6. Moss/Sharepoint 为特定用户组设置特定视图以及可见视图,自定义列表新建页,修改页和显示页(无代码法)...
  7. VMware网络连接方式(Host-only、NAT、Bridged)介绍及NAT环境下静态IP配置
  8. Python OpenCV显示图像并保存图像
  9. Newtonsoft.Json序列化和反序列之javascriptConvert.SerializeObject,DeserializeObject,JsonWriter,JsonReader...
  10. 种子文件多服务器,别再问我什么是 BT 种子了!一次性全告诉你
  11. 金蝶K3 webservice接口
  12. 微信小程序开发工具编辑样式文件后模拟器不显示
  13. NPS - 数字化营销 - 净推荐值
  14. 怎么用计算机算lnx,ln计算(log计算器在线)
  15. 用360查看本地dns
  16. UIUC简介 -- 写给今年得到UIUC Offer并在犹豫中的同学
  17. Shell 脚本 — 多行注释、开启子/不开启子进程执行、转义带颜色输出、读取键盘输入、输入输出重定向、单双引号、命令替换、读取变量、系统变量、正则过滤、算术运算、一行多条命令、字符串比较
  18. 网站降权根服务器有关系吗,导致网站降权或被k的原因有哪些?
  19. python进行数据处理_用python进行数据分析(二:数据处理)
  20. 爱立信软件测试英语笔试题,爱立信测试平台(dallas)开发岗位offer咨询

热门文章

  1. flash和ram的区别
  2. Exceptional C++ Syle 学习笔记
  3. 关于技术趋势改变我们需要知道的五件事情
  4. Android 10.0 软硬键盘同时使用的兼容(软键盘与内置物理键盘共存)
  5. 一块听听:Mixin 主网上线语音直播文字稿
  6. 基于汉宁窗FIR滤波器实现语音信号的去噪处理
  7. 分享35套免费的国外 PSD 名片模板
  8. mybatis源码学习1--学习源码的目的
  9. 大学物理学下——静电场04电场线与电通量
  10. ibm x3650 安装centos6