1.3.2 PGSQL MVCC与锁
在介绍MVCC之前,我们先来看一下PGSQL的最小数据页单位——Tuple。Tuple的结构,除了专业的数据库开发人员,一般人员不用太在意,无非就是有一个Header,存一些元数据、Tuple的内容对应的类型和具体的数据内容,如图1-28所示。
图1-28 PGSQL的Tuple结构
但有一点不一样的地方,就是Tuple的多版本,并不像MySQL一样实现行版本化(Row Versioning)。
大多数关系型数据库,都是想办法构造一个回滚段来实现MVCC,这得益于Oracle的实现方式。即使如有些年头的DB2,也是通过补丁(Patch)的补充,在不改动原始逻辑的场景下,提供一个构造回滚段的功能的,只不过如果在构造回滚段时已经等到锁,则放弃回滚段的构造。这说明MVCC的回滚段设计是非常符合大众需求的,也成为经典的做法。
PGSQL是把数据直接多版本地写在Tuple里,这样冗余地写,直到事务提交,打上标记,由vacuum来做垃圾清理。所以在日志中经常会看到vacuum的汇报。
这个设计会带来一个问题,就是复制冲突(Hot Standby Conflict)。
按照官方文档的记载,主要就是两类冲突:
Master节点删除类DDL锁导致的冲突。
Master节点vacuum清理过期MVCC记录导致的冲突。
从堆栈来看,主要是这两类函数阻塞关系。
堆栈中heap_xlog_clean() 函数用于回放XLOG_HEAP2_CLEAN类型的xlog。这是vacuum(旧版本Tuple清理进程)产生的xlog,物理删除旧版本Tuple。所以要等待Slave节点访问到该旧版本Tuple的事务结束(事务级别结束,非单条SQL语句结束)。
Master节点上普通SQL进程update、delete row,MVCC机制产生新的Tuple。xlog类型是增加新版本Tuple,与Slave节点的查询事务不会冲突,PGSQL的MVCC也保证了查询和修改可以并行。
除了上述复制冲突,PGSQL有非常好的锁跟踪设计,这一点对于DBA和数据库开发人员来说真的非常方便。PGSQL会把超过1s的行锁打印到日志中,同时也会将Blocking Chain(锁的等待队列)打印到日志中。此外,如果这个会话最终拿到锁,也会再打印一条日志记录。
上述例子说明了这个Blocking Chain,Blocking Header是会话ID6146,我们的长SQL语句来自会话ID 6101,被6146阻塞了163s,而实际上,这个执行计划只需要0.01s就可以执行完成,硬生生等了165s才完成。此例中,因为Blocking Chain非常清晰,所以会比较容易定位到业务上的交叉和冲突,方便进一步调优。
PGSQL还有很多组件优势,因篇幅有限,就不在此处展开介绍了,请读者留意RDS官网上更多关于PGSQL组件和插件的最新应用情况。