干了这么多年数据库迁移,最怕的不是数据搬不完,而是数据搬完了应用起不来。前两天刚把一个跑在Docker容器里的PostgreSQL 14业务库迁到达梦8(DM8)上,数据校验全部通过,结果应用一启动就报“无效的模式名[public]”,当时第一反应是数据没迁干净,查了半天发现表都在,就是跨模式查询全部失效。这个坑在PG转DM8的迁移里相当典型,今天把排查过程和解决办法完整写出来,给正在做同类迁移的朋友做个参考。
先说下环境:源库是PostgreSQL 14,部署在Docker容器里,业务上有多个schema(模式),比如public、app、log,应用通过search_path来跨模式访问表;目标库是DM8,部署在统信UOS服务器上,用DM数据迁移工具做的全量迁移。迁移完成后,单模式内的查询都正常,一旦SQL里有跨模式访问(比如SELECT * FROM app.users),就报“无效的模式名[app]”。这个问题适合正在做PG迁DM、或者准备做国产化数据库替换的运维和开发同学看看,踩过的坑能帮你省一个下午。
1. 问题现场还原
1.1 报错信息与环境细节
先用一句话描述这个报错的样子:在DM8中执行带模式名的SQL时,数据库直接返回无效的模式名[xxx],其中xxx就是SQL里写的schema名称。比如在PG里写SELECT * FROM app.users没问题,同样的SQL到DM8就报错。
当时我验证了一下,发现一个很有意思的现象:用SYSDBA账号登录,执行SELECT * FROM app.users也报同样的错;但执行SELECT * FROM users却能查到数据。这就有意思了,说明不是权限问题,而是模式app本身在DM8里根本不存在,或者没被迁移工具正确创建。
我确认了迁移工具的配置,源库连接的是PG容器,目标库连接的是UOS上的DM8,选的是“按模式迁移”的方式。按理说模式应该一起迁过来,但事实是,迁移工具把PG的多个schema合并成了一个,或者把表全部放到了SYSDBA这个默认模式下,源库的schema结构被拍平了。
1.2 单模式正常,跨模式集体翻车
为了确认影响范围,我把所有业务SQL梳理了一遍,发现受影响的不只是app.users这一条,而是所有显式带模式前缀的SQL全部报错。同时,存储过程/函数里如果引用了带模式名的表,调用也会在运行时抛出同样的“无效模式”。
这个现象很有迷惑性。因为大部分应用在PG里虽然用的是search_path隐式解析,但一部分历史SQL为了保险,都会写成schema.table这种显式格式。迁移后,单模式查询靠DM8当前用户的默认模式还能蒙混过关,一旦遇到显式带模式前缀的,就立刻暴露问题。我后来在DM管理工具里执行了下面这条SQL,确认了模式列表的情况:
sql复制-- 查看DM8当前实例下所有用户/模式
SELECT USERNAME FROM ALL_USERS;
结果整个实例下只有SYSDBA、SYSAUDITOR和几个系统模式,我们源库里的app、log、public一个都没有。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 根因分析:PG的schema机制和DM8的差异
2.1 模式(Schema)在两个数据库里的地位不同
要理解为什么PG迁到DM8会出现“模式丢失”,关键要搞明白两个数据库对schema的处理方式完全不同。
在PostgreSQL里,schema就是一个纯粹的命名空间,用来组织表和对象。数据库连接后通过search_path参数来决定“不带模式前缀的SQL到底去哪找表”。用户可以自由创建多个schema,一个用户访问多个schema是很常规的操作。它的逻辑可以类比成一个文件柜里有多个抽屉,每个抽屉装了不同的文件,你手里拿着哪把钥匙(search_path)决定你先开哪个抽屉。
而在DM8里,模式跟用户是深度绑定的。每个用户默认有一个与用户名同名的模式,用户登录后,默认就是用自己的同名模式。虽然DM8也支持一个用户访问其他模式(前提是有权限),但SQL解析规则更接近Oracle——不带模式前缀时,默认只找当前用户自己的模式。所以迁移工具如果只是把PG的表搬到DM8的SYSDBA模式下,那么应用账号登录后,默认模式是应用账号自己,自然就找不到那些表,更别说带上app.前缀去访问了。
2.2 迁移工具默认做了“拍平处理”
DM数据迁移工具(DTS)在迁移PG数据时,默认的映射策略是按“库名.模式名.表名”的层级来处理的。但实际操作中,很多版本的工具在处理PG的schema时,不会自动在DM8里创建同名模式,而是把所有表都扔到目标用户对应的默认模式下。
这就像你把一个带子文件夹的目录拷贝到新硬盘,新硬盘的目录结构被自动压平了,所有的文件都堆在根目录。数据内容没变,但目录结构变了,依赖目录结构的路由就全部失效。
从PG 14容器的实际表现来看,源库有多个schema,迁移到DM8后表都在SYSDBA模式下,访问方式从原来的app.users变成了SYSDBA.users。应用层的SQL还是按老写法来,数据库自然就报“无效模式”。
2.3 大小写和引号的问题叠加
PG里如果建表时用了双引号,比如CREATE TABLE "App"."Users" (...),那么模式和表名的大小写会被严格保留。而DM8默认会转为大写存储,除非对象名也用了双引号。迁移工具处理这种情况时很容易把大小写搞乱,导致模式名看起来存在,但实际查询怎么都不对。
我们这次迁移没遇到大小写问题,因为源库的表名都是小写,没有用双引号,所以排查起来还能聚焦在“模式不存在”这个核心原因上。如果你的源库有驼峰命名,那排查难度会更大,因为你看列表时可能看到一个“长得像”但实际大小写不同的模式名,一执行还是报错。
3. 排查思路与定位方法
3.1 三步定位法
遇到跨模式报错,别急着改代码,先用三个步骤把情况摸清楚。
第一步,查模式清单。上面已经说了,用SELECT USERNAME FROM ALL_USERS或SELECT DISTINCT OWNER FROM ALL_TABLES都可以。如果模式列表里没有你要访问的名字,那问题就在迁移阶段,模式没被创建。
第二步,确认表实际的归属。用这条SQL可以快速找到某张表在哪个模式下:
sql复制-- 根据表名模糊查找实际的owner(即模式)
SELECT OWNER, TABLE_NAME FROM ALL_TABLES WHERE TABLE_NAME LIKE 'USERS%';
我这边查到结果全是SYSDBA.USERS,立刻就知道根因了。
第三步,验证用SYSDBA访问是否正常。如果SELECT * FROM SYSDBA.USERS能查到数据,说明表本身没问题,纯粹是模式名映射错位。
3.2 从应用侧反向定位报错SQL
如果SQL混杂在业务代码里,不好一个个找,可以通过DM8的SQL日志或者DBA_HIST_SQLTEXT之类的视图,把报错SQL抓出来看。最直接的方法是让应用保持报错状态,然后在DM管理工具里执行:
sql复制SELECT * FROM V$SQL WHERE SQL_TEXT LIKE '%无效的模式名%';
不过说实话,这种问题大部分情况下不用捞SQL日志,应用启动时控制台会直接打印导致失败的SQL,看下是哪个schema名报错就基本能锁定。关键是要把“所有显式带模式前缀的SQL”和“没带前缀但原本靠search_path访问的SQL”都找出来,因为后者在DM8里可能也会出问题,只是不会立刻报错,等用到的时候才炸。
3.3 别被“无权限”的假象带偏
还有一种情况容易和“无效模式”搞混——模式确实存在,但当前用户没权限访问,DM8的报错有时会提示“无效的模式名”,有时提示“表或视图不存在”。这两种报错都可能是权限问题引起的。
我当时也踩过这个坑,一开始以为是应用账号没授权,后来用SYSDBA查同一个SQL,发现照样报“无效模式”,才排除权限因素,确认是模式映射问题。排查时建议先用SYSDBA登录测试,如果SYSDBA也报错,那基本就是模式压根不存在,跟权限无关;如果SYSDBA能查而应用账号不能,那才是权限问题。
4. 解决方案详解
4.1 方案A:重建模式,把表移回去(最符合预期)
这个方案的思路是,在DM8里手动创建缺失的模式,然后把表搬过去,让结构对齐源库。听起来简单,但实际操作最麻烦,因为涉及大量表的移动、索引重建、约束和序列的处理。
如果表数量不多(几十张以内),可以这样操作。首先创建模式,例如:
sql复制-- 创建模式(DM8中通过创建用户来创建同名模式)
CREATE USER APP IDENTIFIED BY "password";
注意DM8的模式与用户绑定,创建一个用户就等于创建了一个同名模式。然后通过迁移工具或者CREATE TABLE AS的方式,把SYSDBA下的表复制到APP模式下:
sql复制-- 复制表结构+数据(以一张表为例)
CREATE TABLE APP.USERS AS SELECT * FROM SYSDBA.USERS;
之后要手动重建主键、索引、外键、序列。序列这一步最容易漏,如果PG源库用的是SERIAL或IDENTITY,迁移后DM8里未必能自动绑定,插入数据时可能出现主键冲突或序列不存在。
表多的时候不建议纯手工做,更推荐回到DTS工具,在任务配置里把“模式映射”改为:源库模式app映射到目标用户APP,然后只迁移数据,表结构由工具重新生成。这种方式比手工CTAS更稳,索引约束也会带着走。
4.2 方案B:创建同义词(应用改动最小的应急方案)
如果表已经全部在SYSDBA模式下,不想动数据,也不想重建表,最快的办法是创建同义词。DM8支持同义词,语法和Oracle类似:
sql复制-- 在目标模式下创建同义词,指向实际的表
CREATE OR REPLACE SYNONYM APP.USERS FOR SYSDBA.USERS;
这样应用再执行SELECT * FROM app.users时,DM8会通过同义词解析到SYSDBA.USERS,报错消失。
同义词方案适合跨模式访问点不多、且表不会频繁重建DDL的场景。表结构一旦变更,同义词跟着失效,需要重新创建。另外,如果应用账号APP需要访问这些同义词,还要确保有基础权限:
sql复制-- 授予应用账号对目标表的DML权限
GRANT SELECT, INSERT, UPDATE, DELETE ON SYSDBA.USERS TO APP;
如果有很多张表,可以写个存储过程批量生成同义词,不需要一张张手工敲。我当时是把所有漏掉的模式名整理出来,用DM管理工具的脚本功能批量跑了同义词创建语句,十几分钟搞定,属于最快止血的办法。
4.3 方案C:修改SQL,显式加上正确的模式前缀(改动直观但量大)
如果你希望一劳永逸,同时代码也愿意改,那就在SQL里把所有访问的schema前缀改成DM8实际存在的模式名。比如原来写app.users,DM8里实际是SYSDBA.USERS,那就全局替换。
这个方案适合:应用层SQL集中在少数几个Mapper文件里、或者有统一的SQL规范管理的项目。改动量小时还行,改动量大时容易漏,而且一旦模式名和表名大小写处理不好,改完还是会报错。
需要注意:不要只改SQL而不改事务、锁、序列等关联对象。PG里的nextval('app.seq_xxx')在DM8里要改成NEXTVAL('SYSDBA.SEQ_XXX')或序列的全名,否则插入数据时依然会出问题。
4.4 方案D:设置登录用户的默认模式(适合“只有一个业务模式”的场景)
如果业务里跨模式查询其实很少,绝大多数SQL都是不带前缀的,那可以在应用数据库连接初始化时,把会话的默认模式切到实际数据所在的模式。
DM8支持类似Oracle的ALTER SESSION SET CURRENT_SCHEMA,也支持SET SCHEMA。在连接池初始化时执行下面语句即可:
sql复制SET SCHEMA SYSDBA;
这样应用账号登录后,不带模式前缀的SQL会优先在SYSDBA模式下解析,单模式内的查询就全好了。但注意,这条语句只对当前会话生效,连接池里的每个连接都要初始化,否则部分连接还是默认模式。在JDBC连接串里,不同版本的DM驱动支持的方式不完全一样,建议在应用启动时的SQL初始化列表里显式执行。
我之前在Spring Boot项目里就是通过HikariCP的connectionInitSql配置来实现的,一行配置搞定所有新连接:
yaml复制spring:
datasource:
hikari:
connection-init-sql: SET SCHEMA SYSDBA
4.5 组合方案的取舍逻辑
四种方案不是互斥的,实际实施可以组合。我的建议是:
如果表量大、结构复杂,优先用DTS重做迁移,在迁移任务里明确设置模式映射,从根上解决问题;如果表量小或急需恢复业务,先上同义词方案止血,再择机重迁;如果跨模式访问很少,且代码不好动,配合SET SCHEMA解决大部分默认解析问题;如果代码规范统一、改动成本低,直接改SQL加前缀是最干净的。
5. 常见问题与避坑实录
5.1 迁移后权限导致的“无效模式”假象
我们上面提到过,DM8的报错容易被权限问题干扰。授予权限时,要确保不仅授了表的权限,视图、存储过程的执行权限也要一并授,否则应用能连上库,但一调用存储过程就报“无效模式”或“对象不存在”。
实际排查时用这条SQL,能看到当前用户到底有哪些对象的权限:
sql复制-- 查看当前模式的权限情况
SELECT * FROM USER_TAB_PRIVS;
另外,DM8里如果应用账号和模式名不是同一个,访问其他模式的对象时,建议在对象名前显式加模式名,避免依赖默认解析规则。
5.2 序列、视图、函数里的隐性依赖
迁移PG到DM8后,序列的坑很容易被忽略。PG里SERIAL字段自增是绑定序列的,DTS迁移后不一定能自动把序列和表字段关联起来。当应用插入数据时不带主键,数据库就会报主键为空或序列不存在。
同样,视图和函数在PG里如果依赖search_path定位对象,迁移到DM8后,函数体内的SQL如果没带模式前缀,默认解析到当前用户的模式,经常会找不到表。这种问题表现为“能编译通过,运行时才报错”,非常隐蔽。建议在函数体内部把所有表引用都改成显式的模式名.表名,不要依赖会话默认模式。
5.3 一个插曲:Java程序能跑,但IDE断点无效
排查过程中我还浪费了一点时间在一个跟数据库无关的“假问题”上。当时改完JDBC配置,重启应用,准备在IDEA里打断点跟踪一下具体是哪条SQL触发了报错,结果发现Debug模式下断点怎么打都不生效,代码能跑但不停。一度以为是不是DM8的驱动和IDE调试冲突。
后来检查发现,是本地起了多个应用实例,旧的Java进程没有完全停掉,新起的进程和旧进程混在一起,IDE连的是旧进程对应的调试端口。把进程全部杀干净,重启后再打Debug断点就正常了。这个跟数据库迁移本身没关系,但如果你也遇到“能运行但断点无效”的情况,优先检查是不是有多个进程在同时跑,别在调试配置上折腾太久。
5.4 统信UOS上部署DM8的注意事项
既然环境是在统信UOS上跑DM8,顺便说几个UOS环境下的实操注意点。
第一,DM8安装时依赖的libncurses、libaio这些库在UOS上可能需要手动补齐,安装前先把依赖装好,否则安装进程能启动但最终起不来服务。第二,DM8安装完成后的安装路径,建议不要放在有特殊字符或中文的目录下,否则部分脚本会执行异常。第三,UOS如果开了防火墙,要放行DM8的默认端口(通常5236),不然应用远程连不上,而本机用DM管理工具连却正常,容易误判为应用配置问题。
5.5 迁移后的验证清单
最后给一份我们当时用来快速验证的检查清单,你可以直接照着做:
- 对比源库和目标库的模式(schema)数量,确认一一对应。
- 用
SELECT OWNER, COUNT(*) FROM ALL_TABLES GROUP BY OWNER检查各个模式下的表数量是否与源库一致。 - 抽查几组业务SQL,包括跨模式查询、带视图的查询、存储过程调用,全部执行一遍。
- 测试应用登录账号的默认模式解析,执行不带前缀的SQL是否命中预期的表。
- 确认序列、自增主键在插入数据时能正常生成。
这个清单看起来简单,但能帮你把大部分迁移后的“定时炸弹”提前拆掉。
写在最后
这次PG到DM8的迁移,折腾了大概一个工作日,核心问题就一句话:源库的schema结构在迁移后丢了,而应用还在按原来的schema去访问。弄明白DM8“模式与用户绑定”的解析逻辑之后,解决办法其实不复杂——要么把模式结构补回去,要么让应用去适配新的模式结构。
我个人在实际操作里最推荐的做法是:迁移任务里就配好模式映射,宁可多花点时间重迁,也不要事后靠同义词和SET SCHEMA补救。数据迁移从来不只是搬数据,搬的是整个对象关系和访问语义。也希望这篇文章能帮你少走点弯路,真遇到“无效模式”的时候,知道自己该从哪查起。
