写点什么

技术实践丨 PostgreSQL 插件之 pg_dirtyread "闪回查询"

发布于: 2020 年 10 月 30 日

摘要:Oracle 数据库有时候不小心删除掉数据,想查询这些数据,或者恢复数据,就可以使用带有 as of 子句的 select 语句进行闪回查询。


PG 粉有福了,下面介绍一种类似“闪回查询”插件 pg_dirtyread,可以读取未被 vacuum 的 dead 数据。


github 主页:https://github.com/df7cb/pg_dirtyread


1.2 released:https://www.postgresql.org/message-id/20170923211004.uh27ncpjarkucrhd%40msg.credativ.de


一、我们一起看下官网的 3 个例子:


语法:


SELECT * FROM pg_dirtyread('tablename') AS t(col1 type1, col2 type2, ...);
复制代码


样例 1: 删除找回


  CREATE EXTENSION pg_dirtyread;    -- Create table and disable autovacuum  CREATE TABLE foo (bar bigint, baz text);     ALTER TABLE foo SET (    autovacuum_enabled = false, toast.autovacuum_enabled = false  );  --测试方便,先把自动vacuum关闭掉。   INSERT INTO foo VALUES (1, 'Test'), (2, 'New Test');    DELETE FROM foo WHERE bar = 1;     SELECT * FROM pg_dirtyread('foo') as t(bar bigint, baz text);   bar   │   baz  ─────┼──────────       1     │ Test       2     │ New Test
复制代码


可以看到, 被删除的记录(1, 'Test')已经可以查询到。


样例 2:列被 drop 的情况


 CREATE TABLE ab(a text, b text);    INSERT INTO ab VALUES ('Hello', 'World');     ALTER TABLE ab DROP COLUMN b;    DELETE FROM ab;     SELECT * FROM pg_dirtyread('ab') ab(a text, dropped_2 text);     a   │ dropped_2  ───────┼───────────   Hello │ World
复制代码


可以看到,虽然 b 列被 drop 掉了,但是仍然可以读取到数据。


如何指定列:这里使用 dropped_N 来访问第 N 列,从 1 开始计数。


局限:由于 PG 删除了原始列的元数据信息,因此需要在表列名中指定正确的类型,这样才能进行少量的完整性检查。包括类型长度、类型对齐、类型修饰符,并且采取的是按值传递。


样例 3:系统列


SELECT * FROM pg_dirtyread('foo')           AS t(tableoid oid, ctid tid, xmin xid, xmax xid, cmin cid, cmax cid, dead boolean,           bar bigint, baz text);   tableoid │ ctid  │ xmin │ xmax │ cmin │ cmax │ dead │ bar │        baz  ──────────┼───────┼──────┼──────┼──────┼──────┼──────┼─────┼───────────────────        41823 │ (0,1) │ 1484 │ 1485 │    0 │    0 │ t    │   1 │ Delete  41823 │ (0,2) │ 1484 │    0 │    0 │    0 │ f    │   2 │ Insert        41823 │ (0,3) │ 1484 │ 1486 │    0 │    0 │ t    │   3 │ Update  41823 │ (0,4) │ 1484 │ 1488 │    0 │    0 │ f    │   4 │ Not deleted        41823 │ (0,5) │ 1484 │ 1489 │    1 │    1 │ f    │   5 │ Not updated        41823 │ (0,6) │ 1486 │    0 │    0 │    0 │ f    │   3 │ Updated        41823 │ (0,7) │ 1489 │    0 │    1 │    1 │ t    │   5 │ Not quite updated        41823 │ (0,8) │ 1490 │    0 │    2 │    2 │ t    │   6 │ Not inserted
复制代码


可以看到,xmax 和 ctid 可以被恢复了。 oid 只在 11 以及更早的版本中才能被恢复。


二、支持的版本


10 和 11 已经支持,2.0 以后的版本已经支持 12 和 13,社区还是很活跃。


三、实现分析


核心代码有 2 部分:


1、dirtyread_tupconvert.c 主要实现了 dirtyread_convert_tuples_by_name,通过列名进行元组转换,处理列原信息被清理以及存在表继承的情况,关键部分是数组:attrMap[],下标从 1 开始。


重点分析下 dirtyread_do_convert_tuple


HeapTupledirtyread_do_convert_tuple(HeapTuple tuple, TupleConversionMap *map, TransactionId oldest_xmin){     /*     * Extract all the values of the old tuple, offsetting the arrays so that     * invalues[0] is left NULL and invalues[1] is the first source attribute;     * this exactly matches the numbering convention in attrMap.     */    heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1); //+1是因为是从下标1开始,从旧的元组中把数据的值获取到     /*     * Transpose into proper fields of the new tuple. 这部分是重点,在这里完成转换     */    for (i = 0; i < outnatts; i++)    {        int         j = attrMap;         if (j == DeadFakeAttributeNumber)         //场景1:明确是dead,直接调用内核的函数HeapTupleIsSurelyDead即可,        //定义在tqual.c中,其它场景可以使用HeapTupleSatisfiesVacuum、HeapTupleSatisfiesMVCC等等,这里明确是dead,所以使用HeapTupleIsSurelyDead        {            outvalues = HeapTupleIsSurelyDead(tuple                    , oldest_xmin);            outisnull = false;        }        else if (j < 0) //场景2:系统列,交给函数heap_getsysattr来处理。            outvalues = heap_getsysattr(tuple, j, map->indesc, &outisnull);        else        {   //场景3:最常见的场景,直接获取即可。            outvalues = invalues[j];            outisnull = inisnull[j];        }    }     return heap_form_tuple(map->outdesc, outvalues, outisnull); //重新包装为tuple格式}
复制代码


2、pg_dirtyread.c 面向客户的接口在这里实现。


重点分析下 Datum pg_dirtyread(PG_FUNCTION_ARGS)


第 1 部分


    if (SRF_IS_FIRSTCALL()),这部分比较套路化    {        superuser校验        PG_GETARG_OID获取表的oid        heap_open打开表        get_call_result_type计算结果校验,不支持复合类型        BlessTupleDesc(tupdesc) 拿到表结构        usr_ctx->map = dirtyread_convert_tuples_by_name(usr_ctx->reltupdesc,                        funcctx->tuple_desc, "Error converting tuple descriptors!");  //关键的一步,这里使用dirtyread_convert_tuples_by_name函数,。        heap_beginscan(usr_ctx->rel, SnapshotAny...),开始启动表扫描,这里使用了SnapshotAny       }
复制代码


第 2 部分,不断的获取每一行,然后对每一行进行转换,直到扫描结束。


  if ((tuplein = heap_getnext(usr_ctx->scan, ForwardScanDirection)) != NULL)    {        if (usr_ctx->map != NULL)        {            tuplein = dirtyread_do_convert_tuple(tuplein, usr_ctx->map, usr_ctx->oldest_xmin);            SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuplein));        }        else            SRF_RETURN_NEXT(funcctx, heap_copy_tuple_as_datum(tuplein, usr_ctx->reltupdesc));    }    else    {        heap_endscan(usr_ctx->scan); //结束扫描        heap_close(usr_ctx->rel, AccessShareLock); //关闭表        SRF_RETURN_DONE(funcctx);    }
复制代码


整体上实现并不是很复杂,理解了这些后,就可以在此基础上增加自己的功能了。 而 PG 的魅力就在于此--架构的开放性,可以让开发者迅速地开发自己的“小程序”出来。


点击关注,第一时间了解华为云新鲜技术~


发布于: 2020 年 10 月 30 日阅读数: 41
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
技术实践丨PostgreSQL插件之pg_dirtyread "闪回查询"