PostgreSQL , 脏读 , 事务 , flashback , 闪回 , drop , truncate , dml
闪回的需求往往是救命的需求,因为通常情况下数据库正常运行是不需要闪回的,往往是出现了误操作,被攻击,被注入后,数据库的数据被删除或恶意纂改并且纂改的事务已提交,也就是说纂改已经被持久化了。
这种情况下需要闪回来救命,回到被破坏前的状态。
DML闪回和DDL闪回。
DML闪回指对INSET, UPDATE, DELETE操作的闪回。DDL闪回指DROP, TRUNCATE操作的闪回。
1、物理回退,相当于使用物理备份和归档进行时间点恢复,全库恢复到误操作前的状态。
(可以新建一个库用于恢复,恢复到目标时间点,恢复后,将误操作前的数据导出来,再导入线上数据库。)
2、在当前库回退,在当前库,将误操作影响的数据找出来。
1、物理回退,PG内核已支持时间点恢复,只要有误操作前的全量备份和所有归档即可。
2、当前库回退,使用HOOK,可以实现DROP和TRUNCATE操作的回收站功能。
3、使用延迟垃圾回收、脏读、行头事务号、事务提交日志,可以实现DML操作的闪回。
以前写的一些闪回方案:
<a href="https://github.com/digoal/blog/blob/master/201408/20140828_01.md">《PostgreSQL 闪回 - flash back query emulate by trigger》</a>
<a href="https://github.com/digoal/blog/blob/master/201504/20150429_01.md">《PostgreSQL Oracle 兼容性之 - 事件触发器实现类似Oracle的回收站功能》</a>
<a href="https://github.com/digoal/blog/blob/master/201404/20140403_01.md">《PostgreSQL 回收站功能 - 基于HOOK的recycle bin pgtrashcan》</a>
<a href="https://github.com/digoal/blog/blob/master/201608/20160823_04.md">《PostgreSQL 最佳实践 - 任意时间点恢复源码分析》</a>
<a href="https://github.com/digoal/blog/blob/master/201608/20160823_03.md">《PostgreSQL 最佳实践 - 在线增量备份与任意时间点恢复》</a>
<a href="https://github.com/digoal/blog/blob/master/201512/20151220_06.md">《阿里云ApsaraDB RDS for PostgreSQL 最佳实践 - 6 任意时间点恢复》</a>
<a href="https://github.com/digoal/blog/blob/master/201302/20130205_01.md">《PostgreSQL PITR THREE recovery target MODE: name,xid,time USE CASE - 2》</a>
<a href="https://github.com/digoal/blog/blob/master/201302/20130204_01.md">《PostgreSQL PITR THREE recovery target MODE: name,xid,time USE CASE - 1》</a>
本文将详细介绍“使用延迟垃圾回收、脏读、行头事务号、事务提交日志,实现DML操作的闪回。”的方法。
1、延迟VACUUM,确保误操作的数据还没有被垃圾回收。
2、记录未被freeze,确保无操作的数据,以及后面提交的事务号没有被freeze(抹去)。
3、开启事务提交时间跟踪,确保可以从xid得到事务结束的时间。(开启事务结束时间跟踪后,会开辟一块共享内存区存储这个信息。)。
4、事务提交时间跟踪未抹去。可以加大BUFFER来增加可跟踪的事务数。(重启数据库不影响,有持久化已有的跟踪记录)
1、将xid转换为txid的函数,见本文末尾。(因为记录的行头部,存储的是XID,而不是TXID,而查询事务提交状态,用的是TXID,因此需要转换一下。)
2、脏读插件pg_dirtyread(用于读取脏页)
<a href="https://github.com/ChristophBerg/pg_dirtyread">https://github.com/ChristophBerg/pg_dirtyread</a>
3、根据txid查询事务提交状态
4、根据xid查询事务提交时间
5、使用脏读插件,将需要flashback的数据(连同头信息xmin,xmax)写入临时表,根据以上两种方法生成如下字段:
写入事务提交状态、事务提交时间。(xmin)
删除事务提交状态、事务提交时间。(xmax)
例如
1、根据pg_xlogdump找到精确的误操作xid,以及事务提交对应的时间戳。(或者使用用户提供的时间戳,大概的误操作前的时间。)
参考:
<a href="https://github.com/digoal/blog/blob/master/201512/20151210_01.md">《PostgreSQL 使用pg_xlogdump找到误操作事务号》</a>
2、回退到过去的某个时间点(采用基于临时表的VIEW来展现) (根据事务提交顺序,逆序,逐个事务排除,逐个事务回退。)
例子
3、找出被误操作事务 删除、更新、插入 的记录(OLD ROW(被删除、更新前的), NEW ROW(更新后的、误插入的))。
flashback的前提,前面已经讲了,但是可能带来一些负面影响。
1、由于设置了vacuum_defer_cleanup_age,因此每次都会被触发VACUUM,扫描表,但是又有一些垃圾不能被回收;
2、可能导致表膨胀。
1、提高autovacuum_naptime,从而降低频繁扫描的概率。
2、膨胀可能无法解决。建议修改内核,实现可以仅针对重要的表设置 vacuum_defer_cleanup_age。从而避免全库膨胀。
Name
Return Type
Description
txid_status(bigint)
txid_status
report the status of the given transaction: committed, aborted, in progress, or null if the transaction ID is too old
pg_xact_commit_timestamp(xid)
timestamp with time zone
get commit timestamp of a transaction
<a href="https://www.postgresql.org/docs/10/static/functions-info.html">https://www.postgresql.org/docs/10/static/functions-info.html</a>
开启事务结束时间跟踪后,会开辟一块共享内存,跟踪事务结束时间。
xid是32位的整型,会被FREEZE,循环使用,xmin,xmax都是XID类型。通过xid可以得到事务结束时间。
txid是64位整型,不会被循环使用,通过epoch可以从xid转换为txid。通过txid可以得到事务的提交状态。
我们可以自定义一个函数,将xid转换为txid。
编译
拷贝到数据库软件LIB目录
创建函数
测试
为什么会有EPOCH呢?因为XID是循环使用的,每一个循环都会使得EPOCH自增1。从而使得TXID可以持续增长。
<a href="https://github.com/digoal/blog/blob/master/201109/20110930_03.md">《Use pg_resetxlog simulate tuple disappear within PostgreSQL》</a>
<a href="https://github.com/digoal/blog/blob/master/201109/20110930_01.md">《PostgreSQL xid(int4) to txid(int8)》</a>
误操作后,如果又发生了DDL,例如新增字段,修改字段等。使用本文提供的闪回方法,有没有问题?
测一下就知道了,不会有问题。
本文介绍了原地闪回的一种方法:
1、新增脏读功能、新增表级vacuum_defer_cleanup_age(避免全库膨胀)功能、开启track_commit_timestamp。可以实现dml flashback(闪回)。
2、增加回收站(通过HOOK)功能,可以实现DDL flashback。
全库闪回,可以参考PostgreSQL的PITR的功能。
原地闪回的另一种方法:利用redo的undo内容进行闪回,这种方法可以避免膨胀的问题,回退到什么时间点,与保留的REDO文件数有关。使用redo来闪回也会引入一个问题,UNDO需要记录更多的内容,导致REDO文件内容变多。因此建议也是设置表级redo的UNDO内容。