GBase 8c 里 search_path 没管住,SQL 可能跑到另一个对象上

GBase 8c 里 search_path 没管住,SQL 可能跑到另一个对象上

我最近复盘 GBase 8c 开发规范时,发现 search_path 是一个很容易被低估的点。很多环境上线初期只有一个业务 schema,大家写 SQL 时表名前面不带 schema 也能跑。等系统拆出多个业务域、多个租户、多个临时 schema 后,同名表、同名函数、同名视图一多,SQL 的解析路径就成了隐患。

这种问题现场不一定表现为报错,更麻烦的是"能跑,但跑错对象"。比如开发环境查的是 public.customer_info,生产环境会话的 search_path 被应用改过,最后查到 crm.customer_info。结果不是语法错,而是数据口径错。

search_path 影响的是对象解析

我自己理解下来,search_path 就是数据库在遇到未带 schema 的对象名时,按什么顺序去找对象。表、视图、函数、类型都可能受影响。

sql 复制代码
SHOW search_path;

SET search_path TO crm, public;

SELECT * FROM customer_info LIMIT 10;

上面这条查询没有写 schema,数据库会按当前路径去解析 customer_info。如果 crmpublic 下都有同名对象,解析结果就取决于路径顺序。

写法 风险 我更倾向的方式
SELECT * FROM customer_info 依赖当前会话路径 核心 SQL 带 schema
SELECT * FROM crm.customer_info 对象明确 推荐用于生产 SQL
函数里直接访问表名 函数执行时路径变化可能影响结果 函数内限定 schema 或设置路径
应用连接后随意 SET search_path 连接池复用时影响下个请求 连接初始化统一处理

开发阶段省几个字符,后期可能换来很高的排查成本。尤其是连接池场景,会话不是请求结束就销毁,路径状态可能被复用。

先查库里有哪些重名对象

遇到"同样 SQL 在不同环境结果不一致",我会先查重名对象。这个动作很简单,但经常能直接定位问题。

sql 复制代码
-- 查不同 schema 下的同名表或视图
SELECT
    relname,
    string_agg(nspname, ', ' ORDER BY nspname) AS schemas,
    count(*) AS object_cnt
FROM (
    SELECT n.nspname, c.relname
    FROM pg_class c
    JOIN pg_namespace n ON n.oid = c.relnamespace
    WHERE c.relkind IN ('r', 'v', 'm')
      AND n.nspname NOT IN ('pg_catalog', 'information_schema')
) t
GROUP BY relname
HAVING count(*) > 1
ORDER BY object_cnt DESC, relname;

函数也要查。函数重载、参数类型、schema 路径叠在一起时,排查会比表复杂。

sql 复制代码
SELECT
    p.proname AS function_name,
    string_agg(n.nspname, ', ' ORDER BY n.nspname) AS schemas,
    count(*) AS function_cnt
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')
GROUP BY p.proname
HAVING count(*) > 1
ORDER BY function_cnt DESC, function_name;

如果结果里出现大量同名对象,就说明环境已经不适合依赖默认路径了。

连接池里最容易留下脏状态

应用使用连接池后,一个物理连接会服务多个请求。某个请求执行了 SET search_path TO tenant_a, public,如果没有恢复,后续请求可能沿用这个路径。问题不一定马上暴露,因为只要对象名存在,SQL 就会正常执行。

我更倾向于在连接初始化时固定路径,并禁止业务代码临时改全局会话状态。如果确实需要租户路径,也要在请求结束时恢复。

sql 复制代码
-- 连接初始化阶段设置
SET search_path TO app_core, public;

-- 或者更保守,生产核心 SQL 全部带 schema
SELECT order_id, order_status
FROM app_core.order_base
WHERE order_id = 10086;

如果应用确实要动态切 schema,至少要把它写成明确的请求上下文,而不是散落在 SQL 片段里。

场景 建议
单业务 schema 可以设置固定 search_path,但核心 SQL 仍建议显式 schema
多租户按 schema 隔离 连接获取后设置,释放前重置
存储过程内部访问业务表 函数内写完整对象名
报表平台拼 SQL 禁止无 schema 的跨域对象访问
临时排查会话 执行前先 SHOW search_path

函数里的路径更要谨慎

函数和存储过程里如果使用未限定表名,后续迁移 schema、同名对象增加、调用方路径变化,都可能让行为变得难以预期。我的习惯是在函数里把核心对象写完整。

sql 复制代码
CREATE OR REPLACE FUNCTION app_core.get_customer_level(p_customer_id bigint)
RETURNS varchar
AS $$
DECLARE
    v_level varchar(32);
BEGIN
    SELECT customer_level
      INTO v_level
      FROM app_core.customer_info
     WHERE customer_id = p_customer_id;

    RETURN v_level;
END;
$$ LANGUAGE plpgsql;

如果函数必须依赖特定路径,也要在创建和评审时明确写出来,不要让它隐含在数据库默认值里。

权限隔离不能只靠 schema 名字

有些系统把 schema 当成天然隔离边界,但如果权限给得太宽,用户仍然可以访问不该访问的对象。对象路径和权限是两件事:路径决定先找谁,权限决定能不能用。

sql 复制代码
-- 示例:给应用用户授予指定 schema 的使用权
GRANT USAGE ON SCHEMA app_core TO app_user;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA app_core TO app_user;

-- 限制 public 下随意创建对象
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

我在现场更关注 public schema。很多库默认对象都堆在 public,下游人员也习惯把临时表、测试函数放进去。时间一长,public 就从公共空间变成了风险集合。

上线前可以加一个扫描脚本

为了减少对象解析风险,我会在上线检查里加几类扫描。

sql 复制代码
-- 检查业务 SQL 中可能遗漏 schema 的高风险对象,示例只查重名表
SELECT relname
FROM (
    SELECT c.relname, count(*) AS cnt
    FROM pg_class c
    JOIN pg_namespace n ON n.oid = c.relnamespace
    WHERE c.relkind IN ('r','v','m')
      AND n.nspname NOT IN ('pg_catalog','information_schema')
    GROUP BY c.relname
) x
WHERE cnt > 1;

-- 检查当前会话路径
SHOW search_path;

如果有代码仓库,可以配合静态扫描,把 FROM 表名JOIN 表名 这类未带 schema 的 SQL 片段标出来。不是所有 SQL 都要立刻改,但核心链路和跨 schema 查询要优先处理。

我会采用的命名和使用规范

规范 说明
业务 schema 不用 public 承载核心表 public 只保留必要公共对象
生产 SQL 优先写完整对象名 降低路径依赖
不同业务域避免同名核心表 即使 schema 不同,也会增加误读
函数内部访问表要带 schema 避免调用方路径影响
连接池初始化统一设置路径 不让请求自己决定默认路径
临时对象命名带前缀和批次 防止和业务对象混淆

GBase 8c 的 schema 机制本身很灵活,适合做对象分层、权限隔离、业务域拆分。但灵活性也意味着需要规范。我的感受是,search_path 管得越早,后期迁移、拆库、权限改造时越轻松。

参考资料

text 复制代码
GBase 8c SQL参考指南 https://www.gbase.cn/docs/gbase-8c/05%20SQL%E5%8F%82%E8%80%83/SQL%E8%AF%AD%E6%B3%95
GBase 8c 开发者指南 系统模式 https://www.gbase.cn/docs/gbase-8c/03%20%E5%BC%80%E5%8F%91%E8%80%85%E6%8C%87%E5%8D%97/%E7%B3%BB%E7%BB%9F%E6%A8%A1%E5%BC%8F
GBase 8c 文档介绍 https://www.gbase.cn/docs/gbase-8c/%E6%AC%A2%E8%BF%8E/
相关推荐
升鲜宝供应链及收银系统源代码服务2 小时前
升鲜宝云商品库功能设计与数据库表结构详细文档(一)---升鲜宝生鲜配送供应链管理系统源代码服务
数据库·生鲜配送源代码·供应链源代码·生鲜供应链源代码·升鲜宝供应链管理系统源代码·b2b客户订货源代码
2301_783848652 小时前
如何用 IDBKeyRange 范围匹配检索特定区间的本地数据
jvm·数据库·python
解决问题no解决代码问题2 小时前
【无标题】
数据库
倒流时光三十年2 小时前
PostgreSQL 中的 NULL 陷阱:从一次排除过滤说起
java·数据库·postgresql
weixin_444012932 小时前
SQL处理大规模分组聚合的内存限制_调整服务器配置.txt
jvm·数据库·python
接着奏乐接着舞2 小时前
redis 知识点(java)
数据库·mysql
2401_867623982 小时前
SQL如何提取分组中的第一条记录_使用ROW_NUMBER定位数据
jvm·数据库·python
lifewange2 小时前
Hive 数据库 增删改 完整操作指南
数据库·hive·hadoop
Mike117.2 小时前
GBase 8c 写入高峰抖一下,我通常会先看检查点和 WAL
数据库