尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

达梦数据库同义词“能建不能用”排查指南:存储过程与自定义类型

达梦数据库同义词“能建不能用”排查指南:存储过程与自定义类型 上周帮一个团队做存储过程迁移收尾对方卡在一个很诡异的问题上同义词在达梦数据库里CREATE成功了SELECT也能看到可一调用就报“无效的引用对象”。查下来发现这不是个例达梦对自定义类型和存储过程创建同义词这件事支持得并不像表面看起来那么“顺畅”。这篇文章就按我实际排查的路径把“能建不能用”背后的机制、常见坑、解决方法和一套可复用的排查清单完整讲一遍给正在做Oracle到达梦迁移、或者日常维护达梦库的朋友当个参考。1. 建同义词很顺利一调用就报错先看现场1.1 三个最容易翻车的场景达梦创建同义词的语法和Oracle基本一致CREATE [PUBLIC] SYNONYM [模式名.]同义词名 FOR [模式名.]基对象名;从语法上看普通表、视图、存储过程、自定义类型都能创建同义词。但实际跑起来三个场景的表现完全不同场景创建同义词使用同义词常见现象表/视图同义词成功正常一般没有大问题存储过程同义词成功调用报错无效的引用对象、无效的过程名自定义类型同义词成功声明变量报错未定义的类型、语法错误我用一段最小化代码复现一下存储过程和类型这两个坑-- 场景A存储过程同义词 CREATE OR REPLACE PROCEDURE USER_A.P_TEST AS BEGIN DBMS_OUTPUT.PUT_LINE(OK); END; / CREATE PUBLIC SYNONYM SYN_PROC FOR USER_A.P_TEST; / -- 看起来没问题实际调用时报错 CALL SYN_PROC(); -- 常见报错无效的引用对象 / 无效的过程名 -- 场景B自定义类型同义词 CREATE OR REPLACE TYPE USER_A.TY_ADDR AS OBJECT( CITY VARCHAR(50), STREET VARCHAR(100) ); / CREATE PUBLIC SYNONYM SYN_ADDR FOR USER_A.TY_ADDR; / -- 在PL/SQL块里声明变量直接挂掉 DECLARE V_ADDR SYN_ADDR; -- 常见报错未定义的类型 / 无效的引用对象 BEGIN NULL; END; /表同义词基本能正常跑但过程同义词和类型同义词就是另一回事了。这也是为什么很多人第一次遇到时会怀疑“达梦压根不支持同义词”——其实是支持的但支持的边界和Oracle不完全一样。1.2 达梦报错信息的阅读方式达梦的错误提示比较“宽泛”同一个错误在不同上下文里对应完全不同的根因。我整理了实践中最常见的几种提示报错提示实践中对应的根因无效的引用对象大概率是权限不足也可能是过程体内部对象解析失败无效的过程名同义词没被解析到或者当前会话里同义词不可见未定义的类型PL/SQL编译器在类型声明路径上没命中同义词对象不存在同义词指向的基对象被删了或者FOR后面对象名写错了遇到过很多次“无效的引用对象”第一反应不是SQL写错而是顺着解析链路去查权限和对象状态。这个思路比死记错误码有用得多。1.3 为什么这种问题在迁移期被集中引爆做了几个迁移项目后我总结出这类问题在迁移阶段集中爆发的三个原因迁移工具只导对象定义不导运行时权限。表结构、类型、过程都能搬过来但授权脚本经常漏掉。脚本执行顺序不对。建表、建过程、建同义词、授权四个步骤顺序一乱同义词指向的对象还没建好或者建好后权限没跟上。对象owner和调用者不一致。原来Oracle库里一个账号全搞定到了达梦为了隔离拆成了好几个账号跨模式访问一下子就暴露出同义词和权限链路的问题。2. 同义词在达梦里到底是个什么东西2.1 同义词是“路由表”不是“复印件”同义词在数据字典里本质就是一个指向记录。执行如下SQL就能看到它的真面目SELECT OWNER, SYNONYM_NAME, TABLE_OWNER, TABLE_NAME FROM ALL_SYNONYMS WHERE SYNONYM_NAME SYN_PROC;查询结果里真正干活的是TABLE_OWNER和TABLE_NAME同义词只是把用户输入的名字“路由”到这两个字段指向的对象上。同义词本身不存数据、不存代码基对象没了同义词就是个空壳。打个比方同义词就像前台的花名册上面写着你的花名、真名和工号。别人喊花名能找到你但门禁系统只认工号上的授权。你给“花名”开通门禁是没用的必须开在“工号”上。2.2 DM的名字解析顺序当前对象优先于同义词达梦解析一个未带模式名的对象时顺序大致是当前模式下是否存在同名对象是否存在同名私有同义词是否存在同名公共同义词这个顺序会引发一个隐蔽问题同义词会被同名本地对象掩盖。比如你在USER_B下建了一张表SP_EMP然后又创建了一个公共同义词SP_EMP指向USER_A.T_EMP那USER_B执行SELECT * FROM SP_EMP命中的永远是本地表同义词完全被忽略。另外尽量避免同义词指向同义词。理论上有时候能创建成功但实际解析链条很容易断达梦对链式同义词的支持并不友好。FOR后面直接写基对象别绕。2.3 同义词不能分担权限授权必须落在基对象上这是最容易被误解的一点。同义词本身没有权限实体你查DBA_TAB_PRIVS看不到同义词的授权记录。换句话说-- 正确写法授权给基对象 GRANT EXECUTE ON USER_A.P_TEST TO USER_B; -- 错误习惯试图给同义词授权 GRANT EXECUTE ON SYN_PROC TO USER_B; -- 这行在达梦里基本没用公共同义词挂在PUBLIC模式下更不要指望通过给PUBLIC授什么权限来解决问题。权限的落点永远在基对象上。很多“能建不能用”的存储过程同义词问题查到最后都是因为这一句GRANT EXECUTE ON USER_A.过程名 TO 调用用户没写。3. 存储过程同义词的真正难点跨用户调用和过程体内部引用3.1 一个典型的A建过程、B调同义词案例我给一个完整案例帮你把整个链路看清楚。假设有两个用户USER_A和USER_BUSER_A建了存储过程和同义词-- USER_A 下建表、建过程、建同义词 CREATE TABLE USER_A.T_EMP(ID INT, NAME VARCHAR(50)); CREATE OR REPLACE PROCEDURE USER_A.P_INSERT_EMP( P_ID INT, P_NAME VARCHAR(50) ) AS BEGIN INSERT INTO T_EMP VALUES(P_ID, P_NAME); END; / CREATE PUBLIC SYNONYM SP_INSERT_EMP FOR USER_A.P_INSERT_EMP; -- 给USER_B授权 GRANT EXECUTE ON USER_A.P_INSERT_EMP TO USER_B; GRANT SELECT, INSERT ON USER_A.T_EMP TO USER_B;然后USER_B登录调用CALL SP_INSERT_EMP(1, 测试);如果缺少GRANT EXECUTE这里基本就是“无效的引用对象”。授权补上之后多数场景就通了。但还有更隐蔽的第二个坑。3.2 过程体内部的第二次名字解析存储过程调用走通之后还要看过程体内部引用的对象。达梦存储过程默认是定义者权限AUTHID DEFINER也就是说只要定义者USER_A对内部对象有权限调用者USER_B即便没有直接权限也能跑。这种情况下问题不大。但如果存储过程定义成了调用者权限CREATE OR REPLACE PROCEDURE USER_A.P_INSERT_EMP( P_ID INT, P_NAME VARCHAR(50) ) AUTHID CURRENT_USER AS BEGIN INSERT INTO T_EMP VALUES(P_ID, P_NAME); END; /此时过程体里的INSERT INTO T_EMP会按照调用者USER_B的权限去检查。如果USER_B对T_EMP没有INSERT权限调用同义词时照样报“无效的引用对象”。同义词本身没问题问题是调用者权限模式下授权不完整。这种问题在迁移工程里特别常见因为很多老系统存储过程内部不写模式前缀迁移到达梦后一旦配合调用者权限内部对象解析就会暴雷。我的建议很简单过程体内部引用的表、视图、序列一律写完整的“模式名.对象名”别偷懒。这样无论定义者权限还是调用者权限至少解析路径是明确的。3.3 能解决存储过程同义词问题的几种姿势触发场景推荐处理说明B调用A的存储过程同义词报无效引用GRANT EXECUTE ON USER_A.P_INSERT_EMP TO USER_B权限只能授在基过程上过程体内跨模式引用对象报错在过程体里写完整模式名显式路径避免解析歧义需要对外提供统一入口PUBLIC同义词 基对象授权公共同义词全库可见不想给开发账号太多底层表权限用默认的AUTHID DEFINER只授过程EXECUTE内部权限按定义者检查如果你的同义词是私有同义词还要确认同义词建在哪个模式下。私有同义词只对它的owner可见别的用户即使知道名字也用不了。要让所有用户都能通过同义词调用最省事的方式是建PUBLIC同义词同时把基对象的EXECUTE权限授给调用者。4. 自定义类型同义词SQL窗口能查PL/SQL却声明不了4.1 类型同义词的典型用法和失败现场自定义类型在达梦里的典型用法包括对象类型、数组类型、嵌套表类型等。比如建一个对象类型CREATE OR REPLACE TYPE USER_A.TY_ADDR AS OBJECT( CITY VARCHAR(50), STREET VARCHAR(100) ); / CREATE PUBLIC SYNONYM SYN_ADDR FOR USER_A.TY_ADDR; /在SQL窗口里有些达梦版本能正常执行SELECT SYN_ADDR(北京, 长安街) FROM DUAL;但切到PL/SQL块里声明变量就原形毕露了DECLARE V_ADDR SYN_ADDR; -- 报错未定义的类型 / 无效的引用对象 BEGIN NULL; END; /更麻烦的是类型同义词的问题会传导到存储过程入参上。从Oracle迁移过来的代码经常这么写CREATE OR REPLACE PROCEDURE USER_A.P_SAVE_ADDR( V_ADDR IN SYN_ADDR ) AS BEGIN NULL; END; /在Oracle里这种写法通常能编译过到达梦经常直接报错。这时候只能改成全路径类型名或者改成包里的公有类型。4.2 为什么SQL上下文和PL/SQL编译器对类型的解析不一致这是我实测后反推出来的结论官方文档对这部分描述得比较简略但行为规律是稳定的SQL语句的对象解析可以借助数据库的全局元数据完成而PL/SQL编译器在声明变量时走的是静态类型查找路径这条路径对同义词的支持并不完整尤其是对象类型、嵌套表这类复合类型。简单理解就是SQL引擎查对象时“视野”比较宽同义词在它的查找范围内PL/SQL编译器查类型时“视野”窄当前模式找不到就直接报错不会像SQL那样自动跳到公共同义词上去。所以在达梦上不要指望“SQL里能用”就代表“PL/SQL里一定能用”。4.3 绕开类型同义词的几种可行方案方案APL/SQL代码里全部写“模式名.类型名”DECLARE V_ADDR USER_A.TY_ADDR; BEGIN V_ADDR : USER_A.TY_ADDR(北京, 长安街); END; /这种方式最直接缺点是代码里出现了硬编码模式名换环境时要批量改。方案B把类型定义放到包里面做成包公有类型CREATE OR REPLACE PACKAGE USER_A.PKG_ADDR AS TYPE TY_ADDR IS RECORD(CITY VARCHAR(50), STREET VARCHAR(100)); END; /使用时写成V_ADDR PKG_ADDR.TY_ADDR。这种方式完全绕开了类型同义词多个过程还能共享同一个类型定义是我在迁移项目里最推荐的做法。方案C接受“类型同义词不能用于PL/SQL声明”这个现实只把类型同义词留给SQL查询和元数据展示用业务代码里一律走全路径或包类型。4.4 类型构造器不能通过同义词调用的坑对象类型自带构造器。当你写SYN_ADDR(北京, 长安街)时编译器需要先确认SYN_ADDR是类型再把构造器当函数解析。这个链路相当于让解析器同时跨了两层同义词 → 类型 → 构造器。达梦对这条链的支持非常脆弱。所以我在项目里有一条不成文的规定类型同义词不承担构造器调用职责。需要构造器的地方全部用全路径类型名或者通过包函数返回类型实例。这样能把“SQL能跑但PL/SQL跑不了”的概率降到最低。5. 可复用的排查清单从报错到修复的完整路径5.1 拿到报错先分类别急着改代码面对同义词相关报错先问自己五个问题报错发生在建同义词阶段还是调用阶段调用场景是SQL语句、CALL命令还是PL/SQL块同义词是私有还是公有调用用户和对象owner是不是同一个基对象的权限有没有授到位把这五个问题答完问题范围基本能缩小一半。5.2 用SQL确认同义词元数据和对象状态排查第一步确认同义词确实存在且指向正确SELECT OWNER, SYNONYM_NAME, TABLE_OWNER, TABLE_NAME FROM ALL_SYNONYMS WHERE SYNONYM_NAME SP_INSERT_EMP;第二步查基对象本身的状态SELECT OWNER, OBJECT_NAME, OBJECT_TYPE, STATUS FROM ALL_OBJECTS WHERE OBJECT_NAME IN (SP_INSERT_EMP, P_INSERT_EMP, TY_ADDR, SYN_ADDR);注意同义词本身在ALL_OBJECTS里也有记录状态一般是VALID。判断同义词是否可用关键看它指向的基对象状态。如果基对象不是VALID比如被改成无效状态同义词调用照样失败。5.3 三组对比实验锁定解析盲区为了区分是“解析不到对象”还是“权限不足”我做三组对比实验实验一同义词查询 vs 基对象全路径查询SELECT COUNT(*) FROM SP_EMP; -- 如果失败 SELECT COUNT(*) FROM USER_A.T_EMP; -- 如果成功说明权限或解析问题在USER_A对象上实验二CALL同义词 vs BEGIN块内调用同义词CALL SP_INSERT_EMP(1, 测试); -- 可能失败 BEGIN SP_INSERT_EMP(1, 测试); END; -- 观察是否报同样的错实验三类型声明用同义词 vs 用全路径DECLARE V_ADDR SYN_ADDR; END; -- 失败 DECLARE V_ADDR USER_A.TY_ADDR; END; -- 成功则说明PL/SQL类型解析问题三组实验做完基本能判断问题出在权限链路还是解析链路。5.4 权限链路的检查和授权方法查权限时用这条SQL一次性把基对象的授权情况列出来SELECT GRANTEE, PRIVILEGE, GRANTABLE FROM DBA_TAB_PRIVS WHERE OWNER USER_A AND TABLE_NAME IN (P_INSERT_EMP, T_EMP, TY_ADDR) ORDER BY TABLE_NAME, GRANTEE;缺什么补什么GRANT EXECUTE ON USER_A.P_INSERT_EMP TO USER_B; GRANT SELECT, INSERT, UPDATE, DELETE ON USER_A.T_EMP TO USER_B; GRANT EXECUTE ON USER_A.TY_ADDR TO USER_B;如果调用用户是通过角色拿到的权限还要确认该角色在会话中已生效SELECT * FROM SESSION_ROLES;这一步很容易漏角色没生效权限检查一样过不去。5.5 迁移期预防同义词故障的做法以我现在的习惯做达梦迁移时同义词相关部分按这几条走基本不会再出问题对象脚本和授权脚本分离。先导对象定义再建同义词最后统一跑授权脚本。存储过程内部SQL全部使用“模式名.对象名”的完整路径。同义词命名规则独立禁止和表、视图、过程同名避免触发解析优先级问题。同义词建完后切换到业务账号真实调用一遍而不是在DBA账号下只看SELECT。定期跑一段“同义词健康检查”SQL把空壳同义词捞出来SELECT S.OWNER, S.SYNONYM_NAME, S.TABLE_OWNER, S.TABLE_NAME, O.STATUS AS BASE_STATUS FROM ALL_SYNONYMS S LEFT JOIN ALL_OBJECTS O ON O.OWNER S.TABLE_OWNER AND O.OBJECT_NAME S.TABLE_NAME WHERE (O.STATUS IS NULL OR O.STATUS VALID) AND S.TABLE_OWNER NOT IN (SYS, SYSAUDITOR);这个脚本能查出指向已失效或不存在对象的同义词在迁移验收阶段特别有用。最后说一个我自己的习惯不管在Oracle还是达梦只要涉及跨模式使用对象代码里一律写完整的“模式名.对象名”同义词只做前端应用入口不让后端代码在同义词上绕来绕去。同义词建完以后我会切到业务账号把增删改查、过程调用、类型声明各自真实跑一遍而不是在DBA账号下只看一眼元数据。这套流程在迁移项目里帮我挡掉了大部分同义词坑也从源头上避免了“能建不能用”这种问题反复出现。
返回列表