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。如果 crm 和 public 下都有同名对象,解析结果就取决于路径顺序。
| 写法 | 风险 | 我更倾向的方式 |
|---|---|---|
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/