postgresql 删除触发器_PostgreSQL:我没有带闪,不讲武德
前言
今天有个业务的妹子问我“在吗?”
我说什么事?
给我发个截图,我一看!噢,原来是把数据删除了,想让我把数据找回来。
他说,大哥你能不能帮我。
我说可以!
很快啊,我就打开终端,一个指令就开始了。
我大E了,发现数据闪回不了。
然后十分钟后我告诉她,搞不定了。
然后她就投诉到领导哪里去了,说她丢的数据找不回来了,DBA也搞不定。
我找到这个妹子,说:“你不讲规矩,你不懂这个恢复的难度。”
她忙说对不起,我不懂规矩啊!
我说:“不讲规矩,来,告状,诬陷我20多年经验的dba连点数据都恢复不了。这好吗?这不好。我劝这位女同学,耗子尾汁。好好反思。
PostgreSQL如何开闪
说到闪回查询,PostgreSQL一开始是有这个功能的,它叫Time Travel
(时间旅行),这名字听上去比闪回查询要高大上,瞬间让我想起了好几部描述这类的电影,如《时间机器》、《时间旅行者的妻子》、《信条》。
我们拥有时间旅行,可以穿梭于任意的时空,然后来阻止一场灾难?大多数电影都是这类的桥段,而对于数据库来说,时间旅行也是我们拯救灾难的一种方法。
时间旅行Time Travel
这项技术还要追溯到PostgreSQL 6时代,但是在之后就被打入了冷宫,变成可以使用但是并不推荐。官方文网也说有性能上的影响,推荐使用触发器替代,一直到PostgreSQL 12这个版本,才真正的把这项功能移除了。
那么怎么在PostgreSQL 12上实现呢?有很多种解决方案,其实原生的PG只是一个食材,大家可以根据这个食材,自由发挥做出各种各样美味的料理。你可以灵活选择适合你口味的料理(适配你的系统)。
pg_dirtyread
pg_dirtyread
的工作原理比较简单,就是从Dead Tuples中读取数据。这得益于PostgreSQL的MVCC机制。在PostgreSQL的MVCC机制中,当更新或者删除任何一行记录的时候,将在内部创建新行并将旧行标记为Dead Tuples。而Pg_dirtyread
就可以助我们从Dead Tuples读出数据。但是缺点也很明显,如果Dead Tuples被autovacuum进程清理了,也就没数据了。
接下来我们来测试一下,下载编译pg_dirtyread
插件。
make && make installcreate extension pg_dirtyread;
create table students( stuno int, name varchar(50), age varchar(50), city varchar(50));
insert into students (stuno, name, age, city) values (1, 'abhiram', 22, 'allahabad'); insert into students (stuno, name, age, city) values (2, 'alka', 20, 'ghaziabad'); insert into students (stuno, name, age, city) values (3, 'disha', 21, 'varanasi'); insert into students (stuno, name, age, city) values (4, 'esha', 21, 'delhi'); insert into students (stuno, name, age, city) values (5, 'manmeet', 23, 'jalandhar');
postgres=# select * from students; stuno | name | age | city -------+---------+-----+----------- 1 | abhiram | 22 | allahabad 2 | alka | 20 | ghaziabad 3 | disha | 21 | varanasi 4 | esha | 21 | delhi 5 | manmeet | 23 | jalandhar
对上述表做多次更新。
update students set city='WuHan' where stuno=5;update students set age=21 where stuno=5;update students set city='Shanghai' where stuno=5;
通过pg_dirtyread读取更新前的数据。
postgres=# SELECT * FROM pg_dirtyread('students') students(stuno int, name varchar(50),age varchar(50),city varchar(50)); stuno | name | age | city -------+---------+-----+----------- 1 | abhiram | 22 | allahabad 2 | alka | 20 | ghaziabad 3 | disha | 21 | varanasi 4 | esha | 21 | delhi 5 | manmeet | 23 | jalandhar 5 | manmeet | 23 | WuHan 5 | manmeet | 21 | WuHan 5 | manmeet | 21 | Shanghai
可以看到把历史的数据都找回来了,但是很乱,它读出了所有的历史数据,可能你只是想恢复到其中的某一个时间点。
postgres=# SELECT * FROM pg_dirtyread('students')postgres-# AS students(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean,stuno int, name varchar(50),age varchar(50),city varchar(50)); tableoid | ctid | xmin | xmax | cmin | cmax | dead | stuno | name | age | city ----------+-------+----------+----------+------+------+------+-------+---------+-----+----------- 20023788 | (0,1) | 26678735 | 0 | 0 | 0 | f | 1 | abhiram | 22 | allahabad 20023788 | (0,2) | 26678736 | 0 | 0 | 0 | f | 2 | alka | 20 | ghaziabad 20023788 | (0,3) | 26678737 | 0 | 0 | 0 | f | 3 | disha | 21 | varanasi 20023788 | (0,4) | 26678738 | 0 | 0 | 0 | f | 4 | esha | 21 | delhi 20023788 | (0,5) | 26678739 | 26678741 | 0 | 0 | t | 5 | manmeet | 23 | jalandhar 20023788 | (0,6) | 26678741 | 26678742 | 0 | 0 | t | 5 | manmeet | 23 | WuHan 20023788 | (0,7) | 26678742 | 26678743 | 0 | 0 | f | 5 | manmeet | 21 | WuHan 20023788 | (0,8) | 26678743 | 0 | 0 | 0 | f | 5 | manmeet | 21 | Shanghai
不过知道死元组的事务ID就可以通过pg_xact_commit_timestamp
函数将xmin转换成时间。
使用
pg_xact_commit_timestamp
函数,需要将参数track_commit_timestamp
设置为on,修改该参数需要重启数据库。
postgres=# select (pg_xact_commit_timestamp(xmin)) from postgres-# (SELECT * FROM pg_dirtyread('students') AS students(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean,stuno int, name varchar(50),age varchar(50),city varchar(50))) as b; pg_xact_commit_timestamp ------------------------------- 2020-11-23 15:23:48.611553+08 2020-11-23 15:23:48.624343+08 2020-11-23 15:23:48.6367+08 2020-11-23 15:23:48.65629+08 2020-11-23 15:23:48.676773+08 2020-11-23 15:23:55.716173+08 2020-11-23 15:23:55.729868+08 2020-11-23 15:23:56.02215+08
这样就可以基于时间点恢复到你想要的位置了。
可以说pg_dirtyread
很好的解决了误操作导致的数据修改删除问题。但是它最大的缺点就是受制于autovacuum进程,如果autovacuum进程清理掉了死元组,pg_dirtyread
就没办法工作了。所以当出现误删数据之后,我们第一时间就要先关闭autovacuum,然后通过下面查询误操作的表是否已经发生了vacuum。
select relname,last_vacuum, last_autovacuum, last_analyze, last_autoanalyze from pg_stat_user_tables;
最后说一点,这个方法虽好,但是不支持truncate和drop这类的ddl。
temporal_tables
temporal_tables
也是一个不错的扩展,但是它的原理和pg_dirtyread
完全不同。它的原理是将我们修改或者删除的旧行归档到一个历史表中,这样可以方便进行审计、对比。至于这个插件我觉得他们和IBM DB2的功能很接近,都需要使用一个叫时态表temporal tables
技术。
我们来测试一下,下载编译temporal_tables插件。
make && make installcreate extension temporal_tables;
接下来我们来创建表。
create table students( stuno int, name varchar(50), age varchar(50), city varchar(50));
建完表后,我们要增加一个sys_period列。
ALTER TABLE students ADD COLUMN sys_period tstzrange NOT NULL;
然后我们需要创建一个历史表。
CREATE TABLE students_history (LIKE students);
最后我们要创建一个触发器,把我们的表和历史表关联起来。
CREATE TRIGGER students_hist_trigger BEFORE INSERT OR UPDATE OR DELETE ON students FOR EACH ROWEXECUTE PROCEDURE versioning('sys_period', 'students_history', true);
插入记录。
insert into students (stuno, name, age, city) values (1, 'abhiram', 22, 'allahabad'); insert into students (stuno, name, age, city) values (2, 'alka', 20, 'ghaziabad'); insert into students (stuno, name, age, city) values (3, 'disha', 21, 'varanasi'); insert into students (stuno, name, age, city) values (4, 'esha', 21, 'delhi'); insert into students (stuno, name, age, city) values (5, 'manmeet', 23, 'jalandhar');
此时通过查询,会发现sys_period列上会显示时间。["2020-11-23 17:10:58.702324+08",)。这个格式前面代表着有效期开始时间,后面则代表有效期结束时间,这里,后面是空的,代表了无穷大。
postgres=# select * from students;stuno | name | age | city | sys_period -------+---------+-----+-----------+------------------------------------ 1 | abhiram | 22 | allahabad | ["2020-11-23 17:48:13.841549+08",) 2 | alka | 20 | ghaziabad | ["2020-11-23 17:48:13.862973+08",) 3 | disha | 21 | varanasi | ["2020-11-23 17:48:13.874852+08",) 4 | esha | 21 | delhi | ["2020-11-23 17:48:13.891388+08",) 5 | manmeet | 21 | Shanghai | ["2020-11-23 17:51:22.613059+08",)
我们对表进行更新操作,此时查询students_history就会显示我们的历史数据。
update students set city='WuHan' where stuno=5;update students set age=21 where stuno=5;update students set city='Shanghai' where stuno=5;
postgres=# select * from students_history; stuno | name | age | city | sys_period -------+---------+-----+-----------+------------------------------------------------------------------- 5 | manmeet | 23 | jalandhar | ["2020-11-23 17:48:13.916846+08","2020-11-23 17:48:31.964398+08") 5 | manmeet | 23 | WuHan | ["2020-11-23 17:48:31.964398+08","2020-11-23 17:50:30.014874+08") 5 | manmeet | 21 | WuHan | ["2020-11-23 17:50:30.014874+08","2020-11-23 17:51:22.613059+08")
虽然能显示数据,但是和我们想要的差距还是有点大。因为如上面所示,数据还是很紊乱的。想要查询到指定的时间点还是很困难的。不过根据历史视图,我们知道我们在下面三个时间点更新了数据。分别是17:48:13 17:50:30 17:51:22
我们可以在创建一个视图。
CREATE VIEW students_with_history ASSELECT * FROM studentsUNION ALLSELECT * FROM students_history;
然后执行下面操作,我就想看看在17:49分应该是显示那条数据,这里要显示第一次做update的结果。
postgres=# SELECT * FROM students_with_historypostgres-# WHERE stuno = 5 AND sys_period @> '2020-11-23 17:49:00'::timestamptz;stuno | name | age | city | sys_period -------+---------+-----+-------+------------------------------------------------------------------- 5 | manmeet | 23 | WuHan | ["2020-11-23 17:48:31.964398+08","2020-11-23 17:50:30.014874+08")(1 row)
可以看到结果完全正确。换成17点51分在看看,这里显示了第二次做update的结果。
postgres=# SELECT * FROM students_with_historypostgres-# WHERE stuno = 5 AND sys_period @> '2020-11-23 17:51:00'::timestamptz ; stuno | name | age | city | sys_period -------+---------+-----+-------+------------------------------------------------------------------- 5 | manmeet | 21 | WuHan | ["2020-11-23 17:50:30.014874+08","2020-11-23 17:51:22.613059+08")(1 row)
以上的操作方法就像Oracle中的语法as of timestamp一样。而AS OF SYSTEM TIME语法是SQL 2011的标准。
而wiki上也有一篇文章详细的描述了PostgreSQL实现SQL 2011的方法和建议,实现的原理和temporal_tables
差不多,但是它多出来了truncate的恢复。其实temporal_tables
要实现truncate也很简单,自己做一个truncate的触发器就行了。
SQL2011Temporal
衍生版PG
当前现在市面上有一些衍生版的PG,比如CockroachDB。提供了SQL 2011中的AS OF SYSTEM TIME语法。
有兴趣的同学可以看看蟑螂数据库是怎么实现SQL:2011标准的。
Time-Travel Queries: SELECT witty_subtitle FROM THE FUTURE
总结
通过验证可以看出PostgreSQL打开闪回功能还是比较复杂的,它不像其他数据库内置了这个功能。需要我们自己找第三方插件来实现。而大多数第三方插件都是基于触发器实现的。而触发器的往往会存在一些开销。同时还要在原表上增加一个时间区间的字段。所以还是推荐使用pg_dirtyread
来拯救您误删除的数据啊喂。
postgresql 删除触发器_PostgreSQL:我没有带闪,不讲武德相关推荐
- postgresql 删除触发器_postgresql 触发器
一.创建事件触发器 1.ddl_command_start - 一个DDL开始执行前被触发: 2.ddl_command_end - 一个DLL 执行完成后被触发: 3.sql_drop -- 删除一 ...
- postgresql删除索引_PostgreSQL 13 发布,索引和查找有重大改进
9月24日,PostgreSQL全球开发组宣布PostgreSQL 13正式发布,作为世界上使用最多的开源数据库之一,PostgresSQL 13是目前的最新版本. PostgreSQL 13 在索引 ...
- Postgresql添加/删除触发器示例
-- 定义触发器 CREATE TRIGGER "vss_after_insert_flow_201702" AFTER INSERT ON "public". ...
- 【Postgresql】触发器某个字段更新时执行,行插入或更新执行
[Postgresql]触发器某个字段更新时执行,行插入或更新执行 1. postgresql触发器 2. 触发器的创建及示例 1) 字段更新时,触发 2) 行插入或更新时,触发 3. 触发器的删除 ...
- PostgreSQL数据库触发器实验
PostgreSQL数据库触发器实验 实验目的 (1)掌握触发器的创建.修改和删除操作. (2)掌握触发器的触发执行. (3)掌握触发器与约束的不同. 实验要求 (1)创建触发器. (2)触发器执行触 ...
- 解析postgresql 删除重复数据案例
这篇文章主要介绍了postgresql 删除重复数据案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下 1.建表 /*Navicat Premium ...
- 可以删除的android系统自带程序—详细列表【转】
android系统自带的很多程序平时根本用不到或很少用到,但是它们有的却在你开机之后默默地在后台运行着,既占内存又消耗电量,这时我们就需要把他们一一杀掉!但是很多人不明白这些软件都是些什么,哪些能删哪 ...
- mysql创建删除触发器的时候_mysql触发器简介及如何创建和删除触发器
什么是mysql触发器 需要MySQL 5 对触发器的支持是在MySQL 5中增加的.因此,本章内容适用于MySQL 5或之后的版本. MySQL语句在需要时被执行,存储过程也是如此.但是,如果你想要 ...
- MySql命令行创建和删除触发器
创建和删除mysql触发器 举例如下: 现有表tab1和tab2 要求触发器具有下面功能 当对tab1插入一条记录时则tab2也插入相同的记录 1 2 3 4 5 6 7 8 mysql>del ...
最新文章
- python如何将列表,字典,元组,集合首字母变成大写 以及其他的大小写转换!
- The user specified as a definer ('root'@'%') does not exist
- nginx日志模块及日志格式
- cocos2d-x初探学习笔记(14)--菜单项
- LeetCode 1966. Binary Searchable Numbers in an Unsorted Array
- 原来华为是一家做音乐的公司
- 大数据下单集群如何做到2万+规模?
- Windows注册表相关示例(重装Chrome、思迅条码软件、清除远程痕迹等)
- UTF-8字符集成为Java 18默认字符集?发布周期将至,Java 18现身
- 为什么博客园安卓端无法登陆,真服了
- java 解锁关闭文件占用_程序员:Java文件锁定、解锁和其它NIO操作
- idea 初始界面_IDEA 初始配置教程
- 三目运算符?:结合性
- 常用的C语言学习网站
- 微软影子系统EWF软件用法及参数描述
- 利用matlab实现卷积实验报告,matlab卷积实验报告(共8篇).doc
- WIN7的便签使用快捷键
- 概率图模型: Coursera课程资源分享和简介
- java coherence_coherence配置说明
- 中国宽带无线移动互联网论坛-无线传感器网络
热门文章
- boost::mp11::tuple_for_each相关用法的测试程序
- boost::math::hyperexponential用法的测试程序
- ITK:创建三角形四边形网格
- ITK:获取图像大小
- VTK:图片之ImageGradientMagnitude
- OpenCV边缘之间的距离转换功能的实例(附完整代码)
- C++返回一个数的二进制最右边的一位的实现算法(附完整源码)
- OpenGL blending sort 混合排序(前后)的实例
- C++重载运算符最好声明为友元
- QT自定义图表上不同元素的外观