工具 | pg_recovery 设计原理与源码解读

作者:张连壮 PostgreSQL 研发工程师
从事多年 PostgreSQL 数据库内核开发,对 citus 有非常深入的研究。

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

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

1. 如何读取 Dead 元组?

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

PostgreSQL 中提供了 SnapshotAny 的特殊快照(还有很多其他类型)。这个快照可以读取任何数据,pg_recovery 便是通过该方式读取的所有数据。默认情况下,只返回 recovery 的数据,不返回可见的数据。

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

数据量是按行返回的,一次只能返回一行。

[En]

The amount of data is returned by row and is limited to one row at a time.

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, 表示返回所有数据。
执行以下语句以更改参数的缺省值。

[En]

Execute the following statement to change the parameter default value.

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]; -- &#x8F6C;&#x6362;&#x6570;&#x636E;
        outisnull[i] = inisnull[j]; -- &#x8F6C;&#x6362;&#x6570;&#x636E;
    }

    return heap_form_tuple(map->outdesc, outvalues, outisnull); -- &#x5C06;&#x5217;&#x6570;&#x636E;&#x8F6C;&#x6362;&#x6210;&#x5143;&#x7EC4;
}

Original: https://www.cnblogs.com/radondb/p/15608965.html
Author: RadonDB
Title: 工具 | pg_recovery 设计原理与源码解读

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/507992/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 索引的树结构

    二分查找 二叉树 二叉平衡树 B-TREE :二叉平衡树的基础上,使加载一次节点,可以加载更多路径数据,同时把查询范围缩减到更小 缺点:业务数据的大小可能远远超过了索引数据的大小,…

    数据库 2023年5月24日
    068
  • python-django框架中使用docker和elasticsearch配合实现搜索功能

    注意:系统环境为Ubuntu18 一、docker安装 0:如果之前有安装过docker使用以下命令卸载: bash;gutter:true; sudo apt-get remov…

    数据库 2023年6月6日
    0206
  • 12 用最有效率的方法计算 2 乘以 8

    2 << 3 左移是位运算符,直接操作内存中整数对应的二进制位,效率高; 左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方。 Original: https:…

    数据库 2023年6月6日
    075
  • Mysql生成测试数据函数

    1、查看设置是否允许创建函数系统参数 show variables like ‘log_bin_trust_function_creators’; 2、临时设置允许创建函数系统参数…

    数据库 2023年6月16日
    086
  • Centos MySQL 安装手册(超简洁)

    EL8 系统会遇到 yum报404: Errors during downloading metadata for repository ‘appstream’:原因是2022年1…

    数据库 2023年6月9日
    0111
  • 啥是Session?

    什么是 Session 会话? 1、Session 就一个接口(HttpSession)。2、Session 就是会话。它是用来维护一个客户端和服务器之间关联的一种技术。3、每个客…

    数据库 2023年6月11日
    078
  • Activiti7 多实例子流程

    顾名思义,子流程是一个包含其他活动、网关、事件等的活动,这些活动本身形成了一个流程,该流程是更大流程的一部分。 使用子流程确实有一些限制: 一个子流程只能有一个none类型的启动事…

    数据库 2023年6月14日
    0162
  • windows下安装mysql5.7

    1.首先官网下载ZIP安装包(即以解压,配置的方式安装) 2.解压完成之后在目录下创建 my.ini文件 内容如下: [mysql]设置mysql客户端默认字符集default-c…

    数据库 2023年5月24日
    083
  • 在 Pisa-Proxy 中,如何利用 Rust 实现 MySQL 代理

    一、前言 背景 在 Database Mesh 中,Pisanix 是一套以数据库为中心的治理框架,为用户提供了诸多治理能力,例如:数据库流量治理,SQL 防火墙,负载均衡和审计等…

    数据库 2023年6月16日
    0136
  • Yapi安装配置(CentOs)

    环境要求 nodejs(7.6+)mongodb(2.6+)git 准备工作 清除yum命令缓存 sudo yum clean all 卸载低版本nodejs yum remove…

    数据库 2023年6月11日
    075
  • 新版 google 谷歌浏览器跨域问题

    新版本的firefox火狐浏览器限制了 127.0.0.1 本地部署测试的时候,用火狐浏览器需要把 前端的 后台中的服务地址改成 http://localhost:8081 浏览器…

    数据库 2023年6月6日
    099
  • 3. 视图-触发器-存储过程-索引

    404. 抱歉,您访问的资源不存在。 可能是URL不正确,或者对应的内容已经被删除,或者处于隐私状态。 [En] It may be that the URL is incorre…

    数据库 2023年5月24日
    087
  • Django设置跨域访问

    Django设置跨域访问 pip install django-cors-headers (2) settings.py 配置如下 INSTALLED_APPS = [ # ‘dj…

    数据库 2023年6月14日
    097
  • MySQL在Linux环境下的安装、初始化、配置

    CentOS操作系统,可选择: MySQL Community Server 8.0.28 Red Hat Enterprise Linux / Oracle Linux Red …

    数据库 2023年5月24日
    094
  • 如何使用Intellij IDEA工具导入SVN项目

    步骤一:选择VCS 打开Intellij IDEA开发工具,在导航栏中选择 VCS栏位,如图。 步骤二:创建SVN地址 执行步骤二,可以看见打开了一个SVN Repositorie…

    数据库 2023年6月6日
    086
  • MySQL系统安装与部署

    数据库版本标准化 1.确认Supported Platforms https://www.mysql.com/support/ 2.确认安装版本 推荐:5.7.22 ,8.0.20…

    数据库 2023年5月24日
    084
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球