参考内容
PostgreSQL 源码解读(116)- MVCC#1(获取快照#1)
PostgreSQL 源码解读(117)- MVCC#2(获取快照#2)
有以下几种不同类型的快照:
- 常规的MVCC快照
- 在恢复期间的MVCC快照(处于Hot-Standby模式)
- 在逻辑解码过程中使用的历史MVCC快照
- 作为参数传递给HeapTupleSatisfiesDirty()函数的快照
- 作为参数传递给HeapTupleSatisfiesNonVacuumable()函数的快照
- 用于在没有成员访问情况下SatisfiesAny、Toast和Self的快照
快照的内容包括:xmin,xmax,事务id的链表
GetTransactionSnapshot
PortalRun
-> FillPortalStore 执行一次获取一个元组
-> PortalRunMulti
-> GetTransactionSnapshot
参考内容
PostgreSQL 源码解读(118)- MVCC#3(Tuple可见性判断)
可见性判断:
-
元组的状态有几个时间点:元组开始被创建,元组被创建完成,元组开始被修改(实际是删了这个去创建一个新的),元组被修改完成。
-
当涉及事务间判断或本事务并行进程的判断时就要用快照去和这几个时间点比较:本事务的比较用cmin、cmax字段,事务间比较用xmin、xmax字段。未提交的事务如果超出快照范围(更新的事务),则要查看log(活跃的事务日志在缓存里有个指针链表,指针里就存了状态,不需要访问磁盘)去比较。
-
对于事务块:由于xmin,xmax都只记录块的id,需要拿块中具体的单个事务的id去比较
参考内容
PostgreSQL 源码解读(119)- MVCC#4(启动事务)
参考内容
PostgreSQL 源码解读(120)- MVCC#5(获取事务号-主逻辑)
PostgreSQL 源码解读(121)- MVCC#6(获取事务号-实现函数)
子事务的号是放在一个池子里的,每次分配要创建一个日志
xid分配会有的问题:
- 超过数量限制可能要 vacuum 操作
- clog与commitTs与SUBTRANs可能需要新页
- 放在进程的子事务中,更新当前事务id的指针
xid数量限制:
-
VacLimit —> 200,000,561 —> 大于等于该值,触发自动vacuum
-
WarnLimit —> 2,136,484,208 —> 大于等于该值,系统报警
-
StopLimit —> 2,146,484,208 —> 大于等于该值,系统不允许执行事务,使用单用户模式处理
参考内容
PostgreSQL 源码解读(122)- MVCC#7(提交事务-整体流程)
PostgreSQL 源码解读(123)- MVCC#8(提交事务-实际提交过程)
提交事务:commitTransaction
主要做清理、写日志、更新资源状态等工作
写提交日志
RecordTransactionCommit:
- 要停止中断和检查点
- 同步提交时还要把日志刷盘
- 对事务标记提交时间
参考内容
PostgreSQL 源码解读(124)- 后台进程#4(autovacuum进程#1)](http://blog.itpub.net/6906/viewspace-2564154/)
PostgreSQL 源码解读(125)- MVCC#9(vacuum-主流程)
PostgreSQL 源码解读(126)- MVCC#10(vacuum过程)
PostgreSQL 源码解读(127)- MVCC#11(vacuum过程-vacuum_rel函数)
PostgreSQL 源码解读(128)- MVCC#12(vacuum过程-heap_vacuum_rel函数)
知识点
MVCC优势:使用MVCC,读操作不会阻塞写,写操作也不会阻塞读,提高了并发访问下的性能
几种 VACUUM 命令区别:
- Vacuum :不要求获得排它锁,只把dead tuple标为可用,产生碎片
- Vacuum Full:需要排他锁,通过copy方式把非dead数据copy到新磁盘文件
- Vacuum Analyze:更新统计信息,使得优化器能够选择更好的方案执行sql
- Vacuumm Freeze:将freeze的元组xmin 设为3(可用的有效最小事务ID为3,freeze的元组比任何事务都老),清除比oldest xmin老的clog
- vacuum freeze table:将表xmax设为2,其行对所有事务可见
balance_ worker 执行 vacuum 一次的时间是 1min,被平均分配到每个db上
当update、delete元组数超过 表元组数 * 0.2 + 50 就会发生 自动vacuum
清理进程 autoVacLauncherMain,默认有三个worker,每个worker执行平均的工作量,是在db级别并行的。每个worker创建时会计算在每个db上分配的时间(总共1min,分散在不同的db上),放在list上,launch_worker函数会为每个数据库分配1 min / 数据库数量的间隔时间,循环找一个超过间隔时间的数据库,向postgreMain请求分配一个worker 。
当一张表上update,delte记录次数达到 0.2 * 表记录数 + 50 的时候,触发autovacuum
-
每个 worker 清理的过程会调用 ExecVacuum 函数,这个函数不可以重入因为全局变量在清理后就变了。每次都要新开一个 worker
-
中断信号处理中,worker完成任务会发送SIGUSR2,触发autovac_balance_cost(),把总cost(200)平均分配给每个worker。
清理函数 vacuum 主要流程:
- 配置vacuum处理的相关参数、执行相关检查
- 构造vacuum处理上下文
- 在上下文中构造vacuum需处理的relation链表
- 循环处理relation链表
- 收尾工作
-
注意清理函数 vacuum_rel 会开启一个命令,所以对于sql命令来的清理要先commitCommand再执行vacuum_rel。
-
对于事务块中来的vacuum不commit命令,并且要调用 analyze_rel 而不是 vacuum_rel。最后结束时要start一个命令与postgre中的commit命令匹配。(整个过程相当于把树形的子命令拍平成列表了)
对每个表的清理工作:
每个表单独启动一个清理事务命令,主要是锁表,切换用户和实际清理
大概流程:
- 启动事务,快照入栈
- 打开relation,请求合适的锁
- 执行相应的检查
- 切换user
- 执行实际的工作 cluster_rel / heap_vacuum_rel
- 收尾
- 执行主表附带的 toast 表清理
非集群的清理大概过程:heap_vacuum_rel
主要是计算统计信息和是否访问冻结表的判断,通过索引进行清理
- 计算最老的xmin和冻结截止点 vacuum_set_xid_limits
- 判断是否执行全表(不跳过pages)扫描,标记变量为aggressive
- 初始化统计信息结构体vacrelstats
- 打开索引,执行函数lazy_scan_heap进行vacuuming,关闭索引
- 更新pg_class中的统计信息
参考内容
PostgreSQL 源码解读(129)- MVCC#13(vacuum过程-vacuum_set_xid_limits函数)
知识点
**冻结事务id的目的:**事务id的范围是 2^32,当超过这个范围时,新事务的id会比老事务id老,为了版本控制不乱,那些老事务会被冻结(未冻结的事务id总比冻结的事务id年轻)。
pg中冻结的id是活动的最小事务id(xmin) - 50 million以前分配的id,每出现150 million个新id就会触发一次冻结(即 lazy vacuum 的 full scan 过程,会把 200 million 以前的事务冻结)。xmax小于冻结期限的元组将被认为是太老的版本,是dead的,会被删除。
参考内容
PostgreSQL 源码解读(130)- MVCC#14(vacuum过程-lazy_scan_heap函数)
- vacuum进程读每块时都要判断是否需要休息,休息时间是 20 ms,判断标准是cost是否达到 200,其中cost计算如下:
block 命中 buffer | block 在磁盘中 | block 是 clean 块,需要刷盘 | |
---|---|---|---|
cost | 1 | 10 | 20 |
- 元组删除时,索引不是马上删除,而是标记dead,当dead元组数离阈值只有不到一页的数量时,才清除索引
lazy_scan_heap的工作:
- 删除dead元组,整理碎片,更新FSM
- 把update过时元组标记为recently dead
- 修改all_visible标记
- 执行冻结元组
- 统计信息
参考内容
PostgreSQL 源码解读(131)- MVCC#15(vacuum过程-lazy_vacuum_heap函数)
每释放一个元组会整理一次元组数据,移除中间的空隙(碎片)
参考内容
PostgreSQL 源码解读(132)- MVCC#16(vacuum过程-lazy_vacuum_index函数#1)
PostgreSQL 源码解读(133)- MVCC#17(vacuum过程-lazy_vacuum_index函数#2)
vacuum过程中可能存在分裂与合并的处理:对于合并比较容易,不影响正常流程;对于分裂要在分裂时标记页特别空间(opaque)的 btpo_cycleid 字段(为非0)。由于每次分裂新块都在老块后面,所以判断如果blocknum < origin block number,则说明分裂发生了。这些块也要被检查,处理完后清空 btpo_cycleid 标记。
参考内容
PostgreSQL 源码解读(134)- MVCC#18(vacuum过程-HeapTupleSatisfiesVacuum函数)
活着的事务状态可分五个时间段:
- 正在插入
- 插入成功
- 准备修改或删除(如 for update 的锁定)
- 正在修改或删除
- 修改或删除成功
关于状态有几个规律要满足:
- 对于自己的事务,只能是正在状态
- 其它事务可能没有活着(正常结束,或意外退出),在判断xmin、xmax状态时要看该事务是否在进行中,如果已经不在运行要根据clog(commit log)判断是否提交成功,以避免事务回滚或退出了。
- 多事务的xid,包含了很多子xid
元组的死亡状态分为 Recently Dead 和 Dead,Recently Dead 的元组有回滚可能,先不去删除。
HeapTupleSatisfiesVacuum 的实现
参考内容
PostgreSQL 源码解读(135)- MVCC#19(vacuum过程-heap_execute_freeze_tuple函数)
元组的 freeze bit 的设置是在 heap_prepare_freeze_tuple 函数完成的,在 heap_execute_freeze_tuple 只是设置 xmax 与冻结事务号