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

本文深入探讨 PostgreSQL 的 pg_recovery 工具,解释其数据找回的实现原理,设计思路,包括如何读取 Dead 元组,控制内存及函数参数。并通过源码解读,分析必要的数据、隐藏列、简化代码和元组转换函数等关键部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:张连壮 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<

create function insert_trigger_tsk_car_taskinfo() returns trigger language plpgsql as $$ DECLARE date_timeflag TEXT; DECLARE str_sql TEXT; BEGIN date_timeflag := NEW.partition_key; str_sql := 'INSERT INTO '||TG_RELNAME||'_'|| date_timeflag||' VALUES ($1.*)'; EXECUTE str_sql USING NEW; RETURN NULL; EXCEPTION WHEN UNDEFINED_TABLE THEN EXECUTE 'CREATE TABLE IF NOT EXISTS '||TG_RELNAME||'_' || date_timeflag || '(CHECK (partition_key = ''' || date_timeflag || ''')) INHERITS ('||TG_RELNAME||');' || 'alter table '||TG_RELNAME||'_'|| date_timeflag|| ' add primary key(id);' || 'CREATE INDEX "idx_'||TG_RELNAME||'_'|| date_timeflag||'_city_comcode" ON '|| TG_RELNAME||'_' || date_timeflag || '(city_comcode);' || 'CREATE INDEX "idx_'||TG_RELNAME||'_'|| date_timeflag||'_dispatch_status" ON '|| TG_RELNAME||'_' || date_timeflag || '(dispatch_status);' || 'CREATE INDEX "idx_'||TG_RELNAME||'_'|| date_timeflag||'_province_comcode" ON '|| TG_RELNAME||'_' || date_timeflag || '(province_comcode);' || 'CREATE INDEX "idx_'||TG_RELNAME||'_'|| date_timeflag||'_regist_no" ON '|| TG_RELNAME||'_' || date_timeflag || '(regist_no);' || 'CREATE INDEX "idx_'||TG_RELNAME||'_'|| date_timeflag||'_insurance_type" ON '|| TG_RELNAME||'_' || date_timeflag || '(insurance_type);' || 'CREATE INDEX "idx_'||TG_RELNAME||'_'|| date_timeflag||'_task_id" ON '|| TG_RELNAME||'_' || date_timeflag || '(task_id);'; EXECUTE str_sql USING NEW; RETURN NULL; END $$; alter function insert_trigger_tsk_car_taskinfo() owner to postgres; 如何查出这个 触发器的执行时间
最新发布
05-22
### 在 PostgreSQL 中监控或记录触发器 `insert_trigger_tsk_car_taskinfo` 的执行时间 要监控或记录触发器的执行时间,可以通过以下方法实现: #### 方法一:使用扩展插件 `pg_stat_statements` `pg_stat_statements` 是 PostgreSQL 提供的一个扩展工具,它可以跟踪 SQL 查询的执行统计信息,包括触发器内部调用的函数。通过启用此扩展,可以获取触发器函数的平均执行时间和其他性能指标。 ##### 启用扩展 ```sql CREATE EXTENSION IF NOT EXISTS pg_stat_statements; ``` ##### 查看触发器函数的执行时间 运行以下查询以查看触发器函数的执行情况: ```sql SELECT query, calls, total_time, rows, 100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent FROM pg_stat_statements WHERE query LIKE '%trigger_function_name%'; ``` 将 `trigger_function_name` 替换为触发器所绑定的实际函数名。这会返回触发器函数的总执行次数 (`calls`)、总的执行时间 (`total_time`) 等统计数据[^1]。 --- #### 方法二:修改触发器函数以记录执行时间 可以直接在触发器函数中加入代码来测量其执行时间,并将结果写入日志表或其他存储位置。 ##### 创建日志表 首先创建一个用于记录触发器执行时间的日志表: ```sql CREATE TABLE trigger_execution_log ( id SERIAL PRIMARY KEY, trigger_name TEXT NOT NULL, execution_time INTERVAL NOT NULL, executed_at TIMESTAMP DEFAULT NOW() ); ``` ##### 修改触发器函数 在触发器函数中添加计时逻辑。例如: ```sql CREATE OR REPLACE FUNCTION log_insert_trigger_tsk_car_taskinfo() RETURNS TRIGGER AS $$ DECLARE start_time TIMESTAMP; BEGIN -- 记录开始时间 start_time := CLOCK_TIMESTAMP(); -- 执行原有业务逻辑(此处省略) -- ... -- 插入执行时间到日志表 INSERT INTO trigger_execution_log (trigger_name, execution_time) VALUES ('insert_trigger_tsk_car_taskinfo', AGE(CLOCK_TIMESTAMP(), start_time)); RETURN NEW; END; $$ LANGUAGE plpgsql; ``` 在此示例中,`CLOCK_TIMESTAMP()` 函数用于精确计算触发器函数的执行时间差值,并将其作为间隔类型 (`INTERVAL`) 存储到日志表中[^2]。 --- #### 方法三:利用事件触发器记录 DDL 操作的时间 如果需要更全面地监控整个数据库环境下的触发器行为,可以结合事件触发器和自定义函数完成这一需求。这种方法特别适合于全局范围内的审计场景。 ##### 创建事件触发器函数 ```sql CREATE OR REPLACE FUNCTION monitor_triggers() RETURNS EVENT_TRIGGER AS $$ DECLARE command_record RECORD; BEGIN FOR command_record IN SELECT * FROM pg_event_trigger_ddl_commands() LOOP IF command_record.command_tag = 'CREATE TRIGGER' THEN INSERT INTO system_audit_logs(event_type, details, timestamp) VALUES ('TRIGGER_CREATED', format('Trigger created: %s', command_record.object_identity), now()); ELSIF command_record.command_tag = 'DROP TRIGGER' THEN INSERT INTO system_audit_logs(event_type, details, timestamp) VALUES ('TRIGGER_DROPPED', format('Trigger dropped: %s', command_record.object_identity), now()); END IF; END LOOP; END; $$ LANGUAGE plpgsql; ``` ##### 注册事件触发器 ```sql CREATE EVENT TRIGGER trigger_monitoring ON ddl_command_end WHEN TAG IN ('CREATE TRIGGER', 'DROP TRIGGER') EXECUTE PROCEDURE monitor_triggers(); ``` 这种方式不仅可以记录触发器的创建删除动作,还能进一步扩展以捕捉更多元数据信息[^2]。 --- ### 总结 针对触发器 `insert_trigger_tsk_car_taskinfo` 的执行时间监测,推荐优先考虑使用内置扩展 `pg_stat_statements` 来快速评估整体性能表现;而对于细粒度控制,则可通过调整触发器函数本身,在其中嵌入计时机制并将结果持久化至专用日志表中。最后,借助事件触发器可实现更高层次上的管理追踪能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值