PostgreSQL 触发器性能评估实战(pg_stat_user_functions)

在 PostgreSQL 中,如果表上有 行级触发器(FOR EACH ROW),每一行数据变更都会执行一次触发器函数。在高写入业务场景下,触发器可能成为性能瓶颈。

本文介绍一种 线上无侵入评估触发器性能的方法

核心思路是使用 PostgreSQL 的系统统计视图:

复制代码
pg_stat_user_functions

该视图可以统计:

  • 函数调用次数

  • 累计执行时间

  • 平均执行时间

官方文档:

https://www.postgresql.org/docs/current/monitoring-stats.html


一、查询触发器函数执行统计

执行 SQL:

复制代码
SELECT
    funcname,
    calls,
    total_time,
    self_time,
    round((total_time / NULLIF(calls, 0))::numeric, 3) AS avg_ms
FROM pg_stat_user_functions;

示例结果(业务表名已脱敏):

sql 复制代码
                funcname                |   calls   | total_time  |  self_time  | avg_ms
----------------------------------------+-----------+-------------+-------------+--------
 update_table_a*****t_timestamp         |   9561025 |  504633.213 |  504633.213 |  0.053
 update_table_s*********t_timestamp     |      2470 |    2383.109 |    2383.109 |  0.965
 update_table_a**********e_timestamp    |  17164847 |  494839.288 |  494839.288 |  0.029
 update_table_a************n_timestamp  | 156261209 | 7043454.101 | 7043454.101 |  0.045
 update_table_r******y_timestamp        |  13777786 |  434085.728 |  434085.728 |  0.032
 update_table_o****_timestamp           |  28065270 | 1024514.247 | 1024514.247 |  0.037
 update_table_o*************c_timestamp |  12346101 |  345943.599 |  345943.599 |  0.028
(7 rows)

字段说明:

字段 含义
funcname 函数名
calls 函数调用次数
total_time 累计执行时间(毫秒)
self_time 函数自身执行时间
avg_ms 单次平均耗时

注意:

复制代码
round(double precision, integer)

在 PostgreSQL 中不存在,因此需要:

复制代码
::numeric

进行类型转换。


二、如何解读这些数据

先看一个典型函数:

sql 复制代码
update_t*******r_timestamp

统计:

复制代码
calls      = 28065270
total_time = 1024514 ms
avg_ms     = 0.037 ms

说明:

  • 函数被调用 2806 万次

  • 累计执行时间 约 1024 秒

  • 平均每次执行 0.037 ms

也就是说:

复制代码
1000 次触发器执行 ≈ 37 ms
100000 次触发器执行 ≈ 3.7 秒

如果某个 UPDATE 语句影响 10 万行,触发器本身就可能消耗:

复制代码
100000 × 0.037ms ≈ 3.7 秒

这就是 行级触发器在批量更新场景的典型性能特征


三、最值得关注的触发器

最值得关注的是:

sql 复制代码
update_t***************n_timestamp

统计:

复制代码
calls      = 156261209
total_time = 7043454 ms
avg_ms     = 0.045 ms

换算一下:

复制代码
total_time ≈ 7043 秒
≈ 1.96 小时

说明该触发器累计 CPU 时间已经接近 2 小时

虽然单次执行只有:

复制代码
0.045 ms

但是因为调用次数巨大(1.56 亿次),累计开销非常明显。

这是典型的:

单次开销很小,但调用次数巨大导致总成本很高。


四、为什么触发器调用次数这么多?

因为 PostgreSQL 行级触发器是按行执行的

例如:

sql 复制代码
UPDATE table
SET status='paid'
WHERE create_time >= now() - interval '7 days';

如果更新 50 万行

复制代码
触发器执行次数 = 50 万次

如果业务系统每天有大量批量 UPDATE,就会产生非常高的触发器调用量。

PostgreSQL 官方文档也明确说明:

Row-level triggers fire once for each row affected by the triggering statement.

https://www.postgresql.org/docs/current/sql-createtrigger.html


五、如何判断触发器是否成为性能瓶颈

一般可以参考三个指标:

1 调用次数

如果:

复制代码
calls > 1亿

基本可以确定触发器参与了大量业务写入。


2 平均执行时间

经验值:

avg_ms 评价
< 0.05 ms 很轻
0.05 ~ 0.2 ms 正常
> 0.5 ms 偏重
> 1 ms 需要优化

例如:

sql 复制代码
update_t**************t_timestamp
avg_ms = 0.965

接近 1 ms,已经算比较重的触发器。


3 总执行时间

如果:

复制代码
total_time > 1000000 ms

说明该触发器已经消耗:

复制代码
> 1000 秒 CPU

需要重点关注。


六、典型触发器逻辑(示例)

很多系统会用触发器维护更新时间,例如:

复制代码
updated_at

示例逻辑:

复制代码
BEFORE UPDATE
IF NEW.col IS DISTINCT FROM OLD.col
THEN
  NEW.updated_at = now()

这种触发器在 OLTP 系统中非常常见,但需要注意:

  • 行级触发

  • 高频调用

  • 批量 UPDATE 成本放大


七、线上评估触发器性能的最佳实践

推荐步骤:

1 开启函数统计

复制代码
track_functions = pl

2 查看统计

复制代码
pg_stat_user_functions

3 找出高调用函数

按 calls 排序。

4 找出高耗时函数

按 total_time 排序。

5 分析 SQL

结合:

复制代码
pg_stat_statements

确认哪些 UPDATE 导致触发器大量执行。


八、总结

通过 pg_stat_user_functions 可以快速评估触发器性能:

核心指标:

  • calls

  • total_time

  • avg_ms

经验结论:

1️⃣ 行级触发器性能成本 与更新行数线性相关

2️⃣ 单次触发器通常很轻,但高调用量会产生巨大累计成本

3️⃣ pg_stat_user_functions 是线上分析触发器性能的最佳工具之一

相关推荐
御坂10101号2 小时前
「2>&1」是什么意思?半个世纪的 Unix 谜题
java·数据库·bash·unix
韩立学长2 小时前
基于Springboot校园志愿者服务平台77pz7812(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
代码雕刻家2 小时前
MySQL和SQL Server注意事项
数据库·mysql
代码探秘者2 小时前
【Redis】分布式锁深度解析:实现、可重入、主从一致性与强一致方案
java·数据库·redis·分布式·缓存·面试
IvorySQL3 小时前
IvorySQL 5.3 正式发布:基于 PG 18.3 内核,多特性升级+全场景适配
数据库·postgresql·开源
冰糖拌面3 小时前
mysql 与 pg 的网卡监听参数
数据库·mysql·postgresql
DBA小马哥3 小时前
智能电网调度系统国产化:为什么总卡在数据库替换这一步?
数据库
JAVA学习通3 小时前
InnoDB 存储引擎
java·数据库·mysql
oradh4 小时前
Oracle 11g单库环境PSU补丁安装
数据库·oracle