开发者
资源
openGauss入门(二):事务与并发控制
openGauss入门(二):事务与并发控制
原创
发表于06/15
310

第一期聊了五个核心差异,这一期直接进到事务和并发控制。这部分是我觉得openGauss做得最有嚼头的地方,也是跟PG差异最集中的一块。

CSN不是装点门面的

你可能见过一些介绍材料说CSN是openGauss的一个feature,但没讲清楚它到底解决了什么问题。PG的快照可见性判断,核心开销在于需要维护一个当前活跃事务ID列表(xip数组)。高并发下,这个列表的长度可能是几百上千,每次可见性判断都得在里面扫一遍。而且创建快照时获取这个列表本身也需要加锁保护,锁竞争不轻。

CSN把这件事换了个角度。事务提交时分配一个全局递增的序号(CSN),快照里只记一个数值。元组可见性?比一下t_csn和快照CSN就行。社区官方博客的压测数据,256并发下这个优化能把可见性判断的开销降到PG原生的五分之一左右。

从代码看,CSN的分配就是一条原子自增指令:pg_atomic_fetch_add_u64(&nextCommitSeqNo, 1)。拿到CSN后通过CSNLOG存起来,数据结构是xid→csn的映射表。这套实现的核心思想是用全局逻辑时钟代替活跃事务列表。

要注意的是CSN并非全场景碾压。如果查询触发了大量非索引扫描,每个元组头里的xmin/xmax都要去查CSNLOG,这就是两次额外查表。而PG拿到xip列表后是在内存里扫。所以CSN在高并发OLTP(活跃事务多、索引查询多)场景下优势明显,全表扫描多的纯OLAP负载不一定赚。

日志系统有两套

事务日志这块值得一提。PG有CLOG记录事务提交状态(COMMITTED/ABORTED等)。openGauss加了CSNLOG,两者配合工作。CLOG负责回答“这个事务提交了没有”,CSNLOG回答“这个事务是在什么顺序提交的”。可见性判断需要把两个答案合在一起。

两层日志都用了SLRU缓冲池来缓存热点页,避免每次都走磁盘IO。16.0版本对CLOG的并发访问做了优化,多个backend可以同时读同一个CLOG页,高并发场景下这块的锁竞争缓解了不少。

事务状态机与子事务

事务模块分了两个状态机。TBlockState是上层状态,记录用户看到的BEGIN/COMMIT/ROLLBACK层级;TransState是内核层状态,记录事务在内核执行路径上的实际推进。这套设计的好处是能自然地支持嵌套事务和SAVEPOINT。子事务有自己的独立状态和回滚域,上层异常时可以精准回滚到某个savepoint位置,不需要整锅端。

锁机制:表锁、行锁和死锁检测

常规锁走两阶段锁协议,事务执行期扩张阶段加锁、提交期收缩阶段释放。表锁有8个冲突级别,从AccessShareLockAccessExclusiveLock,跟PG基本一致。

行锁实现上,openGauss在页面尾部嵌了一个ITL数组。每个ITL条目记录事务id、锁类型和回滚段指针。要锁一行时,先在ITL里占个位置,然后把这一行的元组头指向这个条目。更新操作在Data Page放新版本,旧版本推入Undo链。并发更新同一行时,后来者发现该行的元组头指向了一个仍在运行的事务ITL,排队等待即可。

死锁检测分为两类。常规锁的死锁检测是主动式,工作线程在锁等待超时后发起等待图遍历,发现环就回滚环中代价最小的那个事务。LWLock的死锁检测用一个独立监控线程周期性扫描持有者和等待者的时间戳,超时阈值到了就主动介入解除。实测中LWLock死锁多出现在latch等待和检查点争用,这个自愈机制能极大减少运维人工介入。

隔离级别:两种选择

openGauss只支持read committedrepeatable read,默认是前者。serializable在语法上接受,实际等价于repeatable read

read committed快照在每个语句开始时刷新,同一事务中不同SELECT可能看到不同数据。repeatable read快照只在事务开始取一次,后面的SELECT始终保持一致。

一个容易被忽略的点:repeatable read模式下,如果事务开始时看不到某条记录,后来另一个事务插入了,本事务仍然看不到,但更新操作会触发检测。比如你SELECT查不到id=10的行,另一个事务插入了id=10并提交,你本事务执行UPDATE SET col=xx WHERE id=10会更新成功;如果你做的是DELETE id=10,也会成功。这种“看不到但能改”的行为容易造成业务逻辑偏差,在开发ORM层时需要注意封装。

小结

第二期把事务和并发控制的核心模块拆了一遍。CSN机制、CLOG+CSNLOG配合、两层状态机、表锁行锁、两种隔离级别——这些东西底层串起来的逻辑链条很清楚:用CSN实现O(1)可见性,用ITL做页面级行锁,用主动/被动结合的方案做死锁检测。下一期进存储引擎,拆Astore的内存页格式、Ustore的Undo链、CStore的CU索引。

收藏举报
Level 1
0
帖子
0
粉丝
0
获赞