上个月我处理了一个现场案例:业务方要对一套 GBase 8s 测试环境的账号做规范化改造,把原来的 bi_old 统一改成 bi_new。改完用户、改完应用连接串之后,所有人以为事情已经结束了。结果应用一启动,普通查询正常,视图也能看,唯独业务里只要调用存储过程就报错,后端日志不断抛出 routine not found。更让人疑惑的是,用旧账号连接数据库,同一个存储过程又能正常执行。
这种“换用户后存储过程失联”的现象,在 GBase 数据库的运维里其实不算罕见。很多 DBA 的第一反应是权限没给够,然后反复执行 GRANT,最后发现毫无用处。GBase 数据库的用户名修改,从来不是改一个登录名那么简单。用户在这个产品体系里不只是“能连库的账号”,它同时是数据库对象的所有者、存储过程的属主标识、权限判断的基准。存储过程一旦建立,它和用户名之间就绑定了好几条隐性的链,改用户名时漏掉任何一条,都会让过程变成“能查到、却调不动”的僵尸对象。
这篇文章我按实际排查的顺序,把完整的处置方案整理出来。里面有诊断 SQL、批量替换思路、重建过程的方法,也有我踩完坑之后总结出的检查清单。无论你用的是 GBase 8s、8a 还是 8c,排查思路都通用,只是具体系统表和命令名需要替换。
1. 现场还原:改完用户名,存储过程为什么会集体失联
1.1 两条最典型报错,先分清是哪一类
我遇到的最常见的报错大概分两类,这两类的处置方向完全不同,所以拿到报错先别急着查权限。
第一类是“过程根本找不到”:应用里写的调用语句是 EXECUTE PROCEDURE new_bi_user.sp_query_balance(...),然后数据库返回 Procedure not found 或 routine not found。这种情况通常是过程对象的 Owner 信息或授权链仍然挂在旧用户名上,新用户看不到这个对象。
第二类是“过程能找到但执行报错”:调用 EXECUTE PROCEDURE sp_query_balance(...) 时能找到过程,但过程体一执行就报 Table ... does not exist 或者 Illegal reference to table ...。这类情况更隐蔽——过程本身没丢,但过程体里引用的表、视图、同义词仍然被解析到旧用户名下。
判断到底属于哪一类,有一个很笨但很有效的办法:直接在数据库客户端里用新账号尝试单独执行过程。如果连解释阶段都过不去,是第一类;如果解释能过、一跑就报表不存在,是第二类。GBase 的命令行工具里,可以先执行 SET EXPLAIN ON 再看执行计划,通常能定位到具体卡住的步骤。
1.2 三条隐性依赖链,任何一条断了都会出问题
为什么修改用户名会影响存储过程?因为存储过程和用户名之间不是单点绑定,而是至少存在三层依赖。
第一层是对象 Owner 关系。GBase 8s 继承了 Informix 的存储模型,用户创建的表、视图、过程、触发器,在系统目录 sysprocedures、systables 等里都有 Owner 字段。改名操作如果只是改了用户登录名,或者新建了一个用户,那么旧用户名存在的对象并不会自动过户到新用户头上。这一点和 MySQL 里 RENAME USER 不会自动把所有对象的 Definer 改掉很像,也和 Oracle 里改了用户之后对象仍在原 Schema 下类似。
第二层是存储过程体内部的静态 SQL 引用。很多老系统的存储过程,写的时候用的是无属主限定的表名,比如:
sql复制CREATE PROCEDURE sp_query_balance(userId INTEGER)
SELECT user_balance FROM t_balance WHERE user_id = userId;
END PROCEDURE;
过程创建时,这个 t_balance 会被数据库解析成 bi_old.t_balance,也默认记录在该过程的属主上下文里。当调用者从 bi_old 变成 bi_new,数据库重新匹配过程体里的表名时,就会尝试按新用户去解析,如果新用户没有同名的表,自然报“表不存在”。更麻烦的是,如果过程体里直接写了 bi_old.t_balance 这种全限定名,那么旧用户名一旦被删,报错会更直接:整个对象直接消失。
第三层是授权链。存储过程的 EXECUTE 权限记录在系统表里,实际操作中表现为“旧用户有权限、新用户没权限”。很多存储过程不是给 Owner 自己用的,而是给业务账号调用,Owner 对过程有没有权限是一回事,调用账号是否持有 EXECUTE 授权是另一回事。改用户名后如果 GRANT EXECUTE ON ... TO new_bi_user 没有重新执行,应用层永远拿不到过程入口。
打个比方:一个公司换了新门禁卡系统,但储物柜、文件柜、会议室门锁里登记的还是老员工的工号。员工以为换了卡就万事大吉,结果开哪扇门都打不开。存储过程就是这个“工号”绑定最深的门锁。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 把诊断前置:先定位旧用户名在两份清单里的残留
2.1 一份可用的系统目录查询清单
处理这个问题的第一步,不是急着改过程,而是先把旧用户名在元数据里的残留全部捞出来。GBase 8s 里,存储过程相关的核心系统表有三个:sysprocedures(记录过程名、Owner、返回类型等概要信息),sysprocbody(记录过程体文本和具体语句),sysprocauth(记录过程授权关系)。对应地,我建议按下面几个维度去查。
查旧用户名名下到底有哪些存储过程:
sql复制SELECT procname,
owner,
mode
FROM sysprocedures
WHERE owner = 'bi_old';
查所有过程体里写了旧用户名的文本记录:
sql复制SELECT p.procname,
b.datakey,
b.seqno,
b.data
FROM sysprocbody b,
sysprocedures p
WHERE b.procid = p.procid
AND b.data LIKE '%bi_old%';
注意 sysprocbody 里的 data 字段存有过程体片段,因为一个过程会拆成多行存储,所以 LIKE 匹配可能返回重复过程,这只是用来辅助定位要处理的范围。真正要精确看某个过程内容时,可以用工具把过程 DDL 导出来。
查权限表里哪些授权还指向旧用户名:
sql复制SELECT a.grantee,
a.procid,
p.procname
FROM sysprocauth a,
sysprocedures p
WHERE a.procid = p.procid
AND (a.grantee = 'bi_old'
OR p.owner = 'bi_old');
如果巡检结果只有权限记录,没有过程体里的静态引用,那处理起来相对轻松,重新授权就能解决。如果第二条 SQL 查出大量过程体引用,那就必须进入“导出-替换-重建”的流程。这个区分决定了工作量级,值得在动手前花五分钟查清楚。
2.2 排查“应用看到的存储过程”和“系统表里的存储过程”不一致问题
实际运维里还有一种很容易走弯路的情况:数据库里明明有过程,但应用报“找不到”。这时需要用当前登录用户身份去确认对象可见性。GBase 8s 里可以执行:
sql复制SELECT CURRENT_USER FROM systables;
先确认你连进来的用户名到底是谁。随后再以对象完整限定名访问:
sql复制EXECUTE PROCEDURE bi_new.sp_query_balance(1001);
如果这样能执行,说明过程还在,只是应用连接时解析到的默认用户不对。这时候问题可能在连接串里配置的 account 指向旧用户,而不是数据库对象本身。我曾经见过一个项目,数据库里过程、权限全都改好了,最后发现应用服务器上某个配置文件里多了一个旧的连接池初始化脚本,每次服务重启都会按旧用户建立 session,导致所有新的授权全部“失效”。这个坑不值得专业技术团队踩第二次。
2.3 不同 GBase 版本的目录表名不完全一样
GBase 8s、8a、8c 虽然是同一家公司的产品,但技术底座差异很大。上面给的 SQL 主要适用于 GBase 8s(Typical 的 Informix 衍生架构)。如果跑在 GBase 8a(分析型 MPP 集群)上,通常可以查 information_schema.routines 和 information_schema.routine_privileges;如果是 GBase 8c(基于 PG 系内核的版本),则对应 pg_proc、pg_namespace、information_schema.routine_privileges。差异如下表:
| 项目 | GBase 8s | GBase 8a | GBase 8c |
|---|---|---|---|
| 用户/角色体系 | 用户即属主 | 用户即属主,分布式 | 用户/Schema 分离 |
| 存储过程目录表 | sysprocedures | information_schema.routines | 系统内置 |
| 过程体碎文本表 | sysprocbody | 不直接开放 | pg_proc.prosrc |
| 权限表 | sysprocauth | 独立权限元数据 | 依赖 ACL |
如果你不确定当前环境的版本,先跑到 SELECT * FROM systables WHERE tabname = 'sysprocedures' 这类查询看看表是否存在。别把 8s 的 SQL 硬塞到 8a 上执行,我在测试环境就因为这个操作浪费过半小时。
3. 处置主链路:让存量过程平滑切换到新用户名的四个动作
3.1 决定改用户之前,先理清“用户名”到底改成谁
标题里说的“用户名修改”,在实际项目中有两种完全不同路径:
第一种是执行了数据库内置的改名能力(例如 GBase 8c 侧的 ALTER USER bi_old RENAME TO bi_new,或者更常见的做法是新建了 bi_new 用户)。第二种是把旧用户保留,为业务创建新的同名替代账号并停用旧账号。无论哪一种,作为 DBA 必须想清楚:你希望对象的 Owner 保持 bi_old,还是迁移到 bi_new?如果是前者,存储过程本身不用动,只要把执行权限授权给新的调用方即可;如果是后者,就需要把对象 Owner 和过程体引用一并进行侧迁移。
实际生产项目里,账号规范化改造绝大多数要求“库内对象归到新业务账号名下”,因为责任边界要清爽,默认属主必须是一致的。所以下面的处置动作都按 Owner 迁移的假设来说明。如果你的场景只是多了一个调用账号,跳过 3.2 和 3.3,重点做 3.4 的授权检查就可以。
3.2 导出存储过程定义,批量替换旧属主引用
当前没有哪条 SQL 能直接“改掉过程体里所有旧用户名”然后编译保存,至少我熟知的 GBase 8s 版本不支持 ALTER PROCEDURE ... OWNER TO ... 这种语法。可行路线是:导出过程 DDL,做文本替换,再重建入库。
导出 DDL 的姿势:
bash复制dbaccess your_database - <<'EOF'
SELECT "unload to /tmp/sp_bak.sql delimiter '\\n'"
FROM systables WHERE tabname = 'sysprocedures';
EOF
但上面这段只是示意,意义不大。真正完整的 DDL 需要结合 dbschema 工具来做,这是 Informix 时代留下的经典工具,GBase 8s 依然沿用:
bash复制dbschema -d your_database -f sp_query_balance -u bi_old -o /tmp/sp_bak.sql
如果存储过程有几十上百个,可以用脚本批量导出,把过程名列表动态传入 dbschema。关键点在于导出的 SQL 文件里通常会有两种引用需要替换:一种是过程定义头部的 CREATE PROCEDURE bi_old.sp_xxx,一种是过程体里对表或视图的显式属主前缀。替换的时候要小心,不要用简单的 sed 's/bi_old/bi_new/g' 把字符串常量或者注释里的内容也换掉。稳妥一点的做法是分两次替换:
bash复制# 只替换 CREATE PROCEDURE 以后的属主声明
sed -i '/^create procedure/i s/bi_old\./bi_new./g' /tmp/sp_bak.sql
实际操作里我会前后兼顾,用 \bbi_old\b 做单词边界匹配,替代后再用 grep 检查是否还有可疑残留:
bash复制sed -i 's/\bbi_old\b/bi_new/g' /tmp/sp_bak.sql
grep -n 'bi_old' /tmp/sp_bak.sql || echo "no residue"
替换后需要人工抽查几个代表性过程,确认没有误伤过程体里注释中的旧用户名。
3.3 重建过程:先删后建,但删的顺序需要点讲究
经典的 DDL 替换完成后,把过程在新用户名下重建。可以把它当成一次纯手工发布:
bash复制dbaccess your_database /tmp/sp_bak.sql
执行前需要确认一个关键问题:旧的存储过程是保留还是删除?如果直接删掉旧属主下的过程,而某些访问路径还在旧用户名上,很容易造成应用侧短暂报错;如果保留旧过程,又可能造成新旧同名过程同时存在,调度时不知道解析到哪个。稳妥做法是:先把新过程全部创建好,验证可执行,然后再根据发布窗口删除旧过程。前提是旧用户 bi_old 还没被删除,否则你就只能一次性重建,毕竟旧用户没了,所有依赖旧属主的过程都无法解析。
还有一点必须注意:重建后一定要检查过程的状态是否完整。GBase 8s 里过程的编译信息分散在 sysprocbody 中,执行创建脚本时如果中途报错,可能出现只有头没有体的残缺过程。我用过下面的 SQL 做完整性检查:
sql复制SELECT p.procname,
(SELECT COUNT(*) FROM sysprocbody b WHERE b.procid = p.procid) AS body_parts
FROM sysprocedures p
WHERE p.owner = 'bi_new';
如果 body_parts 为 0,说明过程只有目录记录没有过程体,执行时必挂。出现这种情况可以把对应过程删掉再单独跑一遍 DDL,不要贪图省事。
3.4 GRANT 执行权限,同时检查 sysprocauth
过程重建完成后,不要以为万事大吉。Owner 有了新过程,但这只是让 bi_new 自己能调用。业务账号如果还是旧的角色体系,必须重新授权。在 GBase 8s 里执行:
sql复制GRANT EXECUTE ON PROCEDURE sp_query_balance TO PUBLIC;
或者授权给指定角色:
sql复制GRANT EXECUTE ON PROCEDURE sp_query_balance TO biz_role;
如果你用的 GBase 版本支持按 Schema 批处理授权,也可以尝试分组授权。这里我想强调一个易错点:修正授权之前,先确认授权的调用方账号确实存在于系统用户列表中。如果应用连接串指向的是 bi_new,但这个用户在数据库里并不存在,那 GRANT 语句会报错,而且之后你做任何排查都会觉得自己改了个寂寞。
执行完授权后,再次查 sysprocauth:
sql复制SELECT p.procname,
a.grantee
FROM sysprocauth a,
sysprocedures p
WHERE a.procid = p.procid
AND p.owner = 'bi_new';
如果授权记录齐全,接入下一步的链路验证。
4. 批量落地与回滚:自动化之前先想清楚逃逸路
4.1 存储过程多到上百个时,手工不够用
一个存储过程两个存储过程可以手工导出替换,但如果系统里有上百个过程,手工操作就非常容易漏。我在处理一个报表库时遇到过 187 个存储过程,其中近 60 个的过程体里显式写了旧用户名。手工处理绝对不现实,必须写脚本。这里给出一个 bash 方案的骨架,核心思路就是三步:连库拿过程名清单、逐过程 dbschema 导出、sed 替换后重新导入。
bash复制#!/bin/bash
# 需要自行替换为自己的实际库名和账号
DB_NAME="analytics_db"
OLD_USER="bi_old"
NEW_USER="bi_new"
WORK_DIR="/tmp/gbase_sp_migration"
mkdir -p "$WORK_DIR"
# 第一步:从 sysprocedures 拿属主为旧用户的过程名
dbaccess "$DB_NAME" - <<EOF > "$WORK_DIR/proc_list.txt"
SELECT procname FROM sysprocedures WHERE owner = '$OLD_USER';
EOF
# 第二步:逐条导出、替换、重建
cat "$WORK_DIR/proc_list.txt" | grep -v '^$' | while read proc
do
echo "=== migrating: $proc ==="
dbschema -d "$DB_NAME" -f "$proc" -u "$OLD_USER" -o "$WORK_DIR/${proc}.sql"
sed -i 's/\bbi_old\b/bi_new/g' "$WORK_DIR/${proc}.sql"
dbaccess "$DB_NAME" "$WORK_DIR/${proc}.sql"
if [ $? -ne 0 ]; then
echo "ERROR on $proc, check $WORK_DIR/${proc}.sql"
exit 1
fi
done
echo "done"
这一步有三个容易翻车的细节。第一,导出的 SQL 文件里可能包含 CREATE PROCEDURE bi_old.sp_xxx ... END PROCEDURE 之外的 GRANT 语句,而 dbschema 默认不会帮你把所有 GRANT 一起导出,所以重建后大量授权还依赖旧用户,需要用前面的 sysprocauth 清单一并补救。第二,dbaccess 执行脚本遇到第一个报错可能不会中断,所以要在执行后主动查返回值,或者干脆用 dbaccess - 逐条执行并设置严格模式。第三,替换过程中如果遇到 bi_old 同时也是某个业务表数据列名的一部分或者注释内容,简单 sed 会把代码改花。因此建议在小范围试跑对比后再铺开。
4.2 一个安全网:改名前先为旧属主做一个完整快照
任何批量操作都必须有回滚方案。存储过程迁移尤其如此,因为过程体重建是一次破坏性操作,一旦替换脚本有瑕疵,可能影响正常业务。我的做法是,在所有 DDL 导入前,先用 dbschema 把旧用户下所有对象完整导出到备份目录,文件名带时间戳:
bash复制dbschema -d "$DB_NAME" -u "$OLD_USER" -o "$WORK_DIR/backup_${OLD_USER}_$(date +%Y%m%d%H%M%S).sql"
这个小步骤通常会多花两分钟,但在出事时能救命。数据库本身没有“撤销上一条 DDL”的能力,手工回滚重建是很痛苦的,而备份文件可以让你直接恢复旧属主下的原版过程。
另外,有一个更稳妥的执行顺序建议:过程迁移尽量放到业务低峰期执行。虽然存储过程的 DDL 操作理论上可以只锁当前对象,但如果过程体依赖了大量表,重建时可能需要长时间获取共享锁,对在线查询的影响不可小觑。
4.3 新用户验证过程:列出完整冒烟清单
一次成功的迁移不是以脚本执行成功为终点的,而是以业务链路完整跑通为终点。我在项目里会做三类验证。第一类是目录验证,查询 sysprocedures 确认所有目标过程都已经归属 bi_new;第二类是权限验证,用业务账号执行 SELECT * FROM sysprocauth 或者直接通过 JDBC 连接执行一个最简单的无参过程;第三类是真实调用验证,取应用日志里几个高频存储过程逐一在新用户下执行,对比返回值是否符合预期。如果过程有输出参数,可以在你的开发框架里写一个最小调用用例,按线上参数跑一遍。有些时候还要顺带验证返回结果集的列名和元数据是否变化,因为重建后的过程如果依赖新的表属主,而那个表在新建用户下也有一个同名但结构不同的版本,就可能出现列错位。
类似这类异常我已经见过多次,所以特别提醒:过程重建之后,调用端不只要看能否执行成功,还要关心返回结构和之前是否完全一致。这两个用户下同名表结构不一致的概率比你想象得高。
5. 收尾检查:改用户名后容易“认老东家”的其他对象
5.1 视图、触发器、函数、同义词一个也别漏
如果只处理存储过程,我敢肯定项目上线后还会有别的环节报错,因为“用户名修改”影响的远不止过程。视图和物化视图的 Owner 同样挂在旧用户名下,查询时不带限定符就会让数据库按新用户解析基础表。触发器的 TRIGGERED ACTION 内部可能引用 bi_old.xxx,删除旧用户后触发器执行直接报错。函数和存储过程同理,尤其是返回表类型或者嵌套调用其他过程的函数,依赖链更深。
查询依赖的范围更全面,可以用 sysdepend 表把关联关系拉出来:
sql复制SELECT d.depender_type,
d.depender_name,
d.dependon_type,
d.dependon_name
FROM sysdepend d
WHERE d.dependon_name LIKE 'bi_old.%'
OR d.depender_name LIKE 'bi_old.%';
这段 SQL 在 GBase 8s 中能看到依赖方和被依赖方,比如哪个视图依赖了旧用户下的表,哪个过程依赖了旧用户函数。8a 环境下 sysdepend 不一定存在,通常需要遍历 information_schema 中的视图定义做相似判断;8c 环境则可以用 pg_depend。
5.2 定时任务与外部脚本里的硬编码最容易藏污纳垢
改用户名改到最后一刻,往往会发现应用连接串都改了,数据库对象也迁移完了,却仍有自动任务在报错。这些任务不一定存在数据库内部,可能存在于应用服务器 crontab、调度平台 DAG,或者 GBase 8c 自带的 job 表里。它们的共同特点是:里面配置了数据库连接账号,且不是通过统一配置中心管理,直接写死了旧用户名。这种问题很难靠数据库侧发现,只能通过全库搜索或者调度平台全局替换。最有效的提前量是改名前先做一轮资产盘点,把可能引用旧账号的配置源列出来,逐项更新。
如果你暂时没有合适的配置管理工具,建议在迁移结束后至少盯一个完整的任务执行周期,看有没有潜伏任务在凌晨触发时才暴露问题。数据库侧也可以查一下是否有定时作业存储在系统表中,GBase 8s 里对应的往往是外部调度器或者任务表;8c 里则看 pg_job。
5.3 别忘了连接池和会话缓存里的旧身份
最后一个隐蔽点:数据库用户改名后,应用侧修改了配置文件,但连接池里可能还保留着旧账号建立的长连接,或者某些框架会缓存初始认证信息。这类问题通常表现为“刚改完时新的连接都正常,一旦服务重启或连接池回收后反而报错”,其实是新配置没有真正生效。我的排查习惯是,服务起来后先执行一下查看当前会话用户的 SQL,比如在 GBase 8s 上执行:
sql复制SELECT CURRENT_USER FROM systables;
如果返回的仍是旧用户名,说明连接池仍然用旧账号在创建连接,需要重启应用或刷新连接池。这个问题虽然不是数据库侧的对象迁移问题,但在“用户名修改无法识别存储过程”的真实案例里,它往往和三、四个问题叠加在一起出现,比单点故障更迷惑人。
说到最后,这次改造对我最深的触动是:数据库的“用户”远不止是一串登录凭证,它是一大堆对象的所有权锚点。给用户名做变更,本质上是一次所有权变更。先想清楚哪些东西归它所有、它被谁依赖,再决定执行方式和顺序,比任何一条“万能 SQL”都重要。如果你今天也在处理 GBase 的用户名迁移,建议先从 sysprocedures 和 sysprocbody 查起,把残留清单拉完整再动手。遇到成千上百个对象要改的时候,也别忘了先留下一个可回退的完整备份。数据库对象迁移这种事,稳妥永远比炫技值钱。
