在 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 是线上分析触发器性能的最佳工具之一