资讯详情

资讯详情

Oracle 11g随书脚本实战:建表约束、PL/SQL与排错

简介面向Oracle 11g初学者与数据库管理员《Oracle 11g从入门到精通第二版》配套实例源程序按19个章节组织涵盖数据库基础与体系结构、安装配置、SQL查询、表与索引、视图、存储过程、触发器、游标以及权限角色、备份恢复、SQL调优、分区与并行执行等主题适合对照书中案例复现实验、课后练习和技能查漏补缺。压缩包共495个文件以236个txt说明与脚本片段、123个class和44个java源文件、36个xml配置为主另含jpg插图、bak备份、doc与xls文档及dmp数据库文件整体约1.21MB目录按知识模块划分便于定位对应章节素材。目前已有405人学习下载。读者可借助这些源程序把抽象概念落到可运行示例中理解SQL编写、对象管理与备份恢复的实际流程也可用于课堂演示和个人进阶逐步提升Oracle数据库管理与开发能力。1. 一套随书脚本的真实用处从对着空库发呆到能跑通第一条存储过程装完 Oracle 11g用 SQL*Plus 登进去看到SCOTT用户下那张EMP表很多人就卡在这一步了——书翻到第三章讲的还是概念想动手却发现自己连一张像样的建表语句都敲不利索。这套《Oracle 11g 从入门到精通第二版》的实例源程序压缩包真正值钱的地方不是把书上的代码抄了一遍而是它把每一章的知识点都拆成了能在库里直接进去就跑的脚本建表、约束、序列、触发器、存储过程、游标批处理一条链路下来是完整的。刚接触数据库方向的学生、做后端想补 SQL 功底的开发者、需要拿示例库练手的人都适合把这份包当成一个可执行的课本。下面按包里有什么、环境怎么配、脚本怎么跑、哪几个地方容易翻车、最后怎么改成自己的练习库一层层拆。2. 资源构成与环境准备压缩包里到底装了什么、要配什么样的库拿到压缩包之后先别急着解压完直接双击 SQL 文件很多编辑器会把.sql当成文本文档打开然后你在图形工具里点到哪跑哪跑到一半报错根本不知道自己落在链路的哪一环。动手之前把包的骨架摸清楚比什么都重要。2.1 目录骨架与文件类型先分清哪类脚本能动、哪类只能看这类随书实例包的组织方式大同小异通常是按章节编号分目录每章下面的.sql脚本再按建表 → 约束 → 数据 → 程序的顺序编号。解压之后我一般先树状列一遍看清楚文件规模和依赖oracle11g_examples/ ├── ch01_intro/ │ ├── 01_create_user.sql -- 建练习用户、授权 │ └── 02_hello_world.sql -- 第一个匿名块DBMS_OUTPUT 输出 ├── ch03_table/ │ ├── 01_create_tables.sql -- 建表 DDL含表空间指定 │ ├── 02_add_constraints.sql -- 主键、外键、CHECK单独成文件 │ ├── 03_create_indexes.sql -- 业务索引和外键索引分开 │ └── 04_init_data.sql -- INSERT 初始化数据含中文 ├── ch06_plsql/ │ ├── 01_procedures.sql -- 存储过程与函数 │ ├── 02_cursors.sql -- 显式游标、BULK COLLECT │ ├── 03_triggers.sql -- 行级触发器、序列自增 │ └── 04_exception_log.sql -- 异常处理与日志表 └── appendix/ ├── drop_all.sql -- 清库脚本重跑前先执行 └── readme.txt -- 执行顺序说明看清这几类文件的性质很重要01_create_tables.sql这种是纯 DDL可以反复执行前提是先 DROP04_init_data.sql是 DML重复跑会出现主键冲突03_triggers.sql里带CREATE OR REPLACE可以覆盖但覆盖之后序列不会重置这一点后面会细说。少数包里还会附一个.dmp导出文件或者.csv示例数据.dmp需要用imp/impdp导入不是用编辑器能打开的遇到别硬啃。提示解压后第一件事是把readme.txt看一遍。随书脚本最常见的坑就是执行顺序作者在正文里写了先建表再建约束但读者往往是按文件名排序跑的02_add_constraints.sql排在01_create_tables.sql前面就必然报ORA-00942。2.2 装库与连库实例、监听、客户端这三件事缺一不可Oracle 11g 的安装本身是个体力活但装完之后连不上问题基本出在三个环节数据库实例有没有起、监听有没有起、客户端解析能不能找到服务。脚本能不能跑第一步就是确认这三件事都通了。# 1) 确认监听在跑1521 端口有响应 lsnrctl status # 2) 用 EASY CONNECT 直连绕开 tnsnames.ora 的配置问题 # 格式用户名/口令//主机:端口/服务名 sqlplus scott/tiger//127.0.0.1:1521/orcl # 3) 登进去先确认身份和会话字符集别急着跑脚本 # SELECT USER FROM dual; # SELECT parameter, value FROM nls_session_parameters # WHERE parameter IN (NLS_CHARACTERSET,NLS_DATE_FORMAT);lsnrctl status输出里如果能看到Service orcl has 1 instance(s)说明监听和服务名都注册上了如果只有The listener supports no services那是实例没起或者local_listener没配好这时候跑任何脚本都会卡在连接阶段。EASY CONNECT 写法里//127.0.0.1:1521/orcl的服务名要和lsnrctl status里显示的完全一致很多人的库装的是orcl却写成ORCL之外的自定义名字连不上就开始怀疑人生。字符集那一项也要看NLS_CHARACTERSET如果是AL32UTF8而客户端环境变量NLS_LANG写的是SIMPLIFIED CHINESE_CHINA.ZHS16GBK后面插入中文就会出现一库两制的乱码这个坑在第 5 章展开。2.3 脚本执行顺序与依赖关系用串成一条可重跑的链路单条脚本名执行路径是相对于 SQLPlus 当前工作目录的脚本名则是相对于当前正在执行的脚本所在目录。这个差异在跨目录批量执行时非常关键——你在D:\work下执行ch03_table\run_all.sql脚本里如果写01_create_tables.sqlSQLPlus 会去D:\work找找不到写成01_create_tables.sql才会去ch03_table目录找。-- run_all.sql放在 ch03_table 目录下一次跑完本章所有 DDL SET DEFINE OFF SET SERVEROUTPUT ON SIZE UNLIMITED WHENEVER SQLERROR EXIT SQL.SQLCODE ROLLBACK 01_create_tables.sql 02_add_constraints.sql 03_create_indexes.sql 04_init_data.sql PROMPT ch03 执行完毕 SET DEFINE OFF关掉替换变量避免脚本里出现字符时 SQL*Plus 弹窗让你输入WHENEVER SQLERROR EXIT SQL.SQLCODE让任何一条语句报错就整体退出而不是硬着头皮往下跑出一堆连锁错误ROLLBACK保证退出前不留下半成品事务。执行时用sqlplus scott/tiger//127.0.0.1:1521/orcl run_all.sql报错信息会带着脚本名和行号打在控制台上定位效率比在图形工具里一段段选中执行高得多。依赖关系的原则只有一条被引用的对象必须先存在。用户和表空间在最前表在约束之前约束在被引用的父表之后索引可以在建表之后任意时刻补初始化数据要在约束加完之后否则脏数据会被 CHECK 拦下PL/SQL 对象放最后。把这条原则记牢遇到ORA-00942、ORA-02291这类错误时先回头看看是不是顺序被打乱了。3. 建表与约束链路一份 DDL 脚本从建表到外键落地的完整顺序DDL 这块看起来最枯燥但它恰恰是后面所有 PL/SQL 脚本的地基。示例库里的表设计得还算克制字段不多、关系清晰很适合拿来练手。真正值得琢磨的是几个细节数据类型怎么选、约束什么时候加、自增主键在 11g 里怎么写。3.1 建表脚本数据类型、字符语义与表空间三处最容易想当然先看一段典型的建表语句这段是从示例包的01_create_tables.sql里抽出来的结构做了注释补全-- 部门表自关联结构用来演示层次查询和递归 CREATE TABLE t_dept ( dept_id NUMBER(4) NOT NULL, -- 部门编号主键由序列生成 dept_name VARCHAR2(40 CHAR) NOT NULL, -- 显式 CHAR 语义避免中文按字节截断 parent_id NUMBER(4), -- 上级部门NULL 表示顶级 created_at DATE DEFAULT SYSDATE NOT NULL, -- 建表时给默认值插入可省略 remark VARCHAR2(200 CHAR) ) TABLESPACE users PCTFREE 10 INITRANS 2;NUMBER(4)表示总共 4 位有效数字最大 9999部门编号够用换成NUMBER不带精度虽然也能跑但类型语义就模糊了别人读脚本时看不出设计意图。VARCHAR2(40 CHAR)和VARCHAR2(40)的区别是血泪经验后者在NLS_CHARACTERSETAL32UTF8的库里按字节算一个中文占 3 字节40 字节只能放 13 个汉字插入技术研发中心第二事业部直接报ORA-12899: value too large for column。DATE类型在 11g 里精确到秒需要毫秒就用TIMESTAMP示例库统一用DATE够用且省事。TABLESPACE users显式指定表空间是练习环境里比较稳妥的做法避免建到SYSTEM表空间里去。PCTFREE 10是给后续 UPDATE 预留的块内空间示例库更新不频繁默认 10 就行INITRANS 2是并发事务槽位练习库单用户跑根本用不上看到不用改。3.2 约束与索引主外键分离成独立脚本顺序不能反把约束单独拆成一个文件是为了让建表和加约束两步可以分别重跑、分别排错。主键和外键的写法如下-- 02_add_constraints.sql -- 主键显式指定索引表空间避免索引散落到用户默认表空间 ALTER TABLE t_dept ADD CONSTRAINT pk_dept PRIMARY KEY (dept_id) USING INDEX TABLESPACE indx; -- 外键自关联先插父节点再插子节点否则 ORA-02291 ALTER TABLE t_dept ADD CONSTRAINT fk_dept_parent FOREIGN KEY (parent_id) REFERENCES t_dept (dept_id) ON DELETE SET NULL ENABLE; -- 外键列必须手动建索引Oracle 不会自动建 CREATE INDEX idx_dept_parent ON t_dept (parent_id) TABLESPACE indx;ON DELETE SET NULL的意思是父部门被删时子部门的parent_id置空而不是级联删除示例库里选这个策略是为了演示删除一个部门不会连带干掉整棵子树。生产环境里更常见的是ON DELETE CASCADE或者干脆用逻辑删除选哪个取决于业务语义但一定要显式写不写默认就是NO ACTION删父表会直接报ORA-02292: integrity constraint violated。外键列手工建索引这一点是新手最容易忽略的。Oracle 只对主键和唯一约束自动建索引外键列不会自动建而外键列恰恰是最常出现在WHERE条件里的比如查某个部门下的所有子部门。不建索引数据量一上来就是全表扫描执行计划里TABLE ACCESS FULL一大片。主键用USING INDEX TABLESPACE indx显式指定索引表空间也是同理不加的话索引会建到该用户的默认表空间练习库无所谓正式环境里索引和表分开放是常规操作方便单独维护和迁移。3.3 序列与触发器11g 里自增主键的标准写法Oracle 11g 没有IDENTITY列那是 12c 才有的自增主键必须靠序列SEQUENCE加行级触发器TRIGGER来实现。示例包的03_triggers.sql给的就是这套标准写法-- 序列步长 1练习库用 NOCACHE 避免跳号带来的困惑 CREATE SEQUENCE seq_dept_id START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; -- 行级触发器只在插入未显式给 dept_id 时补上序列值 CREATE OR REPLACE TRIGGER trg_dept_id BEFORE INSERT ON t_dept FOR EACH ROW WHEN (NEW.dept_id IS NULL) BEGIN :NEW.dept_id : seq_dept_id.NEXTVAL; END; /START WITH 1和INCREMENT BY 1是标准配置关键在于NOCACHE。序列默认CACHE 20意思是每次取号会一次性缓存 20 个值到内存实例重启或者共享池回收时缓存丢失就会出现编号跳过一段的现象。练习库里看到主键从 1 跳到 21 会让人怀疑代码写错了所以示例统一用NOCACHE牺牲一点性能换可读性生产上高并发插入的场景反而应该CACHE 20甚至CACHE 100性能差别很明显。WHEN (NEW.dept_id IS NULL)这个条件很重要它保证了显式插入主键比如数据迁移时不会被触发器覆盖。完整写法里:NEW前面的冒号不能省这是 PL/SQL 里引用行级触发器伪记录的标准语法。脚本末尾单独一行的/是告诉 SQLPlus 这条 PL/SQL 语句到此结束执行它BEGIN...END;内部的分号只是语句分隔符/才是提交执行的信号少写一个/SQLPlus 就会一直等你输入。触发器建完标准插入语句可以只写业务字段INSERT INTO t_dept (dept_name, parent_id) VALUES (技术研发中心, NULL); INSERT INTO t_dept (dept_name, parent_id) VALUES (数据库组, 1); COMMIT;父节点先插、子节点后插parent_id严格对应已存在的dept_id。这两条执行完SELECT * FROM t_dept就能看到dept_id分别是 1 和 2created_at自动填了当前时间。4. PL/SQL 核心模块存储过程、游标批处理与异常日志的三段式写法DDL 跑通之后示例包真正有含金量的部分是 PL/SQL 那几章。随书脚本里的存储过程写得比较教科书——注释多、逻辑短、异常分支写得很全正好适合拿来摸清 PL/SQL 的骨架。这一块我按过程 → 批处理 → 日志三段来拆。4.1 存储过程与函数参数模式、事务边界和自定义异常码示例里的存储过程基本围绕转账、扣款、批量调薪这类场景展开结构上大同小异。抽一个转账过程来看CREATE OR REPLACE PROCEDURE sp_transfer_salary( p_from_id IN NUMBER, -- 转出账户 p_to_id IN NUMBER, -- 转入账户 p_amount IN NUMBER -- 转账金额 ) IS v_balance NUMBER(12,2); -- 转出方余额局部变量 BEGIN -- 入参校验非法值直接抛自定义错误码 IF p_amount IS NULL OR p_amount 0 THEN RAISE_APPLICATION_ERROR(-20001, 转账金额必须大于 0); END IF; -- FOR UPDATE 锁行防止并发场景下余额被重复扣减 SELECT balance INTO v_balance FROM t_account WHERE emp_id p_from_id FOR UPDATE; IF v_balance p_amount THEN RAISE_APPLICATION_ERROR(-20002, 账户余额不足); END IF; UPDATE t_account SET balance balance - p_amount WHERE emp_id p_from_id; UPDATE t_account SET balance balance p_amount WHERE emp_id p_to_id; COMMIT; EXCEPTION WHEN NO_DATA_FOUND THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20003, 账户不存在 || p_from_id); WHEN OTHERS THEN ROLLBACK; RAISE; END; /三个参数全部是IN模式这是最常见的写法调用方传值进来过程内部只读不改。如果需要回传结果可以用OUT或者IN OUT但示例里的过程统一用RETURN值走函数、用日志表记录结果没有用OUT参数这样调用方不用预先声明变量EXEC sp_transfer_salary(1, 2, 100)一行就完事。SELECT ... FOR UPDATE这一行是并发安全的关键。不加它两个会话同时读到余额 1000各自扣 600最后余额变成 -200这就是典型的丢失更新。FOR UPDATE会在读取时对行加排他锁第二个会话会阻塞到第一个提交或回滚为止。练习库单用户跑看不出差别但这个习惯要从一开始就养成。RAISE_APPLICATION_ERROR(-20001, ...)里的错误码必须是-20000到-20999之间的自定义区间超出这个范围会报ORA-21000。异常处理块里的RAISE是不带参数的重新抛出把原始异常原样向上传递配合外层的调用方统一捕获这是分层设计里比较常见的做法。4.2 游标与批量处理BULK COLLECT 加 FORALL 是提速的正解逐行FOR rec IN cursor LOOP ... END LOOP的写法在示例里也有但那是给初学者看流程的。真正有参考价值的是批量版本DECLARE CURSOR c_emp IS SELECT emp_id, salary FROM t_emp WHERE dept_id 10; -- 用 %ROWTYPE 定义集合类型字段结构和游标一致改表结构不用改这里 TYPE t_emp_tab IS TABLE OF c_emp%ROWTYPE INDEX BY PLS_INTEGER; v_rows t_emp_tab; BEGIN OPEN c_emp; LOOP -- 每次只取 500 行到内存避免大表一次全量载入撑爆 PGA FETCH c_emp BULK COLLECT INTO v_rows LIMIT 500; EXIT WHEN v_rows.COUNT 0; -- FORALL 把整批 DML 一次发给 SQL 引擎比逐行 UPDATE 快一个数量级 FORALL i IN 1 .. v_rows.COUNT UPDATE t_emp SET salary salary * 1.05 WHERE emp_id v_rows(i).emp_id; DBMS_OUTPUT.PUT_LINE(本批处理行数 || v_rows.COUNT); END LOOP; CLOSE c_emp; COMMIT; END; /LIMIT 500这个数字不是随便定的。不写LIMITBULK COLLECT会一次性把游标所有结果行都塞进集合十万行数据能把 PGA 撑出ORA-04030。写了LIMIT内存占用就可控了500 到 1000 是练习环境里比较舒服的档位正式环境根据行宽和 PGA 大小调到 1000 到 5000 都正常。FORALL和普通FOR的差别在于执行引擎。普通FOR循环里每条UPDATE都要经历一次 PL/SQL 引擎到 SQL 引擎的上下文切换一万行就是一万次FORALL把整个集合打包成一次调用发给 SQL 引擎切换次数降到个位数。这个差别在小数据量上看不出来上到几万行就是分钟级和秒级的差距。DBMS_OUTPUT.PUT_LINE要能看到输出执行前必须先SET SERVEROUTPUT ON否则什么都不打印很多人以为是代码没跑。4.3 异常处理与日志落表用步骤变量定位到出错的那一行示例包里日志表的用法挺值得学过程里维护一个步骤变量出错时把当前步骤写进日志排查时一眼就知道挂在哪。CREATE OR REPLACE PROCEDURE sp_batch_raise( p_dept_id IN NUMBER ) IS v_step VARCHAR2(50) : INIT; -- 步骤标记随执行推进而更新 BEGIN v_step : UPDATE_SALARY; UPDATE t_emp SET salary salary * 1.1 WHERE dept_id p_dept_id; v_step : WRITE_LOG; INSERT INTO t_job_log(log_time, proc_name, step_name, status) VALUES (SYSDATE, sp_batch_raise, v_step, OK); COMMIT; EXCEPTION WHEN OTHERS THEN -- 记录失败步骤和错误信息然后重新抛出给调用方 INSERT INTO t_job_log(log_time, proc_name, step_name, status, err_msg) VALUES (SYSDATE, sp_batch_raise, v_step, ERR, SQLERRM); COMMIT; RAISE; END; /v_step的价值在长流程里才体现出来。一个过程如果有十几个步骤出错时SQLERRM只告诉你值过大或者唯一约束冲突但不会告诉你是第几步。有了步骤标记日志表里step_nameUPDATE_SALARY一查范围立刻缩小。这里有个隐藏的坑EXCEPTION里的ROLLBACK和COMMIT顺序。上面的写法是先写日志再COMMIT如果中间没有ROLLBACK业务表的未提交修改会被一起提交如果先ROLLBACK再写日志日志本身是独立事务能落盘业务改动被回滚这是更安全的写法。更严谨的做法是把日志插入放到自治事务里PRAGMA AUTONOMOUS_TRANSACTION这样即使主事务回滚日志也不受影响。示例包为了讲清楚基础语法用了简化版二次开发时我一般会改成自治事务。5. 避坑与排查脚本导入环节最常撞上的 5 个报错这一章全是踩出来的。同一套脚本不同机器上跑报错五花八门但归纳下来就那么几类。下面五条按现象 → 原因 → 解决记下来遇到时对号入座。5.1 连不上库ORA-12154 和 ORA-01017现象一执行sqlplus scott/tigerorcl报ORA-12154: TNS:could not resolve the connect identifier specified。原因客户端拿orcl这个别名去tnsnames.ora里找没找到。可能是TNS_ADMIN环境变量指向的目录不对可能是tnsnames.ora里根本没配这个别名也可能是别名拼写和文件里的不一致。这类问题纯粹是配置玄学跟脚本本身没关系。解决先用tnsping orcl确认解析能不能通输出OK说明配置没问题报TNS-03505就是别名找不到。不想折腾配置直接用 EASY CONNECT 写法scott/tiger//127.0.0.1:1521/orcl跳过tnsnames.ora这一层十次有九次能连上。现象二连接时报ORA-01017: invalid username/password; logon denied。原因口令错误、口令过期或者用了as sysdba但当前用户不在ORA_DBA组里。11g 默认有口令有效期策略PASSWORD_LIFE_TIME180 天练习库放几个月没用一登录就提示过期。解决以sys as sysdba身份登录后ALTER USER scott IDENTIFIED BY tiger;重置口令再把ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;关掉过期策略。注意SYS用户登录必须写as sysdba且要在操作系统层面有对应权限登录成功后先SHOW USER确认身份别在不该操作的用户下跑 DDL。5.2 中文乱码与 ORA-01861字符集和日期格式的两处隐式转换现象脚本里插入的中文变成问号或者方块或者执行带日期的INSERT时报ORA-01861: literal does not match format string。原因两个问题本质相同都是隐式转换惹的祸。中文乱码是客户端NLS_LANG和数据库NLS_CHARACTERSET不一致日期报错是脚本里写了2024-01-01这种字符串而当前会话的NLS_DATE_FORMAT是DD-MON-RROracle 按这个格式去解析自然对不上。解决日期一律显式转换不依赖会话默认格式。-- 错误写法依赖 NLS_DATE_FORMAT换台机器就跑不了 INSERT INTO t_emp (emp_id, hire_date) VALUES (1001, 2024-01-01); -- 正确写法显式指定格式任何会话下都能跑 INSERT INTO t_emp (emp_id, hire_date) VALUES (1001, TO_DATE(2024-01-01, YYYY-MM-DD));字符集问题更麻烦一点因为涉及已经存进去的脏数据。客户端环境变量统一设成NLS_LANGSIMPLIFIED CHINESE_CHINA.AL32UTF8Linux 下exportWindows 下改系统环境变量和库的字符集保持一致之后再插入的中文才不会乱。已经乱码的数据只能清理重插没有后悔药。5.3 脚本跑到一半停下来替换变量与分号、斜杠的混淆现象执行脚本时控制台跳出Enter value for xxx:让你输入或者脚本执行到某个CREATE PROCEDURE之后就卡住不动光标一直在等。原因SQLPlus 默认SET DEFINE ON脚本里任何字符都会被当成替换变量哪怕它出现在字符串常量里比如AB公司。卡住不动则是因为 PL/SQL 块以END;结尾后面少了单独一行的/SQLPlus 认为语句没结束继续等待输入。解决脚本开头统一加SET DEFINE OFF把替换变量关掉。PL/SQL 块一律按END;加换行/的格式写CREATE OR REPLACE PROCEDURE ... END;后面必须有/才会执行。还有个衍生问题脚本执行到一半报错中断已经执行的 DDL 是自动提交的Oracle 的 DDL 隐式提交表已经建出来了重跑会报ORA-00955: name is already used by an existing object。这时候先跑附录里的drop_all.sql清干净再重来别一条条手工删。5.4 表不存在与空间不足ORA-00942 和 ORA-01653现象脚本报ORA-00942: table or view does not exist但明明建表脚本刚跑过或者插入数据时报ORA-01653: unable to extend table ... by 8 in tablespace ...。原因ORA-00942九成是 schema 不对——建表用的是A用户执行查询用的是B用户没加 schema 前缀就找不到剩下的一成是对象名建的时候带了双引号t_emp变成大小写敏感后面不加引号查t_emp就找不到。ORA-01653是表空间满了练习库默认的users表空间往往只有几百兆导入几万行测试数据就见底。解决执行前先SHOW USER确认当前用户跨 schema 访问一律写全名schema.table。建对象时养成不加双引号、全小写的习惯就不会有大小写问题。表空间不够就给用户加配额-- 给练习用户在当前表空间的使用配额调到 2G ALTER USER scott QUOTA 2048M ON users; -- 或者直接给表空间加数据文件需要 DBA 权限 ALTER TABLESPACE users ADD DATAFILE users02.dbf SIZE 500M AUTOEXTEND ON NEXT 100M MAXSIZE 4G;AUTOEXTEND ON让数据文件自动增长练习环境里省心正式环境一般会关掉自动增长靠监控提前扩容避免半夜磁盘被撑爆。5.5 触发器与序列联用时的跳号和重复值现象插入数据后主键不连续中间空了一大段或者数据迁移导入后新插入的数据报ORA-00001: unique constraint violated。原因前者是序列CACHE造成的实例重启、共享池回收都会让缓存里的号段丢失后者是迁移时手工指定了主键值但序列的NEXTVAL还停在低位新插入时撞上了已有主键。解决练习库用NOCACHE减少跳号数据迁移之后把序列的起点重置到当前最大主键之上。-- 查当前最大主键假设得到 1500 SELECT MAX(dept_id) FROM t_dept; -- 重建序列从 1501 开始DROP 后重建是最直接的办法 DROP SEQUENCE seq_dept_id; CREATE SEQUENCE seq_dept_id START WITH 1501 INCREMENT BY 1 NOCACHE NOCYCLE;注意DROP SEQUENCE不会影响已经生成的数据但如果有其他对象依赖这个序列比如默认值引用需要一并处理。重建前先SELECT * FROM user_dependencies WHERE referenced_name SEQ_DEPT_ID查一遍依赖。6. 二次开发把随书脚本改成自己的业务练习库示例包跑通之后多数人的下一步是想拿它练自己的东西。我的做法不是直接在原表上加字段而是复制一份ch03_table出来把表名、字段名换成自己熟悉的业务场景比如把t_dept、t_emp改成订单和订单明细把转账过程改成库存扣减。这么改一遍比读十遍书都管用因为改名的时候你会被迫想清楚每个字段的类型、每个约束的语义。下面这张表是我一般会动的几个地方按改动成本从低到高排改造点原脚本写法二次开发建议验证方式表名与字段名示例用t_emp、emp_id换成业务实体名同时改序列名和触发器名SELECT * FROM user_tab_columns对比主键生成序列 触发器保留结构改成NOCACHE便于观察连插 3 条看编号日志表记录步骤和错误码加biz_id字段关联到具体业务单号故意造错查日志能否定位到单据批处理LIMIT 500固定值提到参数里按数据量动态调用 10 万行压测看 PGA 峰值事务边界过程内COMMIT改成由调用方控制过程内不提交制造异常确认业务表能整体回滚改造完了怎么验证我固定会跑一个数据校验脚本确认约束和触发器真的生效了而不是看起来跑通了-- 校验 1主键是否唯一有没有重复 SELECT dept_id, COUNT(*) FROM t_dept GROUP BY dept_id HAVING COUNT(*) 1; -- 校验 2外键有没有孤儿行父表删了但子表还指着 SELECT c.dept_id FROM t_dept c WHERE c.parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM t_dept p WHERE p.dept_id c.parent_id); -- 校验 3序列当前值和表里最大主键是否对齐 SELECT seq_dept_id.NEXTVAL AS next_val, (SELECT MAX(dept_id) FROM t_dept) AS max_id FROM dual;三条查询返回空结果或者next_val max_id说明这套结构是健康的。校验 2 特别容易被忽略外键约束在ENABLE状态下确实能拦住非法插入但如果有人ALTER TABLE ... DISABLE CONSTRAINT之后忘了解开孤儿行就会悄悄积累等到某天报表对不上才发现。还有一个我常用的技巧把每一章的run_all.sql包进一个 shell 脚本按章顺序跑每章跑完spool一份日志出来。#!/bin/bash # 按章跑示例脚本每章输出独立日志失败即停 set -e CONNscott/tiger//127.0.0.1:1521/orcl for ch in ch01_intro ch03_table ch06_plsql; do echo 开始执行 $ch sqlplus -S $CONN EOF log_${ch}.log 21 SET DEFINE OFF SET SERVEROUTPUT ON SIZE UNLIMITED WHENEVER SQLERROR EXIT SQL.SQLCODE ROLLBACK ${ch}/run_all.sql EXIT EOF echo $ch 执行完成日志log_${ch}.log done这份日志的好处是可追溯。哪一章、哪一条语句、报什么错全部落在文件里比事后回忆控制台输出靠谱得多。改动表结构之后重跑对比前后两份日志的差异就能看出改动影响了哪些对象。最后说个教训。最早接触这类随书脚本时我图省事直接在SCOTT用户下跑跑出问题就DROP TABLE重来几次之后SCOTT下的对象乱成一锅粥连示例自带的数据都回不去了。后来我改成每次都单独建一个练习用户跑之前先执行一遍drop_all.sql清空跑完立刻导出备份再动任何一行数据。这套流程看着繁琐但它救过我很多次——尤其是练到触发器那一章一个写错的触发体能悄无声息地把整张表的数据改花没有备份就只能重建库。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →