Oracle 11g实例源程序导入指南:环境对齐、脚本排错与验证库重建
简介:「Oracle 11g从入门到精通(第二版)」配套实例源程序面向数据库初学者与准备进阶的DBA,把书中19章示例集中打包,便于边读边动手验证。内容沿教材脉络展开:从数据库基本概念、Oracle体系结构与安装配置,到SQL查询、表与索引设计,再到视图、存储过程、触发器、游标等数据库对象,以及用户权限、角色管理、备份恢复策略、SQL调优、分区与并行执行等主题,末段还涉及分布式系统与数据仓库等进阶方向。rar压缩包共495个文件、约1.21MB,以txt脚本与学习笔记、java源文件及class编译产物、xml配置文件、jpg运行截图为主,另含bak备份脚本、doc/xls文档表格与dmp、ctl等导入导出样例,可在本地环境复现书中实验。已有405人学习,适合按章节查漏补缺、对照练习。
1. 先别急着导入:《oracle11g从入门到精通(第二版)》实例源程序里装的到底是什么
解压这份《oracle11g从入门到精通(第二版)》的实例源程序压缩包,多数人第一反应是找安装包——其实里面没有数据库安装程序,全是书里每一章配套的.sql脚本、少量.txt数据文件,偶尔夹一两个.dmp导出文件。它解决的是同一个问题:书上的 SQL 和 PL/SQL 示例,你照着敲容易漏掉分号或写错字段名,直接跑配套脚本就能拿到一份和作者屏幕一致的结果集。适合三类人——刚接触 Oracle、想按章节顺序把示例过一遍的新手;要从 11g 迁移到服务端、需要对照样例代码的人工;还有带课的讲师,直接拿脚本当课堂素材。但它是“教材配套”,不是“生产部署包”,脚本的组织方式和健壮性,得你自己补。
2. 环境对齐:让 11g 实例源程序跑在正确的库上
2.1 先确认三个环境参数,再谈导入
拿到脚本之前,先把运行环境固定下来,否则后面所有报错都是干扰项。11g 家族里被当作学习目标最多的版本是 11.2.0.4,教材配套脚本也是按这个版本写的,早期 11.1 的部分语法(尤其是LISTAGG、PIVOT的细节行为)不完全一致。先确认三件事:数据库版本、字符集、监听状态。
-- 以 sysdba 身份登录后执行 SELECT * FROM v$version; -- 关注输出中的 11.2.0.x,教材脚本多数按 11.2 编写 SELECT parameter, value FROM nls_database_parameters WHERE parameter IN ('NLS_CHARACTERSET', 'NLS_LANGUAGE', 'NLS_TERRITORY'); -- 字符集常见为 AL32UTF8 或 ZHS16GBK,这个值决定后面数据文件会不会乱码lsnrctl status # 看监听是否在跑、服务名是否为 orcl lsnrctl services # 确认实例已经动态注册到监听先说字符集这条线。数据库建库时选的字符集是不可随意更改的,ALTER DATABASE CHARACTER SET只能从子集往超集改,从 ZHS16GBK 改到 AL32UTF8 属于“理论可行、实操代价极高”的操作。所以正确姿势是先读NLS_CHARACTERSET,然后让客户端的NLS_LANG去适配它,而不是反过来。库是 AL32UTF8,客户端就设SIMPLIFIED CHINESE_CHINA.AL32UTF8;库是 ZHS16GBK,客户端设SIMPLIFIED CHINESE_CHINA.ZHS16GBK。两端不一致时,DBMS_OUTPUT输出中文会变问号,SPOOL出来的日志也会花,很容易被误判成脚本本身有问题。
监听这条线也常翻车。教材脚本里连接串一般写@orcl,靠的是本地tnsnames.ora里的别名解析;如果你在另一台机器上用 IP 连,就得写成127.0.0.1:1521/orcl这种 EZConnect 形式,否则会撞上ORA-12514: TNS:listener does not currently know of service requested in connect descriptor。这个错的意思是监听活着,但它不认识你请求的服务名——通常是SERVICE_NAME写错,或者实例根本没注册上去。
2.2 建表空间、建用户、授权,三步一个都不能省
教材源码里所有对象默认建在示例用户下(scott、hr,或者书里自定义的用户名)。我不建议直接往scott里塞,一个原因是scott的DEFAULT TABLESPACE通常是USERS,脚本跑多了之后想清理很麻烦;另一个原因是真出问题时你分不清是脚本的锅还是系统 schema 的锅。单独开一个用户,出了问题DROP USER ... CASCADE一删了事。
-- 以 sysdba 执行:先建独立表空间 CREATE TABLESPACE ts_book DATAFILE 'D:\app\oracle\oradata\orcl\ts_book01.dbf' SIZE 200M AUTOEXTEND ON NEXT 50M MAXSIZE 4G SEGMENT SPACE MANAGEMENT AUTO; -- 建用户并绑定默认表空间 CREATE USER book IDENTIFIED BY book DEFAULT TABLESPACE ts_book TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON ts_book; -- 基础角色 + 教材里常缺的几个系统权限 GRANT CONNECT, RESOURCE TO book; GRANT CREATE VIEW, CREATE SYNONYM, CREATE DATABASE LINK TO book;参数逐条说。DATAFILE路径必须和你机器上实际的ORACLE_BASE对上,照抄书上的 Linux 路径放到 Windows 上必报ORA-01119。AUTOEXTEND ON NEXT 50M MAXSIZE 4G是给练手库用的,省得跑到一半ORA-01653报空间不足;生产环境我不这么写,但在学习库里这样最省心。QUOTA UNLIMITED ON ts_book这行最容易被漏——只要漏了,脚本里第一条CREATE TABLE就是ORA-01950: no privileges on tablespace 'TS_BOOK'。
关于RESOURCE角色有个老坑:11g 的RESOURCE里包含CREATE TABLE、CREATE SEQUENCE、CREATE PROCEDURE、CREATE TRIGGER、CREATE TYPE,但不包含CREATE VIEW。所以教材里只要有一章讲视图,脚本执行到那一步必然报ORA-01031: insufficient privileges。上面那句GRANT CREATE VIEW就是补这个洞的,别省。
2.3 用包装脚本批量执行,别手动一条条贴
源程序包里的文件通常按章节命名,几十个.sql。在 SQL*Plus 里一个个@太慢,而且执行顺序错了会连环报错。写一个 Shell 或批处理包装脚本,把所有文件串起来一次跑完。
#!/bin/bash # run_all.sh —— 按依赖顺序执行教材源程序,日志统一落盘 export ORACLE_SID=orcl export NLS_LANG="SIMPLIFIED CHINESE_CHINA.AL32UTF8" LOG=./run_all_$(date +%Y%m%d%H%M).log sqlplus -S book/book@127.0.0.1:1521/orcl <<EOF >> "$LOG" 2>&1 SET DEFINE OFF SET SERVEROUTPUT ON SIZE UNLIMITED SET ECHO OFF SET FEEDBACK ON SET PAGESIZE 200 SET LINESIZE 200 WHENEVER SQLERROR CONTINUE SPOOL run_detail.log APPEND @@sql/01_create_tables.sql @@sql/02_primary_key.sql @@sql/03_foreign_key.sql @@sql/04_index.sql @@sql/05_sequence.sql @@sql/06_insert_data.sql @@sql/07_views.sql @@sql/08_procedures.sql @@sql/09_packages_spec.sql @@sql/10_packages_body.sql @@sql/11_triggers.sql SPOOL OFF EXIT EOF几个参数值得单独讲。SET DEFINE OFF必须放在第一行——教材脚本里如果有字符串包含&(比如地址“研发&测试部”),SQL*Plus 会把它当成替换变量提示你输入,脚本就卡在那里等人敲键盘,无人值守跑批时直接超时。SET SERVEROUTPUT ON SIZE UNLIMITED打开DBMS_OUTPUT缓冲,教材里演示PUT_LINE的章节不加这句什么都看不到,新手常以为存储过程没执行。WHENEVER SQLERROR CONTINUE的意思是出错继续跑下一条,这样一次能收集全部问题;如果你希望出错立即停,改成WHENEVER SQLERROR EXIT SQL.SQLCODE,脚本的退出码会带上 Oracle 错误号,方便写 CI 判断。
@和@@的区别也顺手记一下:@按当前工作目录解析,@@按调用它的那个脚本所在目录解析。把执行脚本放在外层目录、.sql放在sql/子目录时,用@@才不会被路径问题坑到。
3. 脚本目录解剖:DDL、DML、PL/SQL 三类文件的正确打开顺序
3.1 表结构脚本先跑,约束和索引拆开跑
教材配套脚本的组织方式大同小异:建表、加约束、建索引、插数据、建视图和存储过程。直接按文件名首字母排序执行是最容易翻车的做法,因为文件名里带数字前缀不一定反映真实依赖。我一般先做一次人工归类,把那堆.sql分成四层:
| 层级 | 内容 | 典型文件名特征 | 执行顺序 |
|---|---|---|---|
| L1 | 表结构 | create_table、ddl、tab | 1 |
| L2 | 主键、外键、检查约束 | pk、fk、constraint | 2 |
| L3 | 索引、序列、同义词 | index、seq、synonym | 3 |
| L4 | 视图、过程、函数、包、触发器 | view、proc、pkg、trg | 4 |
分层的原因是外键。教材里常见的emp/dept结构,如果建表脚本里内联了REFERENCES dept(deptno),那dept必须先建;而如果脚本顺序反了,ORA-00942: table or view does not exist就会在第一条外键处爆出来。更稳妥的做法是把约束从建表语句里剥出来,单独用ALTER TABLE ... ADD CONSTRAINT追加,这样建表阶段没有互相依赖,后面加约束时即使失败也能单独重跑那一条,不用整表重建。
-- 单独追加约束的好处:可重跑、可单独排查 ALTER TABLE emp ADD CONSTRAINT pk_emp PRIMARY KEY (empno) USING INDEX TABLESPACE ts_book; ALTER TABLE emp ADD CONSTRAINT fk_emp_dept FOREIGN KEY (deptno) REFERENCES dept(deptno) ON DELETE SET NULL; -- 教材示例常用,生产环境务必想清楚再选ON DELETE SET NULL这个子句值得多说一句。教材为了演示“删除部门后员工还在”,常这么写;但它意味着删一条dept记录会静默修改 N 条emp记录的deptno,而且这个修改不会触发你写的AFTER DELETE触发器里的业务逻辑。自己要往真实数据上套的时候,先把这行改成ON DELETE RESTRICT(Oracle 里就是省略ON DELETE子句)或干脆不加外键。
3.2 数据装载:编码和分隔符是两个必踩的坑
教材里的演示数据一般有两种形式:INSERT语句脚本,或者.txt/.csv数据文件配一个 SQL*Loader 控制文件。前者简单,@insert_data.sql就完事;后者才是问题高发区。
-- emp.ctl:SQL*Loader 控制文件示例 LOAD DATA INFILE 'emp.csv' BADFILE 'emp.bad' DISCARDFILE 'emp.dsc' APPEND INTO TABLE emp FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' TRAILING NULLCOLS ( empno, ename, job, mgr, hiredate DATE "YYYY-MM-DD", sal, comm, deptno )TRAILING NULLCOLS必须加。教材数据里comm(奖金)这一列大量为空,不加这句的话,空字段会被当成“行结束”,报SQL*Loader-350: Syntax error,或者整行错位灌进去,最后表里deptno列出现的全是工资数字——这种脏数据不会报错,只会让你在后面的多表连接查询里得到莫名其妙的结果,属于最阴的一类问题。DATE "YYYY-MM-DD"的掩码要和文件里实际格式严格对应,掩码写错不会报错,只会把hiredate灌成0001-01-01之类。
编码这条线更隐蔽。数据文件如果是 GBK 存的,而你的NLS_LANG设成了AL32UTF8,ename里的中文会变成乱码;反过来也一样。判断方法很简单:装载完成后SELECT ename, LENGTH(ename) FROM emp WHERE empno = 7900;,中文正常的话LENGTH应该等于实际汉字数,出现一堆问号或长度翻倍,就是编码对不上。修复方式不是改数据文件,而是执行前把NLS_LANG调到与文件编码一致。
3.3 PL/SQL 对象:spec 必须先于 body,错误要去 USER_ERRORS 里查
教材后半部分开始讲存储过程、函数、包、触发器。这些文件执行时最大的特点是:SQL*Plus 的报错信息极其吝啬。你跑一个CREATE OR REPLACE PROCEDURE,屏幕上可能只出现:
Warning: Procedure created with compilation errors.这时候很多人第一反应是回滚看脚本,其实错在哪,数据库已经记下来了。
-- 查当前用户下所有编译失败的对象 SELECT object_name, object_type, status FROM user_objects WHERE status = 'INVALID' ORDER BY object_type, object_name; -- 查具体错误行号和原因 SELECT name, type, line, position, text FROM user_errors WHERE name = 'RAISE_SALARY' ORDER BY type, sequence;USER_ERRORS里的LINE是相对于该对象源码的行号,POSITION是列号,TEXT会告诉你PLS-00103: Encountered the symbol ";"这类信息。绝大多数教材脚本编译失败,原因就那么几种:一是文件从 PDF 或网页复制时混进了全角空格或中文引号,Oracle 的语法分析器不认识,报的位置往往还偏一行;二是包(PACKAGE)的声明和包体(PACKAGE BODY)分在两个文件里,顺序跑反了,包体编译时找不到声明;三是脚本里的END;后面少了单独一行的/,导致 PL/SQL 块压根没提交给数据库执行。
第三点单独强调一下。SQL*Plus 里执行 PL/SQL,END;之后必须跟一个独占一行的/,或者一个空行。教材配套脚本一般是对的,但从书里手抄的时候就很容易漏,表现出来是:光标停在2提示符上不动,你敲什么它都不执行。这不是卡死,是它还在等 PL/SQL 块结束符。
4. 典型实例拆解:从演示用户到存储过程、触发器的验证
4.1 先把 scott 和 hr 演示用户跑通
Oracle 11g 安装完成后,scott默认是锁定状态,hr示例 schema 默认不创建。教材里凡是用emp/dept举例的章节,都默认你已经把scott打开了。
-- 解锁 scott 并重设密码 ALTER USER scott ACCOUNT UNLOCK; ALTER USER scott IDENTIFIED BY tiger; -- 验证登录与表数据 CONN scott/tiger@orcl SELECT COUNT(*) FROM emp; -- 正常应为 14 SELECT COUNT(*) FROM dept; -- 正常应为 4hr要麻烦一点,它不在默认建库流程里,需要跑安装介质里的hr_main.sql。这个脚本在$ORACLE_HOME/demo/schema/human_resources/目录下,执行时会用替换变量问你四个参数:hr用户的密码、默认表空间、临时表空间、日志输出目录。这里正是SET DEFINE OFF会害你的地方——这个脚本依赖替换变量,所以执行前反而要SET DEFINE ON。我见过有人全局在glogin.sql里写了SET DEFINE OFF,然后hr_main.sql一跑就报ORA-00922: missing or invalid option,查了半天。
-- 在 hr_main.sql 所在目录执行,注意这里要保持 DEFINE 打开 SET DEFINE ON @hr_main.sql -- 依次输入:hr 密码 / users / temp / 日志目录跑完之后CONN hr/hr@orcl,SELECT table_name FROM user_tables;应该能看到 7 张表。这一步通过,说明字符集、权限、表空间三条线都是通的,后面教材里的复杂示例才有个可靠的基线。
4.2 一个存储过程实例:参数模式、游标、异常要一起看
教材讲 PL/SQL 的章节几乎一定会给一个“批量调薪”的过程。这类例子看着简单,但把IN/OUT参数、显式游标、FOR UPDATE、自定义异常、RAISE_APPLICATION_ERROR全串起来了,是理解整套机制的抓手。
CREATE OR REPLACE PROCEDURE raise_salary( p_deptno IN NUMBER, p_percent IN NUMBER DEFAULT 5, p_rows OUT NUMBER ) AS v_cnt NUMBER := 0; -- 显式游标加 FOR UPDATE,锁定待修改的行,避免并发下丢失更新 CURSOR c_emp IS SELECT empno, sal FROM emp WHERE deptno = p_deptno FOR UPDATE OF sal NOWAIT; e_invalid_pct EXCEPTION; -- 自定义异常,业务规则校验用 BEGIN IF p_percent <= 0 OR p_percent > 50 THEN RAISE e_invalid_pct; END IF; FOR r IN c_emp LOOP UPDATE emp SET sal = ROUND(sal * (1 + p_percent / 100), 2) WHERE CURRENT OF c_emp; -- 定位到游标当前行,比再写一遍主键快 v_cnt := v_cnt + 1; END LOOP; p_rows := v_cnt; COMMIT; EXCEPTION WHEN e_invalid_pct THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20001, '调薪比例必须在 0 到 50 之间'); WHEN OTHERS THEN ROLLBACK; RAISE; -- 保留原始错误栈,别吞异常 END; /参数说明几个点。p_percent IN NUMBER DEFAULT 5给了默认值,调用时写raise_salary(20)就走 5%。p_rows OUT NUMBER是出参,调用方必须先声明变量接住:
SET SERVEROUTPUT ON DECLARE v_n NUMBER; BEGIN raise_salary(20, 10, v_n); DBMS_OUTPUT.PUT_LINE('影响行数:' || v_n); END; /FOR UPDATE OF sal NOWAIT是关键。不加的话,两个会话同时调这个过程中会互相等待,表现是“程序卡住不返回”;加了NOWAIT则立即抛ORA-00054: resource busy and acquire with NOWAIT specified,你能马上知道是锁冲突而不是性能问题。WHERE CURRENT OF c_emp依赖游标的FOR UPDATE,如果游标没加这个子句却写了CURRENT OF,编译阶段就报ORA-00904。
RAISE_APPLICATION_ERROR的错误号必须落在-20000到-20999之间,这个区间的错误是用户自定义的,不会和 Oracle 系统错误号冲突。教材里有些例子图省事写-1,跑起来报ORA-00001: unique constraint violated,把自己绕进去。
4.3 触发器实例:行级还是语句级,先想清楚再写
教材讲触发器那章会同时给行级(FOR EACH ROW)和语句级两种例子。区别很直白:行级触发器对每一条受影响的记录触发一次,能用:NEW/:OLD伪记录;语句级触发器整条语句只触发一次,拿不到行数据,通常用于记录操作日志或做权限校验。
-- 行级触发器:员工薪资变动审计 CREATE OR REPLACE TRIGGER trg_emp_sal_audit AFTER UPDATE OF sal ON emp FOR EACH ROW WHEN (OLD.sal <> NEW.sal) -- WHEN 子句里不能写冒号 DECLARE v_user VARCHAR2(30) := USER; BEGIN INSERT INTO emp_sal_audit(empno, old_sal, new_sal, chg_time, chg_user) VALUES (:OLD.empno, :OLD.sal, :NEW.sal, SYSDATE, v_user); END; /两个细节容易错。第一,WHEN子句里的OLD.sal和NEW.sal不带冒号,但BEGIN ... END块里必须带:OLD/:NEW,这个不一致是 Oracle 的历史遗留,教材上常只写一种,抄的时候要对齐。第二,AFTER UPDATE OF sal里的OF sal限定了只有sal列被更新时才触发,不加的话UPDATE emp SET comm = 100也会触发审计,表里就会多出一堆old_sal = new_sal的垃圾记录。
审计表本身的建法也有讲究。emp_sal_audit别加外键指向emp,否则删除员工时会因为审计记录存在而失败;也别在审计表上再建触发器,容易触发递归。验证方式很直接:
UPDATE emp SET sal = sal + 100 WHERE empno = 7369; SELECT * FROM emp_sal_audit WHERE empno = 7369 ORDER BY chg_time DESC; ROLLBACK; -- 练手环境记得回滚,别把示例数据改花了5. 避坑与排查:源程序跑不起来的五类现场
5.1 ORA-00942:表或视图不存在,八成不是表真的不存在
现象:脚本前半段明明建表成功了,执行到视图或过程时报ORA-00942: table or view does not exist。
原因:三种可能。一是对象建在了另一个 schema 下,你当前登录的用户没有权限也没建同义词,Oracle 在解析时按“当前 schema → 同义词 → 公共同义词”的顺序找,找不到就报这个错。二是对象名用了双引号包裹的小写或混合大小写形式("Emp"),而查询时写的是emp,两者在数据字典里是不同的名字。三是脚本里用了&table_name这种替换变量,SET DEFINE OFF之后没被替换,实际执行的 SQL 里带着&字符,解析失败。
解决:先SELECT owner, object_name FROM all_objects WHERE object_name = 'EMP';确认对象到底在哪个 schema;大小写问题用SELECT table_name FROM user_tables WHERE UPPER(table_name) = 'EMP';反查;替换变量问题检查执行脚本开头有没有SET DEFINE OFF,如果有而这个脚本恰恰需要变量,就临时改回ON。
5.2 ORA-01031:权限不足,角色权限在存储过程里会失效
现象:在 SQL*Plus 里直接SELECT * FROM hr.employees没问题,但把这个查询写进存储过程,编译通过、执行时报ORA-01031: insufficient privileges。
原因:Oracle 的DEFINER'S RIGHTS存储过程(默认模式)在执行时不启用通过角色获得的权限,只认直接授予用户的系统权限和对象权限。你在 SQL*Plus 里能查,是因为CONNECT、RESOURCE这些角色生效了;过程里这些角色不生效。
解决:两种选法。给用户直接授权GRANT SELECT ON hr.employees TO book;(不用角色),或者把过程改成AUTHID CURRENT_USER,让它在调用者的权限上下文中执行。教材示例通常不涉及跨 schema,但如果你的练习是把书上的脚本挪到自己建的库上跑,这个坑几乎必踩。
5.3 PLS-00103:遇到符号“;”,先查全角字符
现象:一段看起来完全正确的存储过程,编译报PLS-00103: Encountered the symbol ";" when expecting one of the following: ...,报的行号指向一行毫无问题的语句。
原因:从 PDF 或网页复制代码时混入了全角空格(U+3000)、中文全角分号(;)、中文引号(“ ”)。这些字符在编辑器里和半角字符长得几乎一样,但 Oracle 的语法分析器不认。报错行号偏一行也正常,因为解析器是在读到下一个 token 时才判断前一个有问题。
解决:用编辑器打开文件,正则搜[\u3000\uFF1B\u201C\u201D\uFF08\uFF09]批量替换。更稳的办法是用od -c file.sql | grep -n '343 200 200'这类命令查一遍字节,或者干脆把脚本先用iconv -f GBK -t UTF-8转一次再统一处理。
5.4 ORA-28040 和 ORA-01017:版本错配与密码大小写
现象:用 11g 的客户端连高版本服务端,报ORA-28040: No matching authentication protocol;或者明明密码对,却报ORA-01017: invalid username/password; logon denied。
原因:前者是客户端和服务端的认证协议版本不匹配,11g 客户端默认只协商到较低版本的协议;后者常见于服务端开启了密码大小写敏感(SEC_CASE_SENSITIVE_LOGON),而教材脚本里写的密码是全小写,或者你在不同工具里输入时首字母大写了。
解决:版本错配在客户端sqlnet.ora里加SQLNET.ALLOWED_LOGON_VERSION_SERVER=8和SQLNET.ALLOWED_LOGON_VERSION_CLIENT=8(服务端和客户端各配一处),然后重启监听。密码问题用ALTER USER book IDENTIFIED BY "book";重新设一次,注意这里加了双引号,密码就按字面大小写生效。
5.5 脚本跑一半中断,DDL 已经隐式提交
现象:批量执行到第 6 个脚本报错退出,你以为整个事务回滚了,重新跑一遍发现第 1 到 5 个脚本的表已经存在,报ORA-00955: name is already used by an existing object。
原因:Oracle 的 DDL 语句会隐式提交当前事务,CREATE TABLE执行成功的那一刻就已经落盘了,跟你有没有COMMIT无关。WHENEVER SQLERROR EXIT只是终止了客户端继续发送后续语句,前面已经执行的 DDL 一个都不会回滚。
解决:所有批量脚本都写成幂等的——建表前先判断存在性,或者执行前先跑一个drop_all.sql清场。清场脚本用 PL/SQL 动态生成最省事:
-- drop_all.sql:按依赖倒序删除当前用户下的表 BEGIN FOR r IN (SELECT table_name FROM user_tables WHERE table_name NOT LIKE 'BIN$%' -- 跳过回收站对象 ORDER BY table_name DESC) LOOP EXECUTE IMMEDIATE 'DROP TABLE ' || r.table_name || ' CASCADE CONSTRAINTS PURGE'; END LOOP; END; /CASCADE CONSTRAINTS顺带删掉外键,PURGE不进回收站——不加PURGE的话,反复重建几次之后USER_TABLES里查不到,但表空间里全是BIN$开头的回收站对象,磁盘空间照占,新手常在这上面白折腾半天。
6. 进阶:把教材实例改造成能反复重建的验证库
教材脚本是“一次性演示”思路,跑通了就不管了;但你自己要用它练手、做实验、验证某个索引到底有没有生效,就需要一套可重复重建的结构。我的做法是把源程序包拆成两个目录:ddl/放结构,dml/放数据,中间夹一个drop_all.sql,形成“清场 → 建结构 → 灌数据 → 验证”的闭环,一条命令就能回到初始状态。
在此基础上再补一张验证清单,每次改完脚本跑一遍,比肉眼比对结果可靠得多:
| 验证项 | 检查语句 | 期望结果 |
|---|---|---|
| 对象数量 | SELECT object_type, COUNT(*) FROM user_objects GROUP BY object_type; | 与教材章节对象数一致 |
| 无效对象 | SELECT object_name FROM user_objects WHERE status='INVALID'; | 返回空 |
| 表行数 | SELECT table_name, num_rows FROM user_tables; | 与示例数据量一致 |
| 约束完整性 | SELECT constraint_name, status FROM user_constraints WHERE status<>'ENABLED'; | 返回空 |
| 索引是否被用 | EXPLAIN PLAN FOR ...; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY); | 执行计划走 INDEX RANGE SCAN |
最有价值的是最后一项。教材讲索引那章通常只告诉你“建索引能加速查询”,但不会告诉你什么时候优化器根本不走索引。自己动手验证一次,比看十页书管用:
EXPLAIN PLAN FOR SELECT * FROM emp WHERE UPPER(ename) = 'SMITH'; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, NULL, 'BASIC +COST'));你会看到TABLE ACCESS FULL。原因是在ename上做函数运算,普通 B*Tree 索引失效了,得建函数索引CREATE INDEX idx_emp_upper_ename ON emp(UPPER(ename));才会走。这类“教材没写、但一到真实场景就撞上”的细节,才是配套源程序真正的用法——不是照着敲一遍看它对不对,而是拿它当基座,改一个条件,看执行计划和结果怎么变。
从那以后我每次拿到别人整理的.sql包,都强制先跑一遍drop_all.sql再全量重建,哪怕对方说“已经测过了”。教材配套源程序尤其如此,它是为了配合书上的讲解顺序写的,不是为了一次性跑通设计的,中间藏几个顺序依赖和半角全角问题太正常了。希望帮到你。
本文还有配套的精品资源,点击获取