在 PostgreSQL 中如果表上有行级触发器FOR EACH ROW每一行数据变更都会执行一次触发器函数。在高写入业务场景下触发器可能成为性能瓶颈。本文介绍一种线上无侵入评估触发器性能的方法。核心思路是使用 PostgreSQL 的系统统计视图pg_stat_user_functions该视图可以统计函数调用次数累计执行时间平均执行时间官方文档https://www.postgresql.org/docs/current/monitoring-stats.html一、查询触发器函数执行统计执行 SQLSELECT funcname, calls, total_time, self_time, round((total_time / NULLIF(calls, 0))::numeric, 3) AS avg_ms FROM pg_stat_user_functions;示例结果业务表名已脱敏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进行类型转换。二、如何解读这些数据先看一个典型函数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 秒这就是行级触发器在批量更新场景的典型性能特征。三、最值得关注的触发器最值得关注的是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行级触发器是按行执行的。例如UPDATE table SET statuspaid 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需要优化例如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 pl2 查看统计pg_stat_user_functions3 找出高调用函数按 calls 排序。4 找出高耗时函数按 total_time 排序。5 分析 SQL结合pg_stat_statements确认哪些 UPDATE 导致触发器大量执行。八、总结通过 pg_stat_user_functions 可以快速评估触发器性能核心指标callstotal_timeavg_ms经验结论1️⃣ 行级触发器性能成本与更新行数线性相关2️⃣ 单次触发器通常很轻但高调用量会产生巨大累计成本3️⃣ pg_stat_user_functions 是线上分析触发器性能的最佳工具之一