PostgreSQL 的目录表查多了你会发现,“查所有函数”这个需求比想象中出现的频率高得多。无论是接手一套旧系统需要梳理数据库资产,还是准备做数据库层重构,抑或只是想确认某个自定义函数是不是真的存在,你都会立刻想到要在 PostgreSQL 里查询数据库中所有函数信息——把函数名(全部)列出来,再逐一核对。这篇文章就是专门写这个事的:我会把涉及系统表、信息视图、psql 元命令的几种做法全部拆开讲清楚,并给出可以直接粘贴运行的 SQL。1. 为什么要盘点“全部函数”?这其实是个正经的资产盘点需求1.1 函数清单是数据库资产的“目录页”很多开发者第一次产生“查一下全部函数”的念头,往往是因为接到了一套别人维护过很久的数据库。库里到底有哪些函数、哪些是业务自定义的、哪些是第三方扩展带进来的,心里完全没底。这时候把所有函数名拉出来看一眼,是最快建立全局印象的方式。实际上这不只是“看一眼”这么简单,函数清单会用在三个地方:第一,做数据库文档化,给后续接手的人提供基础目录;第二,做变更审计,对比不同环境的函数定义是否一致;第三,做迁移准备,迁移前必须知道有哪些函数需要随库走,否则业务一启动就是“function does not exist”。我看过很多项目把这些工作拖到上线前才做,结果就是临时抱佛脚,一边查函数一边补文档,效率极低。如果平时能熟练地用 SQL 直接查 PostgreSQL 里的函数信息,这类需求基本就是几十秒的事。1.2 PostgreSQL 里的“函数”到底包含哪几类这里的“函数”不能理解得太窄。PostgreSQL 在 11 版本之后引入了prokind字段,把数据库可调用对象分成了四类:f:普通函数(function)p:存储过程(procedure)a:聚合函数(aggregate function)w:窗口函数(window function)所以当你搜“函数名(全部)”时,必须先想清楚一个问题:你想要的到底是全部可调用对象,还是只包含普通函数?很多业务场景里,存储过程、聚合函数、窗口函数同样需要被盘点,它们也都会被记录在pg_proc这张表里。如果只按prokind = 'f'过滤,聚合和过程就会被漏掉;如果不加过滤,又可能混入大量系统内置函数。1.3 系统函数和业务函数要不要混在一起这是另一个必须先决定的维度。PostgreSQL 安装完成后,pg_catalog、information_schema这些系统 schema 里躺着上千个内置函数。直接执行SELECT proname FROM pg_proc会把这些系统函数全部带出来,结果几十页都翻不完,而且绝大多数不是你想找的。绝大多数场景下,我们要的是业务 schema 下的函数。即使有人真的想把系统函数也列出来,也应该主动加条件去查看,而不是任由系统函数淹没结果。所以在后面给出的每条 SQL 里,我都会默认过滤掉系统 schema,只在public等业务 schema 中搜索。如果你确实需要“全部”,把过滤条件去掉即可,但心里要清楚,那个“全部”的含义通常是“全部业务函数”,而不是“全部系统函数”。2. 查询函数信息的底层路径:先认识两个数据来源2.1 pg_proc:PostgreSQL 最权威的函数户籍表pg_proc是 PostgreSQL 的系统目录表,负责记录所有函数、存储过程、聚合函数和窗口函数的信息。注意是“所有”,不区分用户对象和系统对象,也不区分当前数据库和模板数据库。每一行代表一个函数签名,同名但参数不同的函数会被记成多行。在实际查询时,最常用的列有这么几个:proname:函数名pronamespace:函数所在 schema 的 OID,需要关联 pg_namespace 才能拿到 schema 名prorettype:返回类型的 OIDpronargs:输入参数个数,也就是参数列表里 in/inout 参数的数量proargtypes:参数类型 OID 组成的数组proargnames:参数名数组proargmodes:参数模式数组,包含 i(in)、o(out)、b(inout)、v(variadic)、t(table)prosrc:函数实现源码,PL/pgSQL 函数就是函数体文本,C 函数则是符号名probin:C 函数对应的动态库路径prokind:对象类型,f/p/a/wprolang:函数语言 OID,关联 pg_language 拿语言名provolatile:易失性标记,i 表示 immutable,s 表示 stable,v 表示 volatileproacl:函数访问控制列表看这张表,你会发现它把函数的身份、签名、实现、权限全部放在了一起。这就是为什么我说的“函数户籍表”这个比喻很贴切——只要能从pg_proc里抽出几十行,函数的绝大多数关键信息都能拿到。2.2 information_schema.routines:SQL 标准视角如果你写过多数据库兼容代码,应该会更习惯用information_schema。这套视图是 SQL 标准定义的,PostgreSQL 提供了routines视图专门展示函数和过程信息。比较常用的查询是:SELECT routine_schema, routine_name, specific_name, external_language FROM information_schema.routines WHERE routine_schema NOT IN ('pg_catalog', 'information_schema') ORDER BY routine_schema, routine_name;specific_name是个很妙的列。因为 PostgreSQL 允许函数重载,单靠routine_name无法唯一定位一个函数,标准为每个函数分配了一个特定的名称字符串,可以用来区分同名但不同签名的函数。不过我必须提醒你:information_schema的数据在某些场景下没有pg_proc完整。比如有些权限敏感信息、C 语言函数细节,在标准视图里是被简化掉甚至隐藏的。所以我的建议是:日常快速查看可以用information_schema.routines,但真正要做完整盘点、导出定义、判断权限,直接操作pg_proc更可靠。2.3 两条路径对比:怎么选更合适对比项pg_procinformation_schema.routines数据完整性极高,包含源码、库路径、ACL 等中等,面向标准兼容层是否包含系统函数包含,需手动过滤包含,需手动过滤函数重载区分按 OID 区分,需要额外签名函数提供 specific_name 区分获取函数定义支持 pg_get_functiondef 直接拿 DDLroutine_definition 字段在部分版本中不可靠对权限限制的反映相对完整,能看到 ACL只显示你有权限的对象适合场景资产盘点、迁移、审计、排障跨数据库兼容查询、报表统计从这张表可以看出,如果你做的是“查询数据库中所有函数信息”这种硬核任务,首选一定是pg_proc。它才是这个数据库真正的事实来源。3. 实操:从“函数名”到“完整函数定义”的四级跳3.1 第一跳:只拿全部函数名,最简写法先满足标题里的基础诉求:把函数名全部列出来。最直接的方式是:SELECT DISTINCT proname FROM pg_proc WHERE prokind = 'f' AND pronamespace NOT IN ( SELECT oid FROM pg_namespace WHERE nspname IN ('pg_catalog', 'information_schema') ) ORDER BY proname;这条 SQL 做了两件事:用DISTINCT去掉重载导致的重复名,用pronamespace NOT IN过滤系统 schema。为什么用prokind = 'f'?因为标题和多数场景默认只要普通函数。如果你连聚合函数、窗口函数、存储过程也想一起带上,把条件改成prokind IN ('f', 'p', 'a', 'w')或者直接去掉这行。但是“DISTINCT proname”有一个隐性问题:PostgreSQL 支持同名函数重载,只留函数名会丢掉参数签名信息。所以这个版本只适合快速扫一眼,不适合作为正式的函数清单。老版本 PostgreSQL 没有prokind字段,用proisagg和proiswindow两个布尔列也能区分聚合和窗口函数,如果你在维护 10 及以下版本,把prokind换成兼容写法即可。3.2 第二跳:带上 schema、参数签名与返回类型真正能直接进文档、能拿来讨论的函数清单,至少要包含函数所在的 schema、函数名、参数列表和返回类型。推荐用这条:SELECT n.nspname AS schema_name, p.proname AS function_name, pg_get_function_identity_arguments(p.oid) AS args, pg_get_function_result(p.oid) AS return_type, l.lanname AS language, p.prokind AS kind FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace JOIN pg_language l ON l.oid = p.prolang WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT LIKE 'pg_toast%' AND n.nspname NOT LIKE 'pg_temp%' ORDER BY n.nspname, p.proname;重要说明:pg_get_function_identity_arguments拿到的是“标识参数列表”,也就是用于区分重载函数的那部分参数,它不包含OUT参数;而pg_get_function_arguments会返回完整参数列表,包括OUT参数。如果你关心的是函数内部怎么被调用,用identity_arguments;如果你关心的是一个函数完整对外暴露的参数形态,用arguments版本。之所以要带上 schema 和参数签名,是因为单独的函数名在这种场景下没有足够区分度。比如项目里常见public.create_user(text)和audit.create_user(text)两个函数,前者是业务入口,后者是审计辅助函数,名字一样,但是 schema 完全不同。还有更常见的重载情况:calculate(text)和calculate(int, int),它们在同一 schema 下共存,你不可能只说“查一下 calculate”,必须带上签名才能确定到底说的是谁。3.3 第三跳:导出函数完整定义,备份迁移直接用光有函数名和签名还不够,很多场景下你还想看函数体到底写了什么,或者把函数定义直接导出成 DDL 做备份、迁移、版本对比。PostgreSQL 提供了一个很贴心的封装函数:pg_get_functiondef。用法很简单,传入函数 OID,它会返回一段完整的CREATE OR REPLACE FUNCTION语句。比如:SELECT n.nspname AS schema_name, p.proname AS function_name, pg_get_functiondef(p.oid) AS function_definition FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND p.prokind = 'f' ORDER BY n.nspname, p.proname;输出结果里每行会有一长串的 CREATE OR REPLACE FUNCTION 文本,这就是该函数当前完整定义。我在做迁移审计时最喜欢用这条,因为它不需要依赖任何备份工具,直接在目标库跑一次,就能把所有函数定义保存成 sql 文件,和源库做 diff。pg_get_functiondef有一个细节要注意:它输出的函数名会自动带上 schema 前缀,比如CREATE OR REPLACE FUNCTION public.foo(...)。这对于迁移来说是好事,直接在目标库执行也能创建到正确 schema;但如果你的脚本还要动态替换 schema 名,反而要小心这个自动生成的前缀。3.4 第四跳:用 psql 元命令快速浏览如果你没有把 SQL 存成脚本的习惯,只想在 psql 里临时看一下,元命令其实是最快的路径。\df:列出所有函数\df public.*:列出 public schema 下的所有函数\df+ foo:查看指定函数详情,包含源码\df默认已经带了 schema、参数、返回类型三列信息,视觉效果比 SQL 输出更紧凑。\df+会额外展示函数体、访问权限、是否安全定义者等信息,排障时非常好用。不过元命令的输出是面向屏幕的,不方便进一步加工。如果后面要写脚本自动化处理,还是要落到pg_proc查询上。我的习惯是:临时确认用\df,系统性盘点用 SQL,两者互补不冲突。4. 常见问题与排查技巧实录4.1 结果里一大堆 pg_catalog 函数,怎么过滤最干净这是新手上路最容易懵的地方。不管你在pg_proc里怎么按名字过滤,系统函数总是混进来,因为pg_catalog里的函数是 PostgreSQL 自带的,数量非常多。要过滤得干净,最保险的是按 schema 维度排除:n.nspname NOT IN ('pg_catalog', 'information_schema')再加两个保险条件:n.nspname NOT LIKE 'pg_toast%' n.nspname NOT LIKE 'pg_temp%'pg_toast和pg_temp这两个 schema 虽然一般不会直接放业务函数,但某些扩展或异常操作可能往里注册对象,顺手排除掉不会有副作用。注意:千万别只靠proname NOT LIKE 'pg_%'这种写法,因为函数名不一定带 pg 前缀,schema 过滤才是正解。4.2 同名函数为什么查出来好几条这几乎是必问的问题。PostgreSQL 的函数重载机制导致同一个函数名可以对应多个不同参数签名的函数。你看到的结果是:calculate calculate(integer, integer) calculate calculate(text)这不代表数据库有问题,而是它自然支持重载。所以凡是测“函数名”是否存在的场景,只比对名字远远不够,必须把pg_get_function_identity_arguments(p.oid)拼上才算完整标识。如果你要用程序逻辑判断某个函数是否已存在,我建议直接查:SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = 'public' AND p.proname = 'calculate' AND pg_get_function_identity_arguments(p.oid) = 'integer, integer';这样定位唯一签名,才不会有二义性。4.3 函数定义 prosrc 为空是坏了吗查pg_proc时你会发现某些函数行的prosrc是空的,或者看到的是奇怪的符号名。这不一定是数据损坏,先看语言类型:PL/pgSQL 函数:prosrc通常存的就是函数体源码,理应有内容。C 函数:prosrc存的是动态库中的符号名,probin存的是 .so 库文件路径,所以看不到纯文本函数体是正常的。internal 函数:属于 PostgreSQL 内部实现,源码不对外开放,prosrc通常为空。所以遇到prosrc为空,先join pg_language看看lanname是什么。lanname = 'c'就去查probin;lanname = 'internal'就不用管了,那是内核自带的功能。这类函数通常出现在扩展模块里,比如某个第三方索引插件,业务上一般不需要动它。4.4 权限不足,函数定义看不到怎么办普通用户在某些情况下确实可能看不到全部函数定义。information_schema.routines只展示当前用户有权访问的对象,pg_proc中的proacl字段记录了函数的访问控制列表。如果一个用户对该函数没有EXECUTE权限,也不在 ACL 的授予名单里,查询时就会遇到“函数存在但看不到细节”的尴尬。遇到这种情况,先确认当前连接用户是谁,再确认函数属主是谁:SELECT p.proname, pg_get_userbyid(p.proowner) AS owner, p.proacl FROM pg_proc p WHERE p.proname = 'your_function';超级用户不受此类限制,所以生产环境做完整盘点时,尽量用具备足够权限的账号连接数据库。如果你只是普通开发者,但是确实需要审计函数定义,就别硬跟系统权限对抗,找管理员开权限或者提供脱敏后的导出结果才是正路。4.5 查询很慢?不应该慢但确实遇到的场景pg_proc本身数据量通常只有几千到几万行,直接条件查询不可能慢。真正慢的场景往往是你把pg_get_functiondef用在了几百个函数上,或者你在WHERE里对函数名做了ILIKE '%xxx%'却没有走索引,又或者你把pg_proc和多个 catalog 表做了笛卡尔积式的 join。遇到查询变慢,先看执行计划。多数情况下,加上 schema 和 proname 的等值条件就能走pg_proc的常规索引。如果你是做了全文搜索式的模糊匹配,建议先用\df *xxx*在 psql 里快速过滤,再针对结果做精确查询,比盲目优化 SQL 更省事。5. 进阶玩法:把函数清单变成自动化运维工具5.1 建立函数清单快照,对比变更函数清单不只是查一次就完事的,日常开发中函数会改动。你可以把 3.2 节的查询结果存到一张历史表里,每天定时跑一次,再通过EXCEPT或FULL JOIN对比函数名、参数签名和返回值的变化。比如我习惯做一个简单的变更记录表:CREATE TABLE function_snapshot ( snapshot_date date, schema_name text, function_name text, args text, return_type text );每天把查询结果插入这张表,第二天就能对比出哪些函数新增、哪些被删除、哪些签名变了。这个思路在多人协作的项目里特别有用,可以有效避免“明明函数定义变了,大家却不知道”的情况。5.2 多数据库批量盘点如果你管理的是多套环境,比如测试库、预发布库、生产库,函数清单往往需要跨环境比对。最原始的做法是每台库跑一遍 SQL,然后把结果贴到同一个表格里对比。更好的做法是借助 PostgreSQL 的postgres_fdw把多个库连起来,或者直接用 shell 循环调用 psql,把每个库的函数定义 dump 到单独文件,再统一比对。这里我不展开完整脚本,核心思路就是:函数清单一旦生成,先落文件,后做 diff,别每次都靠肉眼盯屏幕。5.3 定位“可疑函数”和“被遗忘函数”函数清单还有一个隐藏价值:帮助发现异常。比如一个业务数据库里突然冒出一个名字很随意的函数,或者某个 schema 下出现了一批不属于任何应用的函数,这些都有可能是历史遗留或潜在风险。你可以把函数清单和pg_depend关联,查看函数与表的依赖关系。如果一个函数依赖的表已经不存在了,那这个函数大概率是被遗忘的僵尸对象。反过来,如果一个表被业务频繁调用,但没有任何函数引用它,那也可能是逻辑设计上的漏洞。这类分析需要结合具体业务去判断,但出发点永远是先把“全部函数信息”准确地捞出来——没有这份清单,后面所有排查都无从谈起。我在实际整理数据库函数清单时踩过不少坑,最值得提醒的就是:不要把DISTINCT proname当成最终版,函数签名和 schema 才是唯一标识。建议把 3.2 节那条 SQL 存成一个小工具脚本,每次接到新项目先跑一遍,五秒钟就能知道这套库的函数资产长什么样。最后再分享一个小技巧:如果你只想快速搜索某个函数名,在 psql 里直接\df *xxx*会比写 SQL 更快,但任何需要保存、对比、上报的场景,老老实实用pg_proc才是正经路子。
PostgreSQL 的目录表查多了你会发现,“查所有函数”这个需求比想象中出现的频率高得多。无论是接手一套旧系统需要梳理数据库资产,还是准备做数据库层重构,抑或只是想确认某个自定义函数是不是真的存在,你都会立刻想到要在 PostgreSQL 里查询数据库中所有函数信息——把函数名(全部)列出来,再逐一核对。这篇文章就是专门写这个事的:我会把涉及系统表、信息视图、psql 元命令的几种做法全部拆开讲清楚,并给出可以直接粘贴运行的 SQL。1. 为什么要盘点“全部函数”?这其实是个正经的资产盘点需求1.1 函数清单是数据库资产的“目录页”很多开发者第一次产生“查一下全部函数”的念头,往往是因为接到了一套别人维护过很久的数据库。库里到底有哪些函数、哪些是业务自定义的、哪些是第三方扩展带进来的,心里完全没底。这时候把所有函数名拉出来看一眼,是最快建立全局印象的方式。实际上这不只是“看一眼”这么简单,函数清单会用在三个地方:第一,做数据库文档化,给后续接手的人提供基础目录;第二,做变更审计,对比不同环境的函数定义是否一致;第三,做迁移准备,迁移前必须知道有哪些函数需要随库走,否则业务一启动就是“function does not exist”。我看过很多项目把这些工作拖到上线前才做,结果就是临时抱佛脚,一边查函数一边补文档,效率极低。如果平时能熟练地用 SQL 直接查 PostgreSQL 里的函数信息,这类需求基本就是几十秒的事。1.2 PostgreSQL 里的“函数”到底包含哪几类这里的“函数”不能理解得太窄。PostgreSQL 在 11 版本之后引入了prokind字段,把数据库可调用对象分成了四类:f:普通函数(function)p:存储过程(procedure)a:聚合函数(aggregate function)w:窗口函数(window function)所以当你搜“函数名(全部)”时,必须先想清楚一个问题:你想要的到底是全部可调用对象,还是只包含普通函数?很多业务场景里,存储过程、聚合函数、窗口函数同样需要被盘点,它们也都会被记录在pg_proc这张表里。如果只按prokind = 'f'过滤,聚合和过程就会被漏掉;如果不加过滤,又可能混入大量系统内置函数。1.3 系统函数和业务函数要不要混在一起这是另一个必须先决定的维度。PostgreSQL 安装完成后,pg_catalog、information_schema这些系统 schema 里躺着上千个内置函数。直接执行SELECT proname FROM pg_proc会把这些系统函数全部带出来,结果几十页都翻不完,而且绝大多数不是你想找的。绝大多数场景下,我们要的是业务 schema 下的函数。即使有人真的想把系统函数也列出来,也应该主动加条件去查看,而不是任由系统函数淹没结果。所以在后面给出的每条 SQL 里,我都会默认过滤掉系统 schema,只在public等业务 schema 中搜索。如果你确实需要“全部”,把过滤条件去掉即可,但心里要清楚,那个“全部”的含义通常是“全部业务函数”,而不是“全部系统函数”。2. 查询函数信息的底层路径:先认识两个数据来源2.1 pg_proc:PostgreSQL 最权威的函数户籍表pg_proc是 PostgreSQL 的系统目录表,负责记录所有函数、存储过程、聚合函数和窗口函数的信息。注意是“所有”,不区分用户对象和系统对象,也不区分当前数据库和模板数据库。每一行代表一个函数签名,同名但参数不同的函数会被记成多行。在实际查询时,最常用的列有这么几个:proname:函数名pronamespace:函数所在 schema 的 OID,需要关联 pg_namespace 才能拿到 schema 名prorettype:返回类型的 OIDpronargs:输入参数个数,也就是参数列表里 in/inout 参数的数量proargtypes:参数类型 OID 组成的数组proargnames:参数名数组proargmodes:参数模式数组,包含 i(in)、o(out)、b(inout)、v(variadic)、t(table)prosrc:函数实现源码,PL/pgSQL 函数就是函数体文本,C 函数则是符号名probin:C 函数对应的动态库路径prokind:对象类型,f/p/a/wprolang:函数语言 OID,关联 pg_language 拿语言名provolatile:易失性标记,i 表示 immutable,s 表示 stable,v 表示 volatileproacl:函数访问控制列表看这张表,你会发现它把函数的身份、签名、实现、权限全部放在了一起。这就是为什么我说的“函数户籍表”这个比喻很贴切——只要能从pg_proc里抽出几十行,函数的绝大多数关键信息都能拿到。2.2 information_schema.routines:SQL 标准视角如果你写过多数据库兼容代码,应该会更习惯用information_schema。这套视图是 SQL 标准定义的,PostgreSQL 提供了routines视图专门展示函数和过程信息。比较常用的查询是:SELECT routine_schema, routine_name, specific_name, external_language FROM information_schema.routines WHERE routine_schema NOT IN ('pg_catalog', 'information_schema') ORDER BY routine_schema, routine_name;specific_name是个很妙的列。因为 PostgreSQL 允许函数重载,单靠routine_name无法唯一定位一个函数,标准为每个函数分配了一个特定的名称字符串,可以用来区分同名但不同签名的函数。不过我必须提醒你:information_schema的数据在某些场景下没有pg_proc完整。比如有些权限敏感信息、C 语言函数细节,在标准视图里是被简化掉甚至隐藏的。所以我的建议是:日常快速查看可以用information_schema.routines,但真正要做完整盘点、导出定义、判断权限,直接操作pg_proc更可靠。2.3 两条路径对比:怎么选更合适对比项pg_procinformation_schema.routines数据完整性极高,包含源码、库路径、ACL 等中等,面向标准兼容层是否包含系统函数包含,需手动过滤包含,需手动过滤函数重载区分按 OID 区分,需要额外签名函数提供 specific_name 区分获取函数定义支持 pg_get_functiondef 直接拿 DDLroutine_definition 字段在部分版本中不可靠对权限限制的反映相对完整,能看到 ACL只显示你有权限的对象适合场景资产盘点、迁移、审计、排障跨数据库兼容查询、报表统计从这张表可以看出,如果你做的是“查询数据库中所有函数信息”这种硬核任务,首选一定是pg_proc。它才是这个数据库真正的事实来源。3. 实操:从“函数名”到“完整函数定义”的四级跳3.1 第一跳:只拿全部函数名,最简写法先满足标题里的基础诉求:把函数名全部列出来。最直接的方式是:SELECT DISTINCT proname FROM pg_proc WHERE prokind = 'f' AND pronamespace NOT IN ( SELECT oid FROM pg_namespace WHERE nspname IN ('pg_catalog', 'information_schema') ) ORDER BY proname;这条 SQL 做了两件事:用DISTINCT去掉重载导致的重复名,用pronamespace NOT IN过滤系统 schema。为什么用prokind = 'f'?因为标题和多数场景默认只要普通函数。如果你连聚合函数、窗口函数、存储过程也想一起带上,把条件改成prokind IN ('f', 'p', 'a', 'w')或者直接去掉这行。但是“DISTINCT proname”有一个隐性问题:PostgreSQL 支持同名函数重载,只留函数名会丢掉参数签名信息。所以这个版本只适合快速扫一眼,不适合作为正式的函数清单。老版本 PostgreSQL 没有prokind字段,用proisagg和proiswindow两个布尔列也能区分聚合和窗口函数,如果你在维护 10 及以下版本,把prokind换成兼容写法即可。3.2 第二跳:带上 schema、参数签名与返回类型真正能直接进文档、能拿来讨论的函数清单,至少要包含函数所在的 schema、函数名、参数列表和返回类型。推荐用这条:SELECT n.nspname AS schema_name, p.proname AS function_name, pg_get_function_identity_arguments(p.oid) AS args, pg_get_function_result(p.oid) AS return_type, l.lanname AS language, p.prokind AS kind FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace JOIN pg_language l ON l.oid = p.prolang WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT LIKE 'pg_toast%' AND n.nspname NOT LIKE 'pg_temp%' ORDER BY n.nspname, p.proname;重要说明:pg_get_function_identity_arguments拿到的是“标识参数列表”,也就是用于区分重载函数的那部分参数,它不包含OUT参数;而pg_get_function_arguments会返回完整参数列表,包括OUT参数。如果你关心的是函数内部怎么被调用,用identity_arguments;如果你关心的是一个函数完整对外暴露的参数形态,用arguments版本。之所以要带上 schema 和参数签名,是因为单独的函数名在这种场景下没有足够区分度。比如项目里常见public.create_user(text)和audit.create_user(text)两个函数,前者是业务入口,后者是审计辅助函数,名字一样,但是 schema 完全不同。还有更常见的重载情况:calculate(text)和calculate(int, int),它们在同一 schema 下共存,你不可能只说“查一下 calculate”,必须带上签名才能确定到底说的是谁。3.3 第三跳:导出函数完整定义,备份迁移直接用光有函数名和签名还不够,很多场景下你还想看函数体到底写了什么,或者把函数定义直接导出成 DDL 做备份、迁移、版本对比。PostgreSQL 提供了一个很贴心的封装函数:pg_get_functiondef。用法很简单,传入函数 OID,它会返回一段完整的CREATE OR REPLACE FUNCTION语句。比如:SELECT n.nspname AS schema_name, p.proname AS function_name, pg_get_functiondef(p.oid) AS function_definition FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname NOT IN ('pg_catalog', 'information_schema') AND p.prokind = 'f' ORDER BY n.nspname, p.proname;输出结果里每行会有一长串的 CREATE OR REPLACE FUNCTION 文本,这就是该函数当前完整定义。我在做迁移审计时最喜欢用这条,因为它不需要依赖任何备份工具,直接在目标库跑一次,就能把所有函数定义保存成 sql 文件,和源库做 diff。pg_get_functiondef有一个细节要注意:它输出的函数名会自动带上 schema 前缀,比如CREATE OR REPLACE FUNCTION public.foo(...)。这对于迁移来说是好事,直接在目标库执行也能创建到正确 schema;但如果你的脚本还要动态替换 schema 名,反而要小心这个自动生成的前缀。3.4 第四跳:用 psql 元命令快速浏览如果你没有把 SQL 存成脚本的习惯,只想在 psql 里临时看一下,元命令其实是最快的路径。\df:列出所有函数\df public.*:列出 public schema 下的所有函数\df+ foo:查看指定函数详情,包含源码\df默认已经带了 schema、参数、返回类型三列信息,视觉效果比 SQL 输出更紧凑。\df+会额外展示函数体、访问权限、是否安全定义者等信息,排障时非常好用。不过元命令的输出是面向屏幕的,不方便进一步加工。如果后面要写脚本自动化处理,还是要落到pg_proc查询上。我的习惯是:临时确认用\df,系统性盘点用 SQL,两者互补不冲突。4. 常见问题与排查技巧实录4.1 结果里一大堆 pg_catalog 函数,怎么过滤最干净这是新手上路最容易懵的地方。不管你在pg_proc里怎么按名字过滤,系统函数总是混进来,因为pg_catalog里的函数是 PostgreSQL 自带的,数量非常多。要过滤得干净,最保险的是按 schema 维度排除:n.nspname NOT IN ('pg_catalog', 'information_schema')再加两个保险条件:n.nspname NOT LIKE 'pg_toast%' n.nspname NOT LIKE 'pg_temp%'pg_toast和pg_temp这两个 schema 虽然一般不会直接放业务函数,但某些扩展或异常操作可能往里注册对象,顺手排除掉不会有副作用。注意:千万别只靠proname NOT LIKE 'pg_%'这种写法,因为函数名不一定带 pg 前缀,schema 过滤才是正解。4.2 同名函数为什么查出来好几条这几乎是必问的问题。PostgreSQL 的函数重载机制导致同一个函数名可以对应多个不同参数签名的函数。你看到的结果是:calculate calculate(integer, integer) calculate calculate(text)这不代表数据库有问题,而是它自然支持重载。所以凡是测“函数名”是否存在的场景,只比对名字远远不够,必须把pg_get_function_identity_arguments(p.oid)拼上才算完整标识。如果你要用程序逻辑判断某个函数是否已存在,我建议直接查:SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace WHERE n.nspname = 'public' AND p.proname = 'calculate' AND pg_get_function_identity_arguments(p.oid) = 'integer, integer';这样定位唯一签名,才不会有二义性。4.3 函数定义 prosrc 为空是坏了吗查pg_proc时你会发现某些函数行的prosrc是空的,或者看到的是奇怪的符号名。这不一定是数据损坏,先看语言类型:PL/pgSQL 函数:prosrc通常存的就是函数体源码,理应有内容。C 函数:prosrc存的是动态库中的符号名,probin存的是 .so 库文件路径,所以看不到纯文本函数体是正常的。internal 函数:属于 PostgreSQL 内部实现,源码不对外开放,prosrc通常为空。所以遇到prosrc为空,先join pg_language看看lanname是什么。lanname = 'c'就去查probin;lanname = 'internal'就不用管了,那是内核自带的功能。这类函数通常出现在扩展模块里,比如某个第三方索引插件,业务上一般不需要动它。4.4 权限不足,函数定义看不到怎么办普通用户在某些情况下确实可能看不到全部函数定义。information_schema.routines只展示当前用户有权访问的对象,pg_proc中的proacl字段记录了函数的访问控制列表。如果一个用户对该函数没有EXECUTE权限,也不在 ACL 的授予名单里,查询时就会遇到“函数存在但看不到细节”的尴尬。遇到这种情况,先确认当前连接用户是谁,再确认函数属主是谁:SELECT p.proname, pg_get_userbyid(p.proowner) AS owner, p.proacl FROM pg_proc p WHERE p.proname = 'your_function';超级用户不受此类限制,所以生产环境做完整盘点时,尽量用具备足够权限的账号连接数据库。如果你只是普通开发者,但是确实需要审计函数定义,就别硬跟系统权限对抗,找管理员开权限或者提供脱敏后的导出结果才是正路。4.5 查询很慢?不应该慢但确实遇到的场景pg_proc本身数据量通常只有几千到几万行,直接条件查询不可能慢。真正慢的场景往往是你把pg_get_functiondef用在了几百个函数上,或者你在WHERE里对函数名做了ILIKE '%xxx%'却没有走索引,又或者你把pg_proc和多个 catalog 表做了笛卡尔积式的 join。遇到查询变慢,先看执行计划。多数情况下,加上 schema 和 proname 的等值条件就能走pg_proc的常规索引。如果你是做了全文搜索式的模糊匹配,建议先用\df *xxx*在 psql 里快速过滤,再针对结果做精确查询,比盲目优化 SQL 更省事。5. 进阶玩法:把函数清单变成自动化运维工具5.1 建立函数清单快照,对比变更函数清单不只是查一次就完事的,日常开发中函数会改动。你可以把 3.2 节的查询结果存到一张历史表里,每天定时跑一次,再通过EXCEPT或FULL JOIN对比函数名、参数签名和返回值的变化。比如我习惯做一个简单的变更记录表:CREATE TABLE function_snapshot ( snapshot_date date, schema_name text, function_name text, args text, return_type text );每天把查询结果插入这张表,第二天就能对比出哪些函数新增、哪些被删除、哪些签名变了。这个思路在多人协作的项目里特别有用,可以有效避免“明明函数定义变了,大家却不知道”的情况。5.2 多数据库批量盘点如果你管理的是多套环境,比如测试库、预发布库、生产库,函数清单往往需要跨环境比对。最原始的做法是每台库跑一遍 SQL,然后把结果贴到同一个表格里对比。更好的做法是借助 PostgreSQL 的postgres_fdw把多个库连起来,或者直接用 shell 循环调用 psql,把每个库的函数定义 dump 到单独文件,再统一比对。这里我不展开完整脚本,核心思路就是:函数清单一旦生成,先落文件,后做 diff,别每次都靠肉眼盯屏幕。5.3 定位“可疑函数”和“被遗忘函数”函数清单还有一个隐藏价值:帮助发现异常。比如一个业务数据库里突然冒出一个名字很随意的函数,或者某个 schema 下出现了一批不属于任何应用的函数,这些都有可能是历史遗留或潜在风险。你可以把函数清单和pg_depend关联,查看函数与表的依赖关系。如果一个函数依赖的表已经不存在了,那这个函数大概率是被遗忘的僵尸对象。反过来,如果一个表被业务频繁调用,但没有任何函数引用它,那也可能是逻辑设计上的漏洞。这类分析需要结合具体业务去判断,但出发点永远是先把“全部函数信息”准确地捞出来——没有这份清单,后面所有排查都无从谈起。我在实际整理数据库函数清单时踩过不少坑,最值得提醒的就是:不要把DISTINCT proname当成最终版,函数签名和 schema 才是唯一标识。建议把 3.2 节那条 SQL 存成一个小工具脚本,每次接到新项目先跑一遍,五秒钟就能知道这套库的函数资产长什么样。最后再分享一个小技巧:如果你只想快速搜索某个函数名,在 psql 里直接\df *xxx*会比写 SQL 更快,但任何需要保存、对比、上报的场景,老老实实用pg_proc才是正经路子。