当你在PostgreSQL中执行SQL出错时, 如何能跟踪导致出错的堆栈?
你可能会想到gdb, 是的, 比如等等数据库崩溃, 打印core dump, 从core dump文件中进行分析. 或者用gdb提前跟踪将要出错的进程(那就太复杂了).
有没有什么更便捷的方式, 让SQL执行出错(但是数据库没有崩溃)时打印堆栈信息呢?
有个patch, 下面就看看怎么做?
以下内容翻译自:
如果 Postgres 崩溃了,你可以用 gdb 获取堆栈跟踪。但是如何调试不会导致 Postgres 崩溃的错误呢?
让我们获取 Postgres 17 源码并使用调试符号构建它。
$ git clone https://github.com/postgres/postgres
$ cd postgres
$ git checkout REL_17_STABLE
$ ./configure --enable-debug --without-icu \
--prefix=$(pwd)/build \
--libdir=$(pwd)/build/lib
$ make -j16 && make install
创建并启动数据库。 ··· ./build/bin/pg_ctl -D $(pwd)/testdb -l logfile start ···
并用 psql 连接到它。
$ ./build/bin/psql postgres
psql (17.5)
Type "help" for help.
postgres=#
让我们通过查询不存在的表来触发错误。
postgres=# SELECT * FROM nothere;
ERROR: relation "nothere" does not exist
LINE 1: SELECT * FROM nothere;
^
postgres=#
这不是我们通常尝试调试的ERROR类型,但它很简单,我的目标是演示这个过程。
在我了解得更多ERROR之前,如果给我一些通用的东西,我会抓住字符串中非动态的部分用grep在代码中查找。
$ git grep '"relation' | grep 'does not exist"' | grep '.c:'
src/backend/catalog/aclchk.c: errmsg("relation with OID %u does not exist",
src/backend/catalog/aclchk.c: errmsg("relation with OID %u does not exist",
src/backend/catalog/aclchk.c: errmsg("relation with OID %u does not exist",
src/backend/catalog/namespace.c: errmsg("relation \"%s.%s\" does not exist",
src/backend/catalog/namespace.c: errmsg("relation \"%s\" does not exist",
src/backend/parser/parse_relation.c: errmsg("relation \"%s.%s\" does not exist",
src/backend/parser/parse_relation.c: errmsg("relation \"%s\" does not exist",
src/backend/parser/parse_relation.c: errmsg("relation \"%s\" does not exist",
src/backend/utils/adt/regproc.c: errmsg("relation \"%s\" does not exist",
src/pl/plpgsql/src/pl_comp.c: errmsg("relation \"%s\" does not exist", ident)));
是的,经过一些字符串操作和 grep 之后,我们也许能找到它。也可能不会。
了解ERROR错误出自哪个函数的一个直接方法是将VERBOSITY设置为verbose。启用此选项后,将告诉我们错误来自哪个函数名和c文件(这本身就解决了很大一部分原始问题)。
postgres=# \set VERBOSITY verbose
postgres=# SELECT * FROM nothere;
ERROR: 42P01: relation "nothere" does not exist
LINE 1: SELECT * FROM nothere;
^
LOCATION: parserOpenTable, parse_relation.c:1452
但我上周值班的时候也刚了解了GUC backtrace_functions。你可以用这个 GUC 让 Postgres 在发生错误时打印堆栈跟踪信息。这太有用了!
详见:
https://www.postgresql.org/docs/current/runtime-config-developer.html
诀窍在于,你必须明确指定哪些函数会生成你希望获取的堆栈跟踪日志。我猜这是为了避免堆栈跟踪日志泛滥。也就是说,只有当日志来自某个函数(以逗号分隔的值命名)时,backtrace_functions 才会打印堆栈跟踪。
现在我们先用\set VERBOSITY verbose知道了日志来自的确切函数:parserOpenTable。所以我们可以设置backtrace_functions = parserOpenTable并重新加载 Postgres 配置。
postgres=# ALTER SYSTEM SET backtrace_functions='parserOpenTable';
ALTER SYSTEM
postgres=# SELECT pg_reload_conf();
pg_reload_conf
----------------
t
(1 row)
再次运行该失败的查询:
postgres=# SELECT * FROM nothere;
ERROR: 42P01: relation "nothere" does not exist
LINE 1: SELECT * FROM nothere;
^
LOCATION: parserOpenTable, parse_relation.c:1452
我们将在中看到此错误的堆栈跟踪logfile!
2025-07-31 13:46:42.657 EDT [84985] ERROR: relation "nothere" does not exist at character 15
2025-07-31 13:46:42.657 EDT [84985] BACKTRACE:
2 postgres 0x000000010499a0d0 parserOpenTable.cold.1 + 156
3 postgres 0x00000001045d64cc parserOpenTable + 124
4 postgres 0x00000001045d666c addRangeTableEntry + 272
5 postgres 0x00000001045c2744 transformFromClauseItem + 256
6 postgres 0x00000001045c25a8 transformFromClause + 132
7 postgres 0x00000001045ad3fc transformSelectStmt + 128
8 postgres 0x00000001045ac858 transformStmt + 4316
9 postgres 0x00000001045ab344 parse_analyze_fixedparams + 192
10 postgres 0x00000001048003a8 exec_simple_query + 1024
11 postgres 0x00000001047fe2a8 PostgresMain + 3108
12 postgres 0x00000001047fa08c BackendInitialize + 0
13 postgres 0x000000010476f22c PgArchShmemSize + 0
14 postgres 0x0000000104773308 ServerLoop + 6772
15 postgres 0x0000000104770d38 PostmasterMain + 3436
16 postgres 0x00000001046a5844 main + 800
17 dyld 0x0000000196993154 start + 2476
还有一个补丁,可以让你自动打印所有错误的堆栈跟踪。这对于自动化测试环境尤其有用,因为这类环境错误很少发生,而且调试起来很繁琐。我期待这个补丁最终能够发布!