本文经授权转载自 PostgreSQL 中文社区。
PostgreSQL 并未有闪回和数据误删除保护的功能,但是在一些场景下也可以实现。
此方法仅仅针对 DML 操作有效,DDL 操作(drop/truncate 等会将数据页面删除)无法找回数据。由于 PG 是多版本实现机制,因此数据仍然都在,只是不可见而已,vacuum_defer_cleanup_age 可以防止最近这些事务的元祖被删除,即保留这些事务操作的元组,可以闪回到这些操作的任意时间点
开始准备数据
test=# select pg_current_wal_lsn();
pg_current_wal_lsn
0/2003B58(1 row)
test=# create table lzzhang(id int);
CREATE TABLE
test=# insert into lzzhang values(1);
INSERT 0 1
test=# insert into lzzhang values(2);
INSERT 0 1
test=# insert into lzzhang values(3);
INSERT 0 1
test=# insert into lzzhang values(4);
INSERT 0 1
test=# insert into lzzhang values(5);
INSERT 0 1
test=# insert into lzzhang values(6);
INSERT 0 1
test=# select xmin, xmax, cmin, cmax, * from lzzhang;xmin | xmax | cmin | cmax | id——+——+——+——+—-
588 | 0 | 0 | 0 | 1
589 | 0 | 0 | 0 | 2
590 | 0 | 0 | 0 | 3
591 | 0 | 0 | 0 | 4
592 | 0 | 0 | 0 | 5
593 | 0 | 0 | 0 | 6
(6 rows)
回滚 Insert
分析 redo,根据时间找到自己的错误操作,并找到对应的事务号
./pg_waldump -b -s 0/2003B58 -p dj
rmgr: Transaction len (rec/tot): 34/ 34, tx: 589, lsn: 0/0201A660, prev 0/0201A620, desc: COMMIT 2019-03-26 10:55:05.685536 CST
rmgr: Heap len (rec/tot): 59/ 59, tx: 590, lsn: 0/0201A688, prev 0/0201A660, desc: INSERT off 3
blkref #0: rel 1663/16392/16393 fork main blk 0
rmgr: Transaction len (rec/tot): 34/ 34, tx: 590, lsn: 0/0201A6C8, prev 0/0201A688, desc: COMMIT 2019-03-26 10:55:07.749260 CST
rmgr: Heap len (rec/tot): 59/ 59, tx: 591, lsn: 0/0201A6F0, prev 0/0201A6C8, desc: INSERT off 4
blkref #0: rel 1663/16392/16393 fork main blk 0
rmgr: Transaction len (rec/tot): 34/ 34, tx: 591, lsn: 0/0201A730, prev 0/0201A6F0, desc: COMMIT 2019-03-26 10:55:09.893856 CST
rmgr: Heap len (rec/tot): 59/ 59, tx: 592, lsn: 0/0201A758, prev 0/0201A730, desc: INSERT off 5
blkref #0: rel 1663/16392/16393 fork main blk 0
rmgr: Transaction len (rec/tot): 34/ 34, tx: 592, lsn: 0/0201A798, prev 0/0201A758, desc: COMMIT 2019-03-26 10:55:11.917570 CST
rmgr: Heap len (rec/tot): 59/ 59, tx: 593, lsn: 0/0201A7C0, prev 0/0201A798, desc: INSERT off 6
此示例中,我们回滚 5 和 6 的数据。根据 wal 信息找到事务号为 592。
关闭数据库
[lzzhang@lzzhang-pc bin]$ ./pg_resetwal -D dj -x 592
启动数据库
查询
test=# select xmin, xmax, cmin, cmax, * from lzzhang;
xmin | xmax | cmin | cmax | id
------+------+------+------+----
588 | 0 | 0 | 0 | 1
589 | 0 | 0 | 0 | 2
590 | 0 | 0 | 0 | 3
591 | 0 | 0 | 0 | 4
5/6 的数据已经没有了。增长事务号,看看情况!!!
test=# select * from txid_current();
txid_current
--------------
592
(1 row)
test=# select * from txid_current();txid_current
--------------
593
(1 row)
test=# select * from txid_current();
txid_current
--------------
594
test=# select xmin, xmax, cmin, cmax, * from lzzhang;
xmin | xmax | cmin | cmax | id
------+------+------+------+----
588 | 0 | 0 | 0 | 1
589 | 0 | 0 | 0 | 2
590 | 0 | 0 | 0 | 3
591 | 0 | 0 | 0 | 4
592 | 0 | 0 | 0 | 5
593 | 0 | 0 | 0 | 6
由于 5/6 仍然在数据库中,所以 5/6 又可见了。
回滚 delete
删除数据
test=# select pg_current_wal_lsn();
pg_current_wal_lsn
--------------------
0/3000190
(1 row)
test=# delete from lzzhang where id = 5 or id = 6;
DELETE 2
找到事物号(595)
rmgr: Heap len (rec/tot): 59/ 299, tx: 595, lsn: 0/030001B8, prev 0/03000180, desc: DELETE off 5 KEYS_UPDATED , blkref #0: rel 1663/16392/16393 blk 0 FPW
rmgr: Heap len (rec/tot): 54/ 54, tx: 595, lsn: 0/030002E8, prev 0/030001B8, desc: DELETE off 6 KEYS_UPDATED , blkref #0: rel 1663/16392/16393 blk 0
rmgr: Transaction len (rec/tot): 34/ 34, tx: 595, lsn: 0/03000320, prev 0/030002E8, desc: COMMIT 2019-03-26 11:00:23.410557 CST
回滚数据
关闭数据库
[lzzhang@lzzhang-pc bin]$ ./pg_resetwal -D dj -x 595
启动数据库
查看数据
test=# select xmin, xmax, cmin, cmax, * from lzzhang;
xmin | xmax | cmin | cmax | id
------+------+------+------+----
588 | 0 | 0 | 0 | 1
589 | 0 | 0 | 0 | 2
590 | 0 | 0 | 0 | 3
591 | 0 | 0 | 0 | 4
592 | 595 | 0 | 0 | 5
593 | 595 | 0 | 0 | 6
提升事务号,5/6 又被删除
test=# select * from txid_current();
txid_current
--------------
595
(1 row)
test=# select * from txid_current();
txid_current
--------------
596
(1 row)
test=# select xmin, xmax, cmin, cmax, * from lzzhang;
xmin | xmax | cmin | cmax | id
------+------+------+------+----
588 | 0 | 0 | 0 | 1
589 | 0 | 0 | 0 | 2
590 | 0 | 0 | 0 | 3
591 | 0 | 0 | 0 | 4
(4 rows)
回滚 update
回滚 update 的操作类似,就不在讲述。
回滚 drop table
回滚后表可见,但是数据已经没有了 test=# select * from zz;ERROR: could not open file “base/16392/16396”: 没有那个文件或目录根据你的需要回滚/闪回指定操作,然后使用 txid_current()提升事物号,并不影响数据库整体的可用性。
确定数据仍然存在
安装 pageinspect 插件
create extension pageinspect
检查数据是否存在
查看目前表中的内容
test=# select xmin, xmax, cmin, cmax, * from lzzhang;
xmin | xmax | cmin | cmax | id
——+——+——+——+—–
588 | 0 | 0 | 0 | 1
589 | 0 | 0 | 0 | 2
590 | 0 | 0 | 0 | 3
597 | 0 | 0 | 0 | 100
查看表的 page 内容(无效/不可见的元组都可以查看到)
test=# select * from heap_page_items(get_raw_page(‘lzzhang’, ‘main’, 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
—-+——–+———-+——–+——–+——–+———-+——–+————-+————+——–+——–+——-+————
1 | 8160 | 1 | 28 | 588 | 0 | 0 | (0,1) | 1 | 2304 | 24 | | | \x01000000
2 | 8128 | 1 | 28 | 589 | 0 | 0 | (0,2) | 1 | 2304 | 24 | | | \x02000000
3 | 8096 | 1 | 28 | 590 | 0 | 0 | (0,3) | 1 | 2304 | 24 | | | \x03000000
4 | 8064 | 1 | 28 | 591 | 597 | 0 | (0,7) | 16385 | 1280 | 24 | | | \x04000000
5 | 8032 | 1 | 28 | 592 | 595 | 0 | (0,5) | 8193 | 1280 | 24 | | | \x05000000
6 | 8000 | 1 | 28 | 593 | 595 | 0 | (0,6) | 8193 | 1280 | 24 | | | \x06000000
7 | 7968 | 1 | 28 | 597 | 0 | 0 | (0,7) | 32769 | 10496 | 24 | | | \x64000000
vacuum 后查看 page 数据 vacuum lzzhang;
test=# select * from heap_page_items(get_raw_page(‘lzzhang’, ‘main’, 0));
lp | lp_off | lp_flags | lp_len | t_xmin | t_xmax | t_field3 | t_ctid | t_infomask2 | t_infomask | t_hoff | t_bits | t_oid | t_data
—-+——–+———-+——–+——–+——–+———-+——–+————-+————+——–+——–+——-+————
1 | 8160 | 1 | 28 | 588 | 0 | 0 | (0,1) | 1 | 2304 | 24 | | | \x01000000
2 | 8128 | 1 | 28 | 589 | 0 | 0 | (0,2) | 1 | 2304 | 24 | | | \x02000000
3 | 8096 | 1 | 28 | 590 | 0 | 0 | (0,3) | 1 | 2304 | 24 | | | \x03000000
4 | 7 | 2 | 0 | | | | | | | | | |
5 | 0 | 0 | 0 | | | | | | | | | |
6 | 0 | 0 | 0 | | | | | | | | | |
7 | 8064 | 1 | 28 | 597 | 0 | 0 | (0,7) | 32769 | 10496 | 24 | | | \x64000000
(7 rows)
4/5/6 已经被 vacuum 掉,通过 waldump 可以找到对应的事务号的元祖已经不在,所有无法找回。
作者简介:
张连壮,多年 PostgreSQL 数据库内核研发经验,高可用/数据复制方面经验较为丰富,目前主要从事分布式数据库 Citus 相关工作,CitusDB 中国【站主】专注于 Citus 技术分享的全信息平台。
原文链接:
https://mp.weixin.qq.com/s/FbA8haWVOwRcw4CKNP0Vig
更多内容推荐
「SQL 数据分析系列」12. 事务
写在前面: 大家好,我是强哥,一个热爱分享的技术狂。目前已有 12 年大数据与AI相关项目经验, 10 年推荐系统研究及实践经验。平时喜欢读书、暴走和写作。
2021-07-30
误删库删表后,除了跑路还能怎么办?
删库后不是只有跑路一个解决办法的。
14. RocketMQ 发送消息之事务消息
2023-02-16
华为云 MySQL 新增 MDL 锁视图特性,快速定位元数据锁问题(一)
MDL锁视图主要由7个字段组成
宜信的 105 条数据库军规(二)
宜信的大量业务都依赖于数据库。
20|数据库方案设计:如何设计运营搭建平台的数据库?
数据库的设计范式,就是为了让数据的关系清晰,结构精简和减少冗余。不过,实际项目的数据库设计,与范式之间会有一些矛盾。
2023-01-09
PostgreSQL 如何查找某一事务中的完整 SQL
查找某一事务中的完整 SQL 是一个慢工细活,考察 DBA 的诸多细节知识储备,同时考验 DBA 的耐心,但确实是非常实用的 DBA 必备运维技能。 本文从运维实践的角度浅析了一下,欢迎大家来从底层原理/源码的角度探讨更高效的结论和方法。
2021-08-02
MySQL 8.0 与 MariaDB 10.4,谁更易于填坑补锅?
本文介绍一些MySQL 8.0和MariaDB 10.4关键新特性的对比。
华为云 MySQL 新增“逻辑预读”特性,高效提升分析型业务的执行效率
随着用户对数据访问速度的日益重视,MySQL数据库在最初的设计中,采用了线性预读的方式
19|全栈项目搭建:如何搭建 Vue.js 的前后台全栈项目?
全栈项目的搭建是没有标准答案的,都是根据实际项目情况因地制宜做方案设计的。
2023-01-06
一次大量删除导致 MySQL 慢查的分析
本文来自《2019年有赞技术大礼包》系列。
线上故障如何快速排查?来看这套技巧大全
有哪些常见的线上故障?如何快速定位问题?本文详细总结工作中的经验,从服务器、Java应用、数据库、Redis、网络和业务六个层面分享线上故障排查的思路和技巧。
MySQL 锁系统总结
innoDb支持多种粒度的锁,按照粒度来分,可分为表锁(LOCK_TABLE)和行锁(LOCK_REC)。
10. INSERT:最简实现
2023-09-26
十年难得一遇!从数据误删到全量恢复的惊险记录
本文介绍在线下自搭建环境的一次数据误删除事件。
有了 MDL 锁视图,业务死锁从此一目了然
摘要:MDL锁视图让一线运维人员清晰地查看数据库各session持有和等待的元数据锁信息,从而找出数据库MDL锁等待的根因,准确地进行下一步决策。
2020-08-27
三万倍提升,起飞的 PostgreSQL 主从优化实践
某些业务场景安全性要求很高,核心空间的数据不能随意修改,本文介绍腾讯云数据库PostgreSQL在大量drop业务场景下主从复制产生的性能问题,为大家完整剖析此次内核优化的原理和方案,最终让主从同步性能增强了3W多倍,并解决了社区一直悬而未决的问题。
21|数据库 ORM 对象关系映射(一):数据库连接与必备操作
这节课,我们一起来学习在线视频平台的数据管理基石——数据库。
2023-06-09
3 年部署 3000 套 PG 实例的架构设计与踩坑经验
本次分享主要介绍苏宁引入PostgreSQL的背景和历程,以及我们在实际使用PostgreSQL中积累的一些经验。
12|数据库锁:明明有行锁,怎么突然就加了表锁?
数据库锁
2023-07-12
推荐阅读
索引的基础知识
2022-11-08
SQL CREATE INDEX 语句 - 提高数据库检索效率的关键步骤
2023-12-07
3、事务底层原理 MVCC
2023-09-27
6. 集成测试:起步与 MySQL 的增删改查
2023-09-26
MySQL 中的锁机制
2022-09-15
4、事务底层原理 ReadView 案例
2023-09-27
MySQL DeadLock -- 二级索引导致的死锁
2023-06-18
电子书
大厂实战PPT下载
换一换 叶子航 | IDEA 研究院 基础软件中心 MoonBit 资深架构师
林帆(金戟) | 阿里巴巴 云效研发平台高级技术专家
郭家 | 图灵机器人 COO
评论