如果是之前学习别的数据库的人,看PostgreSQL会感觉到有句话非常奇怪:“PostgreSQL的回滚是立即完成的,不会受到事务大小本身的影响”。

奇怪在哪里呢?比方我曾经遇到过一次MySQL的故障,一个开发给生产数据库导入数据,用的是Python脚本,但是,他没有注意一个事情,Python的MySQLdb默认情况下,是设置autocommit为0的,于是这哥们导数据(这里说的导入,不是普通那种load data,而是带有业务操作的SQL语句,所以需要脚本操作)脚本跑了一天之后,整个数据库的状况就变得极为糟糕了:他导入所用的,是一个业务的核心表,一堆业务操作都需要操作这个表,但随着这个导入动作跑了一天,占掉了大量的行锁(几百万行锁)之后,整个业务系统的对外服务都会处于一个无法求到锁的状况了(还掺和着MySQL间隙锁的坑坑洼洼),业务服务停摆,于是,作为DBA来说,最终的决策,只有杀掉这个”大”事务了。一个kill命令过去之后,我们当时俩DBA开始慢慢数—小蚂蚁慢慢爬——碰到—颗大豆芽——碰到两颗大豆芽——

最终在将近三个小时的rollback之后,这个事务完成回滚,业务系统恢复。

所以看到PostgreSQL的这个描述之后,我第一时间的反应是,why?how?what?
于是就有了这一篇文章,我从PG的事务可见性判断讲起,整理一下PG核心文件clog的机理与作用。

另注从pg 10以后,clog改名为xact,主要原因,是很多人习惯性地使用*log删除日志文件,总是会不小心删除掉原先的xlog与clog文件,导致数据库不可用,所以分别改名为wal与xact,后文依然以clog为讨论单词,需要注意。
 
clog简介


第一个问题,什么是clog?或者换个说法,PG到底有哪些日志,它们分别是干啥的?
除了理所当前的各路文本记录(比方数据库的运行报错日志之类),PG的二进制类日志文件主要有两个,一个就是对应传统数据库理论的redo日志,理论上,所有数据的修改操作都会被记录到这个日志,在事务提交的时候确保操作都记录到磁盘中,这样讲即便发生宕机,数据库也能以不丢数据的形态重新复活。

但是,各个数据库在这个点上都有不同的实现,比方MySQL会有一个binlog用于跨存储引擎的主从同步,而在PG中,主从同步已经通过redo日志(PG术语为XLOG)同步的情况下,为了处理没有undo带来的一系列问题,其中可见性判断这个功能,就是交给clog日志文件解决的。

Clog中记录了每一个事务相关的xid(记得之前曾吐槽过这个玩意的大小问题带来的freeze问题)以及xid对应的事务的提交状态。提交状态包括以下一些:执行中,已提交,已中断,已提交的子事务。看到这里,就可以明白,只要事务提交的时候,设置状态为已提交,而事务回滚的时候,设置状态为已中断,就可以达到目的,的确避免了操作数百万行的事务突然要回滚时候的巨大代价。

但我看到这里的时候,就产生一个疑惑,这样的话,我查数据的时候,见到一行的xid之后,需要马上确认其可见性,就需要去查clog,这个查询频率势必极高而且随机性很大,这个问题该怎么解决呢?

#define CLOG_BITS_PER_XACT    2
#define CLOG_XACTS_PER_BYTE 4
#define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
#define CLOG_XACT_BITMASK    ((1 << CLOG_BITS_PER_XACT) - 1)
#define TransactionIdToPage(xid)    ((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
#define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
#define TransactionIdToByte(xid)    (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
#define TransactionIdToBIndex(xid)    ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)

PG代码给了一个非常精彩的回答。

还记得之前vacuum那个里面,我大力吐槽PG对32位xid的执着,但这个32位id果真一无是处吗?看到这里才明白,还留着这么一笔思路。

一个简单的算术,每个事务标记占据2个比特位(无符号0 1 2 3对应前面提到的事务状态),也就是说,每个字节可以保存4个事务,每当PG需要确定当前事务状态的时候,就直接根据当前事务id计算得到对应的clog页位置(除每页clog之后的整数商是页数字,而余数则是在页中的具体位置)。真是把文件当hash表用的典范啊。

在32位xid的情况下,假设xid限制是20亿,每个8K的clog页存储32k事务位的情况下,clog最大也才五百来MB,这部分交给操作系统的文件缓存足以保障访问效率了。

真是一个绝妙的主意不是么?如果不考虑64位xid的情况下,clog大小完全不可控的情况的话。

还是把话题集中在clog,下面我们来探讨的是,当事务提交或者回滚的时候,其内部的运作机理又是如何呢?

以及,前文中可以看到的一个明显问题,pg这种操作的话,写入的行必然是一个”执行中事务状态”的行,这种行难道是每次查的时候,都得去找clog判断吗?如果频繁扫他几百万行,是不是会有问题?
 
clog实现内部


前面提到,clog里面会记录的是xid对应的事务状态。在PG里面,xid是一个珍贵的资源(考虑到每20亿大限的成住空坏),因此并不是每个事务都会被分配到xid。

一般来说,只有一个事务进行了数据修改(比如insert,update,delete)之类的操作,才会被分配给一个xid。

当这个事务最终提交或者回滚的时候,其最终状态就会被记录入clog。

事务提交与回滚时候的clog操作


首先来说提交。

抛开其他各种过程,每次事务提交的时候,主要的调用路径是: CommitTransaction(提交事务时候调用)-> RecordTransactionCommit(记录事务为已提交)-> TransactionIdCommitTree(同步标记事务为提交)/TransactionIdAsyncCommitTree(异步标记事务为提交,调用下一步需要提供lsn)-> TransactionIdSetTreeStatus(设置事务与子事务状态)-> TransactionIdSetPageStatus(设置单数据页内事务状态)-> TransactionIdSetPageStatusInternal(设置实际文件页)-> TransactionIdSetStatusBit(设置比特位)

其中值得拿出来讲的,主要是TransactionIdSetTreeStatus这个方法。

这里涉及到一个概念,子事务。在PG这个地方,子事务的概念主要指:事务从开始到结束,期间可以savepoint,之后rollback到savepoint而不是事务起点,在实际情况中多有应用,因此这里父事务与子事务(比如事务最终提交,但期间有回滚的情况,或者事务期间多次save point)必须尽可能原子性的方式写入,否则事务可见性就会出现问题。

在代码注释里面,对这里的写入做了一个比较直观的例子:
比如一个事务t,有子事务 t1,t2,t3,t4,其中t,t1被映射到clog页p1,t2和t3在p2,t4在页p3。那么写入的时候,顺序如下:

  1. 设置p2 的t2 t3为子提交,之后设置p3的t4位子提交

  2. 设置t1为子提交,之后设置t为已提交,之后设置t1为已提交

  3. 设置 t2 t3 为已提交,设置t4位已提交

对于回滚,实际上也是调用TransactionIdSetTreeStatus方法,只是上层函数是TransactionIdAbortTree,设置的标记是TRANSACTIONSTATUSABORTED,也就是记录事务为中断。语义上来说,对于事务中断,由于事务的原子性要求,中断的事务数据就是不可见的了,没啥问题。
 
数据行事务可见性的判断与clog


众所周知的是,pg新增行都会对原先的行打一个删除标记,然后写在原先行的旁边,理所当然地,每个数据行都会记录一个事务标记(当然还有数据行对应的事务id),来确保可见性,避免看到事务层面已经rollback的事务。

首先,写入的当时,事务没有结束的时候,必然是”执行中”这个状态。当事务之后提交,或者回滚的时候,pg是必然不会回头改这个标记的,否则无论提交还是回滚,都是一个代价巨大的事情。

就前文所言,pg的事务可见性,是通过行的事务id,找到clog里面对应的标记位置,然后判断的,这里非常理所当然的一个事情是,这种判断,每一行做一次就足够了,判断清楚后,修改掉这个事务标记为已提交或者是中断事务,后续读取的时候,就不需要回查clog了。

PG当然就是这么干的。

也就是说,前一个事务所有修改的数据,它没有在提交或者回滚的当时改掉所有的修改标记,而是把烂摊子丢给后来的人。

而这里还藏着一个问题:你既然修改了行的标记,那理所当然地,行所在数据块的校验和就变了,校验和变了,那块是不是就必须得传到wal缓存走流程了?即便没有涉及数据的变更?而且考虑到从库查询的时候,查数据也可以直接走从库的clog流程,这个数据块是不是必须传给从库?

那么,现在就有一个现成的面试问题了:PostgreSQL单纯的select执行,会不会产生WAL日志?

事实上,这里的事务标记带来的校验和的问题,在PG里面的处理是比较特殊的。

PostgreSQL里面,当且仅当设置了walloghints或者初始化时候,initdb启用了checksum的情况下,才会在设置标记为的时候去写WAL日志。

而且这里还不是每次设置标记位都会写。

必须得是,前一次checkpoint之后,数据块第一次被修改就是sethintbit操作的情况下,才会写整个数据块到WAL。
 
clog的一些衍生思考


实际上就清理过期数据,MySQL也是用delete+insert替代update,但在清理以及处理上,并没有搞到vacuum这么大代价,比如MySQL的purge线程的执行,一般很少需要特别关注,而PostgreSQL的vacuum虽然说是并行化,但是在单表内却是串行的,民间贡献的表内并行vacuum的补丁因为各种bug迟迟没有合并(目前来看PG12没戏了),这个事情为什么会这样呢?

因为clog毕竟只是事务可见性的标记,而不是事务的修改关联。在传统的undo类实现中,修改的数据,以及关联的事务等,都在undo按照顺序存储,purge执行的之后,直接从undo就可以找到对应的需要处理的数据块直接处理。

但是对于PG来说,由于仅仅只有事务标记,vacuum必须扫描所有的数据文件的数据块来处理这个问题,虽然pg里面,vacuum和统计信息采集合二为一(统计信息采集是传统数据库最大的全库扫描行为了),但必然需要付出的全库扫描代价却一个都不会少。

因此vacuum对超大表非常慢,极端情况下在vacuum freezen时候导致全库不可用(freezen结束前不允许执行新事务),就是有极大可能的事情了。

为了解决超大表,传统建议是使用分区表,但PostgreSQL的官方实现里面,分区表一直不太稳定,并且支持不足,因此又不得不引入pathman这个外部组件来协调处理,导致运维复杂度的进一步上升,就成了理所当然的事情。

不过目前就PostgreSQL 12来说,已经在逐渐开放存储引擎层面的接口,而社区中实现的undo版本的存储引擎,虽然因为完成度问题没有在本次release中发布,但未来可期,相信vacuum这一类问题,在未来必然会得到更好的处理。

往期精选


从Oracle到PostgreSQL:动态性能视图 vs 标准统计视图

从 Oracle 到 PostgreSQL :从 Uptime 到数据库实例运行时间

作者:刘伟,云和恩墨软件开发部研究院研究员;前微博DBA,主要研究方向为开源数据库,分布式数据库,擅长自动化运维以及数据库内核研究。

编辑:尹文敏

公司简介  | 招聘 | DTCC | 数据技术嘉年华 | 免费课程 | 入驻华为严选商城

zCloud | SQM | Bethune Pro2 | zData一体机 | Mydata一体机 | ZDBM 备份一体机

Oracle技术架构 | 免费课程 | 数据库排行榜 | DBASK问题集萃 | 技术通讯

升级迁移 | 性能优化 | 智能整合 | 安全保障 |  架构设计 | SQL审核 | 分布式架构 | 高可用容灾 | 运维代维

云和恩墨大讲堂 | 一个分享交流的地方

长按,识别二维码,加入万人交流社群

请备注:云和恩墨大讲堂

PostgreSQL的clog—从事务回滚速度谈起相关推荐

  1. java pg数据库事务回滚,基于Postgresql 事务的提交与回滚解析

    用过oracle或mysql的人都知道在sqlplus或mysql中,做一个dml语句,如果发现做错了,还可以rollback;掉,但在PostgreSQL的psql中,如果执行一个dml,没有先运行 ...

  2. oracle按照时间点回退,【Oracle】查看事务回滚的时间

    首先,我是做BI的,在工作中偶尔会出现一些特殊情况,比如突然在某天ETL日增量调度的时候一个简单的插入操作卡住不动几小时都过不去,平时可能30分钟左右. 可能是资源征用导致DataStage资源分配不 ...

  3. oracle计算回滚时间,事务回滚时间估算

    事务回滚时间估算 1. 当Oracle处于open 状态,当Oracle回滚事务的时候,可以从used_urec,used_ublk数值可以初步估计Oracle回滚事务 的速度. SQL> se ...

  4. 实际开发中,有时没有异常发生,但是执行结果不是我们期望的情况,需要手动让事务回滚

    需求:开支单保存 原来的代码: 修改后的代码: Spring控制事务下手动回滚事务的方法: 在实际开发中,有时并没有异常发生,但是由于事务结果未满足具体业务需求,所以我们不得不手动回滚事务! 有如下两 ...

  5. springboot事务回滚源码_Spring Boot中的事务是如何实现的

    1. 概述 一直在用SpringBoot中的@Transactional来做事务管理,但是很少想过SpringBoot是如何实现事务管理的,今天从源码入手,看看@Transactional是如何实现事 ...

  6. php try 并回滚,ThinkPHP异常处理、事务处理(事务回滚)

    本篇文章给大家介绍,ThinkPHP异常处理.事务处理(事务回滚),用购买下订单减库存的案例分析,希望对大家在工作和学习中有所帮助. 1.Mysql建表引擎使用InnoDB,支持事务回滚. 2.代码案 ...

  7. mysql分类和事务回滚

    主要内容: ***数据定义语言DDL重点 ***数据操纵语言DML重点 数据查询语言DQL重点 ---事务控制语言TCL ---数据库控制语言DCL ---主键(primary key) ---数据冗 ...

  8. 事务回滚什么意思 try_分布式事务 TCC-Transaction 源码分析——事务恢复

    1. 概述 本文分享 TCC 恢复.主要涉及如下二个 package 路径下的类: org.mengyun.tcctransaction.recover RecoverConfig,事务恢复配置接口 ...

  9. Redis-10Redis的事务回滚

    文章目录 概述 场景一: 命令格正确,数据类型错误 场景二:命令格式错误 总结 概述 对于 Redis 而言,不单单需要注意其事务处理的过程,其回滚的能力也和数据库不太一样,这也是需要特别注意的一个问 ...

最新文章

  1. R语言ggplot2可视化条形图(bar plot)、配置因子变量的全局填充色方案、这样不同数据集相同因子的填充色具有一致性(Fix colors to factor levels)
  2. Redis学习笔记(八)——持久化
  3. 微信小程序实现无限滚动列表
  4. 网络爬虫--14.【糗事百科实战】
  5. DJango周总结二:模型层,单表,多表操作,连表操作,数据库操作,事务
  6. Windows程序中的Lib和Dll文件
  7. CentOS 6.4安装OpenOffice
  8. 上传文件与下载文件不一致的怪事
  9. 【基础教程】基于matlab疫情防护动图制作【含Matlab源码 028期】
  10. 计算机时间与网络时间无法同步,电脑时间与网络时间不同步解决办法
  11. 数据持久化(Json,二进制,PlayerPrefs)
  12. 损失函数大全Cross Entropy Loss/Weighted Loss/Focal Loss/Dice Soft Loss/Soft IoU Loss
  13. 小学计算机应用能力培训的计划,小学老师信息技术应用能力提升培训个人计划...
  14. EDAS系统上传稿件The font Arial-ItalicMT is not embedbed in the fille.(FAQ 109)解决
  15. Shell脚本常见问题
  16. 后端返给前端的数据格式
  17. 关于Ng-alain的Acl的使用
  18. PS人物换装--白色换纯色
  19. 爬虫3 requests基础2 代理 证书 重定向 响应时间
  20. PC端播放RTMP流(手机端无法播放)

热门文章

  1. 英雄联盟欧洲赛区_Linux命令简介,欧盟的开源数学工具箱以及更多新闻
  2. (11)vue.js 指令(3)
  3. 前端:JS/17/前篇总结(JS程序的基本语法,变量),数据类型-变量的类型(数值型,字符型,布尔型,未定义型,空型),数据类型转换,typeof()判断数据类型,从字符串提取整数或浮点数的函数
  4. SVG基础知识 Adobe Illustrator绘制SVG
  5. 中止请求和超时 跨域的HTTP请求 认证方式 JSONP
  6. CAN笔记(20) 过程数据对象
  7. 深度学习笔记(9) 优化算法(一)
  8. presto 使用 部署_部署PrestoDB on Cassandra
  9. php xml getattribute,PHP XMLReader getAttribute()用法及代码示例
  10. python os.walk遍历目录_Python 用 os.walk 遍历目录