乐趣区

关于数据库:工具-pgrecovery-设计原理与源码解读

作者:张连壮 PostgreSQL 研发工程师

从事多年 PostgreSQL 数据库内核开发,对 citus 有十分深刻的钻研。

上一期 咱们介绍了 PostgreSQL 数据找回工具:pg_reconvery

本文将带大家理解 pg_recovery 工具的实现原理、设计思路,并带来源码解读。

| 数据找回的实现原理

一个数据库系统失常的数据读取形式,是从做 select * from pg_recovery 的查问开始(即执行事务),执行查问操作过程将同时生成事务的快照,通过 GetActiveSnapshot() 函数,便能够看到以后可见的数据。

| 设计思路

1. 如何读取 Dead 元组?

PostgreSQL 通过 快照 来决定以后数据库数据的可见性,因而当一条数据被删除时,数据的实体依然存在于数据库实例中,通常管这种不可见的数据叫做 Dead 元组(PostgreSQL 中一条数据称为一个元组)。

PostgreSQL 中提供了 SnapshotAny 的非凡快照(还有很多其余类型)。这个快照能够读取任何数据,pg_recovery 便是通过该形式读取的所有数据。默认状况下,只返回 recovery 的数据,不返回可见的数据。

2. 函数一次返回多少数据?

数据量是按行返回的,并且每次限定一行。

3. 如何管制内存?

函数会屡次执行,而有些状态是全局级的。因而能够应用 multi_call_memory_ctx(内存池的上下文)参数,来管制内存。

对于函数的参数

通过 SQL 创立函数时,执行如下语句。函数应用请参照上一期内容。

CREATE FUNCTION pg_recovery(regclass, recoveryrow bool DEFAULT true) RETURNS SETOF record

regclass:PostgreSQL 的表类型,会将表名主动转换成 OID(OID 数据库外部对象的惟一标识),因而只需输出表名即可。

reconveryrow bool DEFAULT ture:默认值 true,示意只返回 recovery 数据。取值 false, 示意返回所有数据。
执行下列语句,批改参数默认值。

select * from pg_recovery('aa', recoveryrow => false)

RETURNS SETOF record:函数返回行类型数据。

| 源码解读

必要的数据

typedef struct
{
    Relation            rel;    -- 以后操作的表
    TupleDesc           reltupledesc; -- 表的元信息
    TupleConversionMap  *map; -- 表的映射图,即表的数据映射成自定义返回的列
    TableScanDesc       scan; -- 扫描表
    HTAB                *active_ctid; -- 可见数据的 ctid
    bool                droppedcolumn; -- 是否删除列
} pg_recovery_ctx;

暗藏列

减少 recoveryrow 的暗藏列,当返回全副信息时,通过此列能够分别出该行数据是 recovery 的数据,还是用户可见的数据。

static const struct system_columns_t {
    char       *attname;
    Oid         atttypid;
    int32       atttypmod;
    int         attnum;
} system_columns[] = {{ "ctid",     TIDOID,  -1, SelfItemPointerAttributeNumber},
    {"xmin",     XIDOID,  -1, MinTransactionIdAttributeNumber},
    {"cmin",     CIDOID,  -1, MinCommandIdAttributeNumber},
    {"xmax",     XIDOID,  -1, MaxTransactionIdAttributeNumber},
    {"cmax",     CIDOID,  -1, MaxCommandIdAttributeNumber},
    {"tableoid", OIDOID,  -1, TableOidAttributeNumber},
    {"recoveryrow",     BOOLOID, -1, DeadFakeAttributeNumber},
    {0},
};

pg_recovery 简化代码

Datum
pg_recovery(PG_FUNCTION_ARGS)
{
    FuncCallContext     *funcctx;
    pg_recovery_ctx *usr_ctx;
    
    recoveryrow = PG_GETARG_BOOL(1); -- 获取默认参数

    if (SRF_IS_FIRSTCALL()) -- 每条数据,函数都会调用一次,因而须要先初始化数据
    {funcctx = SRF_FIRSTCALL_INIT(); -- 申请上下文
        oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); -- 应用内存池

        usr_ctx->rel = heap_open(relid, AccessShareLock); -- 减少读锁
        usr_ctx->reltupledesc = RelationGetDescr(usr_ctx->rel); -- 获取元信息
        funcctx->tuple_desc = BlessTupleDesc(tupdesc); -- 函数应用的元信息
        usr_ctx->map = recovery_convert_tuples_by_name(usr_ctx->reltupledesc,
                funcctx->tuple_desc, "Error converting tuple descriptors!", &usr_ctx->droppedcolumn); -- 列映射
        usr_ctx->scan = heap_beginscan(usr_ctx->rel, SnapshotAny, 0, NULL , NULL, 0); -- 扫描全副表数据
        active_scan = heap_beginscan(usr_ctx->rel, GetActiveSnapshot(), 0, NULL , NULL, 0); -- 扫描可见数据
        while ((tuplein = heap_getnext(active_scan, ForwardScanDirection)) != NULL)
            hash_search(usr_ctx->active_ctid, (void*)&tuplein->t_self, HASH_ENTER, NULL); -- 缓存可见数据的 ctid

    }

    funcctx = SRF_PERCALL_SETUP(); -- 获取函数之前的上下文
    usr_ctx = (pg_recovery_ctx *) funcctx->user_fctx;

get_tuple:
    if ((tuplein = heap_getnext(usr_ctx->scan, ForwardScanDirection)) != NULL)
    {
        -- 测验表该数据是否是 dead
        hash_search(usr_ctx->active_ctid, (void*)&tuplein->t_self, HASH_FIND, &alive);

        tuplein = recovery_do_convert_tuple(tuplein, usr_ctx->map, alive); -- 将原表数据转换成输入格局
        SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuplein)); -- 转换成 Datum 格局, 返回数据
    }
    else
    {   
        -- 读取完数据
        heap_endscan(usr_ctx->scan); -- 完结扫描表
        heap_close(usr_ctx->rel, AccessShareLock); -- 开释锁
        SRF_RETURN_DONE(funcctx); -- 开释函数资源
    }
}

生成映射表

TupleConversionMap *
recovery_convert_tuples_by_name(TupleDesc indesc,
                       TupleDesc outdesc,
                       const char *msg, bool *droppedcolumn)
{attrMap = recovery_convert_tuples_by_name_map(indesc, outdesc, msg, droppedcolumn); -- 解决 recoveryrow/ 暗藏列 / 可见列的映射

    map->indesc = indesc;
    map->outdesc = outdesc;
    map->attrMap = attrMap;
    map->outvalues = (Datum *) palloc(n * sizeof(Datum));
    map->outisnull = (bool *) palloc(n * sizeof(bool));
    map->invalues = (Datum *) palloc(n * sizeof(Datum));
    map->inisnull = (bool *) palloc(n * sizeof(bool));
    map->invalues[0] = (Datum) 0;
    map->inisnull[0] = true;

    return map;
}

元组转换函数

HeapTuple
recovery_do_convert_tuple(HeapTuple tuple, TupleConversionMap *map, bool alive)
{heap_deform_tuple(tuple, map->indesc, invalues + 1, inisnull + 1); -- 将元组拆分, 提取列数据

    for (i = 0; i < outnatts; i++)
    {outvalues[i] = invalues[j]; -- 转换数据
        outisnull[i] = inisnull[j]; -- 转换数据
    }

    return heap_form_tuple(map->outdesc, outvalues, outisnull); -- 将列数据转换成元组
}
退出移动版