PostgreSQL 跟踪DDL时间 - cann’t use pg_class’s trigger trace user_table’s create,modify,delete Time

1 minute read

背景

本文适用于没有事件触发器的版本。

最近经常有朋友会问在PostgreSQL中如何查询表的创建时间, MODIFY时间, 删除时间等.

首先来理一理思路:

1. PostgreSQL中pg_class系统表存储了表, 索引, 视图, 序列, TOAST 等数据对象的基本元信息。

每次创建一个新的对象都会往这个系统表插入一条记录。

当涉及到这个系统表的元信息更新时,记录会随之更新。

那么是否可以在pg_class上创建触发器来跟踪一下pg_class里面每条记录的轨迹呢? 从而就可以知道表是什么时候创建的,什么时候被修改的,什么时候被删除的了。就用我前面讲到的HSTORE来存储。

http://blog.163.com/digoal@126/blog/static/163877040201252575529358/

这样可行吗?

答案是不可行,不是因为不能在pg_class上创建触发器,而是触发器没有办法被触发。

如果你需要测试一下的话, 在pg_class上创建触发器前请修改一个隐含参数 : ( allow_system_table_mods = on ) 然后重启数据库.

注意这个参数比较危险,请慎用。

想要得到更详细的情形, 使用gdb进行跟踪.

2. 既然trigger没有办法被触发, 那么rule行不行呢, 我这里测试了是不行的。

3. 那么在pg_class里面还有什么信息可以挖掘的?

还记得tupleHead么? 里面有几个隐含字段, ctic, cmin, cmax, xmin, xmax.

xmin和xmax分别记录的是记录被插入的事务ID和被更新时的事务ID, 而事务ID和时间是挂钩的, 只是事务ID的信息要和时间匹配上的话, 需要检索pg_xlog里面的信息.

而且事务ID是可能被vacuum掉成为frozenXID的, 那就没有参考价值了. 索引使用xmin, xmax来搜寻表的创建时间也就不可取了.

4. 那么外挂一个程序实时的去查询pg_class的新增数据并记录到一个新表行不行呢?

这个办法理论上来说是可行的, pg_class 的 oid字段上是有索引的, 而且oid是往上增长的, (当然系统中不建议创建with oid的用户表)

查询max(oid)的值与自建的用来存储表信息的表的max(oid)进行比较, 不相等则表示有新增数据进入, 把新增的数据插入到自建表即可.

digoal=# create table table_pg_class (reloid oid, relname text, relnsp oid, relkind text, crt_time timestamp);  
CREATE TABLE  
  
create or replace function rec_pg_class () returns int as $$  
declare  
src_max_oid oid;  
des_max_oid oid;  
begin  
select max(oid) into src_max_oid from pg_class;  
select max(reloid) into des_max_oid from table_pg_class;  
if ( src_max_oid <> des_max_oid or des_max_oid is null ) then  
insert into table_pg_class (reloid,relname,relnsp,relkind,crt_time) select t1.oid,t1.relname,t1.relnamespace,t1.relkind,now() from pg_class t1  where oid not in (select reloid from table_pg_class t2);  
end if;  
return 0;  
end;  
$$ language plpgsql;  
  
digoal=# create index idx_pg_class_1 on table_pg_class (reloid);  
CREATE INDEX  
  
digoal=# create table test (id int);  
CREATE TABLE  
  
digoal=# select * from rec_pg_class();  
 rec_pg_class   
--------------  
            0  
(1 row)  
  
digoal=# select count(*) from table_pg_class ;  
 count   
-------  
   297  
(1 row)  
  
digoal=# select count(*) from pg_class;  
 count   
-------  
   297  
(1 row)  
  
digoal=# select * from table_pg_class where relname='test';  
 reloid | relname | relnsp | relkind |          crt_time            
--------+---------+--------+---------+----------------------------  
  24103 | test    |   2200 | r       | 2012-06-26 23:36:29.653227  
(1 row)  
  
digoal=# select * from rec_pg_class();  
 rec_pg_class   
--------------  
            0  
(1 row)  
  
Time: 0.307 ms  
  
digoal=# explain select max(oid) into src_max_oid from pg_class;  
                                                 QUERY PLAN                                                    
-------------------------------------------------------------------------------------------------------------  
 Result  (cost=0.05..0.06 rows=1 width=0)  
   InitPlan 1 (returns $0)  
     ->  Limit  (cost=0.00..0.05 rows=1 width=4)  
           ->  Index Scan Backward using pg_class_oid_index on pg_class  (cost=0.00..15.15 rows=280 width=4)  
                 Index Cond: (oid IS NOT NULL)  
(5 rows)  
  
Time: 0.529 ms  
digoal=# explain select max(reloid) into des_max_oid from table_pg_class;  
                                                  QUERY PLAN                                                     
---------------------------------------------------------------------------------------------------------------  
 Result  (cost=0.04..0.05 rows=1 width=0)  
   InitPlan 1 (returns $0)  
     ->  Limit  (cost=0.00..0.04 rows=1 width=4)  
           ->  Index Scan Backward using idx_pg_class_1 on table_pg_class  (cost=0.00..10.40 rows=294 width=4)  
                 Index Cond: (reloid IS NOT NULL)  
(5 rows)  
  
Time: 0.389 ms  

rec_pg_class()这个函数的调用可以放在操作系统的crontab里面.

5. 当然传统的方法还有使用csvlog来查询表的创建时间, 这个需要配置postgresql.conf的log_statement=’ddl’, 那么会记录下所有的DDL语句, 当然包含表的创建语句. 以及时间等信息.

6. 查看表对应的文件的时间戳也是一种办法, 只是表在被truncate, cluster, rewrite后都会改变文件的创建时间, 或新建文件. 所以不是太准确.

那么最后,还有什么方法呢?

PostgreSQL支持钩子, 所以用钩子来实现是最靠谱的.

例如

http://pgxn.org/dist/pgaudit/1.0.0/

Flag Counter

digoal’s 大量PostgreSQL文章入口