内容提要
PostgreSQL 17 中,自定义设置(如 app.tenant)未设置时 current_setting 报错或返回 NULL;一旦设置过再重置则返回空字符串,导致类型转换失败。事务局部值仅持续到隐式事务结束。任何角色均可设置自定义参数,GRANT SET ON PARAMETER 仅控制默认值。建议用 nullif 包裹并传入空字符串。
延伸解读
两种“未设置”状态:NULL 与空字符串
文章实测显示,自定义设置从未被命名时,current_setting 报错或返回 NULL;一旦被 SET、RESET 或 set_config 命名过(即使回滚),之后读取都返回空字符串。这导致直接类型转换失败,而 NULL 转换成功。连接池中 DISCARD ALL 后连接常处于空字符串状态,因此策略可能在新连接上正常,在复用连接上报错。
事务局部值的实际生命周期
SET LOCAL 或 set_config(..., true) 在事务内有效,提交或回滚后失效。但若不在显式事务中,它只持续到隐式事务结束,通常是一条语句。若多条语句作为单个字符串发送,它们共享同一隐式事务,值会持续到该字符串执行完毕。会话级值不会被事务局部值清除,只是被暂时覆盖,事务结束后恢复。
默认值如何改变“未设置”语义
通过 ALTER DATABASE 或 ALTER ROLE 设置默认值后,新会话中该参数直接以默认值存在,不再有 NULL 状态。若默认值为空字符串,则所有新连接一开始就处于空字符串状态,直接转换会立即失败,便于早期发现问题。若默认值非空,则所有未显式设置租户的路径都会使用该值,可能造成数据泄露。
权限与安全:自定义设置不是凭证
任何能执行 SQL 的角色都可以设置自定义参数,GRANT SET ON PARAMETER 仅控制 ALTER ROLE/DATABASE 或 CREATE FUNCTION 中的默认值设置,不阻止会话级 SET。因此,依赖 current_setting 做行级安全时,必须意识到应用层可随意伪造该值,不能将其视为身份凭证。
Q&A
PostgreSQL 中 current_setting 在自定义设置从未被设置过时会返回什么?
如果该设置从未被设置过,current_setting('app.tenant') 会报错 unrecognized configuration parameter "app.tenant";而 current_setting('app.tenant', true) 会返回 NULL。
为什么在 PostgreSQL 中 current_setting 有时返回空字符串而不是 NULL?
一旦该设置名称被 SET、RESET 或 set_config 设置过(即使事务回滚),之后 current_setting 就会返回空字符串而不是 NULL。这是两种不同的“未设置”状态。
在 PostgreSQL 中,事务局部设置(set_config 的第三个参数为 true)在事务外能持续多久?
在事务外,事务局部值仅持续到隐式事务结束,通常就是当前语句。如果多条语句作为一个字符串发送,则它们共享同一个隐式事务,值会持续到该字符串执行完毕。
如何为自定义设置设置默认值?默认值会如何影响“未设置”状态?
可以在数据库或角色上设置默认值,例如 ALTER DATABASE app_db SET app.tenant = ''; 或 ALTER ROLE ... SET app.tenant = '2'。设置后,新会话中该设置直接返回默认值(如空字符串或指定值),不再有 NULL 状态。
在 PostgreSQL 中,谁可以设置自定义设置?GRANT SET ON PARAMETER 有什么作用?
任何能执行 SQL 的角色都可以设置自定义设置,即使没有 GRANT SET ON PARAMETER 权限。该权限仅控制是否允许通过 ALTER ROLE ... SET 或 CREATE FUNCTION ... SET 使设置持久化。
在行级安全策略中,如何避免因空字符串导致的类型转换错误?
使用 nullif(current_setting('app.tenant', true), '')::int 将空字符串转为 NULL,这样两种“未设置”状态都会返回空结果集,而不会报错。