第一部分把基础查询讲完之后后台留言最多的不是“哪个关键字记不住”而是“真正操作的时候怎么才能不出事”。这批读者其实已经能把 SELECT、WHERE、JOIN 写明白但一碰到真实环境就露怯为什么别人 UPDATE 不卡我的 UPDATE 一执行就锁住为什么同样一条 SQL在测试库 0.1 秒上生产就要跑 10 秒为什么明明点了提交第二天数据还是被回退了这些问题靠背诵语法解决不了。所以“Oracle 数据库操作基础2”这个主题我想换个讲法不追着语法跑而是把增删改、事务、锁、表结构、索引、执行计划这些日常高频操作串起来讲清楚每一步在数据库内部到底发生了什么又该按什么顺序操作最稳。如果你已经过了“能写出 SQL”的阶段接下来想真正管住一个库或者至少别再被自己写出来的操作坑到那这篇文章就是你需要的。1. 第二部分到底该补什么先搞清楚技能边界1.1 第一部分和第二部分的分界线第一部分的内容本质上是只读操作。你查询、连接、过滤、分组数据本身没有变化数据库也不会因为你多跑几条 SQL 就产生副作用。那时候犯错的成本很低最多是查询结果不对再改一遍就好。第二部分完全不同。从 INSERT 开始你就在改变数据库状态。改数据意味着三个连锁反应数据文件要写、UNDO 要记录、REDO 要生成同时行锁要加上。任何一步没处理好轻则影响自己的会话重则把整个表的并发操作拖住。我见过一个真实案例某内部系统的维护人员写了一条 UPDATE忘记加 WHERE 条件把一张几百万行的配置表全部更新了。等他反应过来想回滚事务已经提交了。这种事故在入门阶段几乎人人都可能遇到区别在于懂行的人会在执行前多做几步确认。第二部分的内容就是围绕“改动数据”这个核心展开的。你要学会的不只是每个语句怎么写而是怎么安全地把数据改对改完还能随时说清楚发生了什么。1.2 第二部分围绕三条主线展开我把要讲的内容归结成三条主线方便你对照自己的短板。第一条线是数据操作。INSERT、UPDATE、DELETE、MERGE 这些 DML 的完整用法以及批量操作、意外误改、TRUNCATE 与 DELETE 的区别这些实操细节。80% 的生产事故都发生在这一条线上。第二条线是并发控制。事务什么时候提交、锁什么时候释放、为什么别人会被你阻塞、死锁是怎么产生的。这条线理解不了你在团队协作环境里就是个移动炸弹。第三条线是设计意识。表结构字段怎么选、索引该不该建、视图和序列怎么用、执行计划为什么长这样。这决定了你写的 SQL 在数据量上来以后还能不能跑得动。我在下面的内容里按这套顺序展开每节都尽量给出能直接抄走的 SQL 和排查思路。2. DML 细节与批量操作增删改的正确姿势2.1 插入数据单行、多行与批量插入INSERT 基础语法本身不难大部分人都会写。真正拉开差距的是多行插入的手法。单行插入我就不展开了直接看多行。Oracle 里批量插多行数据最高效的静态写法是INSERT ALL。它能把多条记录一次性插进一个表或者同时插进多个表INSERT ALL INTO emp_2019 (emp_id, emp_name, salary) VALUES (1001, 张明, 8000) INTO emp_2019 (emp_id, emp_name, salary) VALUES (1002, 李华, 8500) INTO emp_2020 (emp_id, emp_name, salary) VALUES (1003, 王强, 9000) SELECT * FROM DUAL;注意最后的SELECT * FROM DUAL不是多余的。Oracle 的 INSERT ALL 语法要求带一个 SELECT 子查询作为数据来源DUAL 表是凑数的但缺了它整个语句就不成立。这个格式如果你第一次见最好自己敲一遍因为实际工作中遇到的脚本经常这么写。如果要把一张表的数据搬进另一张表用INSERT INTO SELECT更直接INSERT INTO emp_bak (emp_id, emp_name, salary) SELECT emp_id, emp_name, salary FROM emp WHERE hire_date DATE 2020-01-01;这里要提醒一件事大批量插入时别傻乎乎一条一条地 INSERT 循环提交。我见过不少新手用 PL/SQL 循环插入几万行跑了十分钟还没结束。原因是每执行一次 INSERTOracle 都要做一次事务日志记录和一致性检查。批量改写为集合操作或者分批提交性能差距可以是几十倍。有一种做法是每攒够 500 行 COMMIT 一次平衡锁持有时间和回滚段压力这个思路在生产环境里很实用。2.2 UPDATE 的三步确认法与 RETURNINGUPDATE 是所有 DML 里最容易出事故的语句因为它动作大、影响面广。我自己养成了一个三步确认法也建议你直接抄走。第一步先用 SELECT 把要改的数据查出来数一数有多少行。比如你有这样一条 UPDATEUPDATE emp SET salary salary * 1.1 WHERE department_id 30;执行之前先跑SELECT COUNT(*) FROM emp WHERE department_id 30;如果这个返回值跟你预期不符说明 WHERE 条件写得有问题赶紧停下来查。真正执行时再打开返回行数确认。SQL Developer 和 PL/SQL Developer 执行完 DML 都会显示影响行数看它是否等于你刚才 SELECT 的数量。第二步检查 WHERE 条件里有没有潜在陷阱。比如空值问题WHERE department_id IS NULL和WHERE department_id NULL完全是两回事。后者永远查不到数据但不会报错你只会发现 UPDATE 影响了 0 行。如果本来想改 200 行结果只改了 0 行这种问题隐蔽又危险。第三步确认要改哪几列。很多人写 UPDATE 时习惯把整行所有字段都重新赋值用别的表的数据覆盖当前数据结果把不该动的字段也冲掉了。规范做法是 SET 子句里只出现确实需要变更的字段别图省事写SET (col1, col2, ...) (SELECT ...)给所有列赋值除非你真的要整体替换。另外说一个不一定常用的语法RETURNING它可以在 DML 执行后把被改动的值返回出来UPDATE emp SET salary salary * 1.2 WHERE emp_id 1001 RETURNING salary INTO :new_salary;这个语法在 PL/SQL 块里配合绑定变量特别好用可以省掉一次额外的 SELECT。工作中需要立刻拿到新值做后续逻辑时就靠它。2.3 MERGE更新插入的总开关MERGE 俗称为 UPSERT意思是一行数据存在就更新不存在就插入。这是个高频需求每天从外部系统同步一批数据进来有变化的改掉新增的插进去。一个典型写法如下MERGE INTO emp e USING new_emp_data n ON (e.emp_id n.emp_id) WHEN MATCHED THEN UPDATE SET e.salary n.salary, e.emp_name n.emp_name WHEN NOT MATCHED THEN INSERT (emp_id, emp_name, salary) VALUES (n.emp_id, n.emp_name, n.salary);用 MERGE 有几点要注意。ON 条件后面的匹配列必须是业务上真正的唯一标识一般是主键或唯一键。如果匹配列不唯一可能出现 ORA-30926 错误意思是源表里的多行同时匹配了目标表同一行Oracle 不知道听谁的。遇到这个错误第一反应应该是检查源数据有没有重复。MERGE 的性能通常比“先 UPDATE 再 INSERT”的组合快因为只需要扫描一次目标表。数据量越大这个优势越明显。但也要保证 USING 子查询里做了必要的过滤别把无关数据全捞进来。2.4 DELETE 和 TRUNCATE后果完全不同删数据的两种方式经常被混用但它们的机制差异非常大。DELETE 是 DML一行一行删除会产生大量 UNDO 和 REDO支持回滚删除后表的高水位线不降低所以表空间不会自动收缩。TRUNCATE 是 DDL直接把表的数据段释放掉速度极快资源消耗极小但几乎不能回滚。在高水位线的处理上TRUNCATE 会把表重置到空状态后续插入的物理位置从头开始。两者使用场景完全不同。清空一张表、不想保留数据可以用 TRUNCATE条件删除部分数据或者需要回滚保底必须用 DELETE。我之前接手过一个同事留的脚本里面清理历史数据用的是 TRUNCATE可是业务逻辑是按日期删除某几个月的数据。执行完才发现把整张表清空了当时差点晕过去。所以说清楚了如果删除条件带 WHERE就老老实实用 DELETE确定要整表清空才考虑 TRUNCATE。就算 TRUNCATE 事后在某些恢复场景下可能找回来那也不是常规手段不能作为默认方案。3. 事务、提交与锁并发环境中的生存法则3.1 COMMIT 和 ROLLBACK 不是随手点着玩事务是数据库里最核心的抽象。一次事务代表一组不可分割的操作要么全部成功要么全部不做。理解事务的关键在于COMMIT 不只是“保存一下”而是告诉数据库这些改动可以永久落盘了ROLLBACK 是撤销当前事务内所有未提交改动。很多初学者容易误解的是“数据在会话里看得见就等于已经保存”。在 Oracle 里这是不对的。你执行完 UPDATE 不提交你的会话能看到改动别的会话默认看不到而且这些改动还停在 UNDO 和内存中没真正稳定。一旦会话异常断开会自动回滚你忙活了半小时的数据说没就没。我在实际项目中习惯把 COMMIT 的时机看作一个显式设计而不是随手触发的动作。批量操作时通常每处理 500 到 1000 行提交一次。这样既不会让事务持续时间过长也能避免锁和回滚段被撑满。事务太大会把 UNDO 表空间用爆这在我处理过的案例里很常见。等一个事务涉及多张表、需要保持一致性时就必须推迟到所有 DML 都成功后再统一 COMMIT。关于什么时候该提交我给一个简单判断标准你觉得“当前这组操作已经完整失败不能留下半截状态”就提交只要你还在继续改就别急着提交。3.2 行锁、阻塞与死锁的生产现场Oracle 默认的行锁机制是写操作会锁住被修改的行直到事务结束。另一个会话想改同一行就只能等待。很多人第一次遇到阻塞都会很困惑我明明只是执行了一条 UPDATE为什么半小时没反应这时候十有八九是那行数据被另一个未提交的事务锁住了。我把这种场景模拟成两个会话就更直观了会话 A 执行UPDATE emp SET salary 9000 WHERE emp_id 1001;不提交。会话 B 执行同一条 UPDATE卡住。B 在等待 A 提交或回滚A 不结束B 就一直等。排查阻塞现场时我最常用的查询是SELECT sid, serial#, username, blocking_session, event, wait_class FROM v$session WHERE blocking_session IS NOT NULL;这个查询能直接看到谁阻塞了谁。拿到阻塞者的 sid 和 serial# 后先用业务口径和对方沟通确认不是正常的长事务再考虑终止会话ALTER SYSTEM KILL SESSION sid,serial# IMMEDIATE;注意别杀到自己也别随便杀你以为没用的会话。我在环境里见过有人把后台跑批任务的会话杀掉导致批量任务失败重跑。处理阻塞的优先级永远是先沟通、再确认、最后才动手。死锁则是另一种情况。两个会话各自持有对方需要的资源互相等待谁也动不了。Oracle 检测到死锁后会让其中一个会话报 ORA-00060 并回滚它的语句另一个会话继续执行。生产环境里死锁大多由应用层加锁顺序不一致导致修应用代码比在数据库层面处理更有效。3.3 读一致性靠什么维持UNDO 的作用Oracle 有个特性叫读一致性简单说跑一个查询时你看到的是某个时间点上的数据快照而不是其他未提交事务正在改的中间状态。这个能力靠的是 UNDO 表空间。UNDO 里保存了数据修改前的旧版本。当一个长查询开始后其他会话改动并提交了某些行Oracle 发现你要读取的版本已经被覆盖就会去 UNDO 里找旧版本拼接出来给你看。整个过程对应用透明。但这个机制有代价。如果长查询运行时间过长UNDO 里的旧版本可能被新事务覆盖这时就会报 ORA-01555 快照过旧。处理思路一般是增大 UNDO 表空间的保留时间或尺寸但更根本的办法是缩短事务和查询的时长别让一条查询跑到几十分钟。理解 UNDO 还有个好处你能明白为什么执行大事务前要掂量一下系统多少并发在跑。过度消耗 UNDO 不只是你自己的问题还可能拖累其他会话的一致性读。4. 表结构、约束与表空间建表之前要做的几个决定4.1 字段类型选择的五个常见误区建表看似简单字段类型选错了后面改起来痛苦万分。我盘点几个常见误区都是踩过坑才总结出来的。第一状态、类型这类固定长度短字符串用 CHAR 而不用 VARCHAR2 的场景其实不多。大多数业务字段用 VARCHAR2 更灵活只在值长度绝对固定时才用 CHAR。很多人习惯随手 CHAR(10)结果存储空间浪费不说比较时还得处理尾部空格。第二日期绝对不要用 VARCHAR2 存。日期要用 DATE需要带时区用 TIMESTAMP WITH TIME ZONE。字符串存日期带来的问题非常多排序不对、区间查询靠转型、时区混乱尤其在做报表统计的时候全是雷。第三金额字段用 NUMBER 而不是 INTEGER。比如NUMBER(14,2)表示整数部分 12 位和小数 2 位可以避免浮点误差。不要用 NUMBER 不带精度存所有数值那会让约束形同虚设。第四大文本字段 CLOB 的读写有限制。CLOB 不能直接参与很多字符串函数比较和排序也麻烦。能用 VARCHAR2 塞下的短描述就别图省事用 CLOB。第五标识类字段要区分是业务编号还是代理键。代理键常用 NUMBER 加序列业务编号可能是字符加编号的组合选错类型会导致查询性能下降和应用层转换逻辑复杂。4.2 约束的正确姿势命名与组合使用约束就是数据库层面强制数据完整性的规则一共有主键、唯一、非空、检查、外键这几类。很多人建表时不写约束名像下面这样CREATE TABLE emp ( emp_id NUMBER PRIMARY KEY );这样不是不行但数据库会自动生成一串乱码般的约束名报错提示也看不懂。正确做法是显式命名CREATE TABLE emp ( emp_id NUMBER CONSTRAINT pk_emp_id PRIMARY KEY, emp_name VARCHAR2(50) NOT NULL, salary NUMBER(14,2) CONSTRAINT ck_emp_salary CHECK (salary 0), dept_id NUMBER, CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(dept_id) );约束命名有规律可循公司内部一般常见pk_、uk_、ck_、fk_前缀加表名加列名方便排查。名一乱后续要查哪个约束挡住了数据会非常费劲。组合使用的场景也很常见。一张订单表里业务上要求同一个客户不能重复下相同订单编号那就建唯一约束。同时订单金额不允许负数这是检查约束。两者是独立维度可以叠加在同一张表上。外键约束我多说一句它保护数据关系但有代价。父表被删时Oracle 要检查子表有没有引用这在高并发下会引起额外锁竞争。有些高吞吐系统会故意不用物理外键靠应用层保证关系。这个取舍没有绝对标准但你要清楚它的代价别默认什么表都加外键。4.3 表空间设计提前规划比事后扩容重要表空间是 Oracle 中组织数据文件的逻辑容器。表、索引、UNDO 都物理存放在某个表空间里。很多初学者不关心表空间直到生产报“无法扩展”才着急这时往往已经很被动。一个相对合理的规划是业务数据、索引数据、UNDO 数据分开存放至少不要把表和索引堆在同一个自动扩展不受限制的表空间里。建表空间时把初始大小、增长策略都定好CREATE TABLESPACE app_data DATAFILE /u01/app/oracle/oradata/ORCL/app01.dbf SIZE 2G AUTOEXTEND ON NEXT 512M MAXSIZE 32G SEGMENT SPACE MANAGEMENT AUTO;这个例子里的路径要根据你实际环境的 ORACLE_SID 调整别直接照抄。SEGMENT SPACE MANAGEMENT AUTO是推荐设置它让段内部的空闲空间管理自动化不然手动管理空闲列表会让 DBA 多出一堆工作。表空间设计还要考虑一个维度预计增长速度。一张每天涨 1G 的表按 2G 初始大小建表空间肯定撑不了几天。按峰值估算后再乘以一个安全系数比如余量 30% 到 50%这样就不至于频繁扩容。4.4 修改表结构时容易被忽略的代价业务变动导致要加列、改列类型这是常态。但 ALTER TABLE 不是零成本的。比如给一张千万行的大表加一个带默认值的列旧版本 Oracle 会引发重写和数据更新锁表时间很长。我在某次版本迭代里就因此吃过亏一条简单的 ALTER 让整张核心表不可用了几十分钟。遇到这种情况可以先把列加为允许为空再用分批 UPDATE 回填默认值或者干脆让应用层处理默认值。改列类型一般需要表的行数据做转换数据量大时同样要谨慎。还有一点修改列名和删除列同样会重建数据字典生产环境里执行前一定要看表数据量和当前锁情况避开业务高峰期。这些细节没人提醒的话容易踩到很深的坑。5. 索引、视图、序列高频数据库对象的使用与误区5.1 B 树索引理解原理才知道什么时候不该建B 树索引是 Oracle 里最常见、也最可靠的索引结构。它类似一本书的目录把索引列的值和对应的行位置存在一个平衡树里查找时从根一路走到叶子而不是整本书翻一遍。建索引很简单CREATE INDEX idx_emp_dept ON emp(department_id);难的是知道什么时候建、什么时候别建。我的判断标准有三条第一WHERE 子句里频繁用到的列第二数据量大并且查询结果占比小第三该列的数据区分度高也就是不同值很多。反过来小表不需要索引全表扫描反而更快。列值重复率高到查询结果占全表很大比例比如性别列只有男女两种值索引几乎没用。经常被 UPDATE 的列建索引也要谨慎因为每次修改索引列都要额外维护索引结构。另外提醒一个很容易忽略的点对列做函数处理后普通索引就用不上了。比如SELECT * FROM emp WHERE UPPER(emp_name) ZHANG;如果 emp_name 上只有普通索引这条查询会全表扫描。解决办法是建基于函数的索引CREATE INDEX idx_emp_upname ON emp(UPPER(emp_name));索引不是越多越好。每张索引都占用空间拖慢 DML 性能。合理的做法是从慢查询里找证据确证某列被高频过滤再加索引。5.2 位图索引不到特定场景不要碰位图索引在 Oracle 里是一种特殊索引它用位图数组表示列值和行的映射关系在低频更新的数据仓库里处理多列组合过滤相当高效。但它有一个致命弱点DML 并发更新会锁住大量的位图片段所以 OLTP 系统里基本不碰。我看到过有人对订单状态列建位图索引结果业务一高峰并发更新频繁直接引发锁竞争和 ORA-00060 死锁。位图索引的正确打开方式是在数据仓库、只读报表表里对维度列建立的组合过滤场景。普通业务系统里优先考虑 B 树索引。5.3 视图好用但别把性能问题藏进去视图本质是一条保存起来的查询本身不占存储空间。它能简化复杂查询也能作为安全层把敏感列隐藏起来。创建一个常用视图非常容易CREATE OR REPLACE VIEW v_emp_dept AS SELECT e.emp_id, e.emp_name, e.salary, d.dept_name FROM emp e, dept d WHERE e.dept_id d.dept_id;之后查订单数据直接SELECT * FROM v_emp_dept就行。但视图有一个常见认知误差它不产生任何隐藏优化你对视图做的过滤会下推到视图内部的 SQL 里执行也可能不会。某些写法下查询视图带 WHERE 条件底层表索引还是能用但涉及多级视图嵌套时优化器可能无法智能下推。所以如果发现某个视图查询特别慢不要“感觉”它应该快直接看执行计划确认过滤条件下没下推再决定是否重写视图逻辑或者把它改成物化视图。物化视图会真正存储结果能大幅提升查询速度但要额外管理刷新不是银弹。5.4 序列缓存、缺口与并发分配序列是用来生成连续数字的对象一般做为主键值来源。它本身不绑定表任何会话都可以调用NEXTVAL和CURRVAL。CREATE SEQUENCE seq_order_id START WITH 1000 INCREMENT BY 1 CACHE 50;上面的 CACHE 50 表示数据库会在内存里预生成 50 个序列值提高并发取值的效率。NOCACHE 则在每次取值都要更新数据字典性能更差。所以我建议除非特殊原因保留 CACHE大小往 100 设置通常没问题。但缓存带来一个副作用实例崩溃时内存里未使用的序列值会丢失导致序列出现数字缺口。不要试图修复缺口因为序列本身不承诺连续只承诺递增和唯一。实际工作中我喜欢在插入语句里显式写序列值而不是依赖触发器。这样可以看清楚数据来源批量导入时也更可控。搬数据时还需要手动同步序列的当前值ALTER SEQUENCE seq_order_id RESTART START WITH 50000;不然新插入的数据就可能碰到主键冲突这是迁移后常见的小坑。6. 从执行计划看性能慢查询自查的基本功6.1 用 EXPLAIN PLAN 看到 SQL 的“地图”查询慢不可怕可怕的是不知道慢在哪里。Oracle 提供执行计划就是优化器给 SQL 制定的执行路线图。生成执行计划的标准做法EXPLAIN PLAN FOR SELECT e.emp_name, d.dept_name FROM emp e, dept d WHERE e.dept_id d.dept_id AND e.salary 10000; SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);第二条语句会打印输出。新手看执行计划第一眼注意三件事缩进最深的操作最先执行每一步旁边有代价数值有没有出现 FULL TABLE SCAN。如果 SQL 已经跑完了发现慢我更推荐直接看真实执行计划SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR);它展示的是最近一条 SQL 实际运行时的计划和 EXPLAIN PLAN 理论计划可能有差别以实际为准。看过几次之后你对 SQL 慢在哪会有很强的直觉。6.2 读懂常见的执行路径执行计划里最常出现的表访问方式有这么几种。全表扫描 FULL TABLE SCAN表示 Oracle 从表的第一行读到最后一行。它不一定差表很小的时候全表扫描往往最优但在大表上高频出现多半是问题。索引范围扫描 INDEX RANGE SCAN表示通过索引定位到一批 rowid再回表取数据。一般来说这是优秀计划应该出现的形式。ROWID 访问 TABLE ACCESS BY ROWID 本身不会单独出现在计划开头它是拿到 rowid 后直接取行的动作非常快。看执行计划时要区分两个术语Access 和 Filter。Access 表示通过索引直接定位到的是哪些行Filter 表示把结果取出来后再过滤。如果 WHERE 条件列在计划里只出现在 Filter 而不是 Access说明索引没有被充分用上需要检查谓词写法是否匹配。6.3 统计信息才是优化器判断的基础优化器做决定依据的是统计信息包括表行数、列数据分布、索引数据等。如果统计信息过时优化器就会按错误的“认知”生成计划。典型表现是表数据量翻了十倍SQL 还是走原先的路线性能急速恶化。收集统计信息的命令BEGIN DBMS_STATS.GATHER_TABLE_STATS(OWNNAME SCOTT, TABNAME EMP, CASCADE TRUE); END;CASCADE 表示连同索引一起收集。在大表上收集统计信息也有成本通常是安排在业务低峰期或者由自动化任务执行不要在业务高峰期直接手动跑。我遇到过一种情况SQL 优化器选择的计划明明不是最优但强制执行某个旧计划反而更快于是有人就给它加了 Hint。Hint 能短期救火但统计信息更新后Hint 可能变成负优化。我的建议是排查顺序永远是先收集统计信息、再看执行计划、再考虑改写 SQL最后才碰 Hint。7. 常见错误与排查实录把事故现场变成经验7.1 结合 V$ 视图自查锁与阻塞锁和阻塞往往需要现场排查。最实用的查询语句我前面提过一次这里整理成一个速查脚本SELECT sess.sid, sess.serial#, sess.username, sess.status, sess.blocking_session, sess.event, sess.wait_class FROM v$session sess WHERE sess.blocking_session IS NOT NULL;查询结果里status 为 ACTIVE 而 event 类似“enq: TX - row lock contention”的会话基本可以确定它在等锁。根据 blocking_session 找源头再判断要不要 kill。如果需要更细粒度地看锁对象可以联查 v$locked_object 和 dba_objectsSELECT l.sid, l.type, o.object_name, o.object_type FROM v$locked_object l, dba_objects o WHERE l.object_id o.object_id;很多时候光看锁等待能定位是哪个应用模块的操作。排查锁问题时最重要的原则是先别急着杀会话先看它跑了多久。有些大事务跑一小时是正常的你把它杀了反而引发更多问题。7.2 ORA 错误速查表我把日常工作中高频遇到的一些错误码整理成了一张表方便你遇到的时候快速对照不必一张张去翻文档错误码原因常见处理ORA-00001唯一约束冲突插入重复值检查主键或唯一键确认数据是否重复ORA-00904标识符无效通常是列名写错核对列名大小写和别名ORA-00942表或视图不存在确认表名、权限、当前用户的 schemaORA-01400无法将 NULL 插入非空列检查数据源的默认值和映射关系ORA-01438数值超出精度范围核对 NUMBER 类型精度和业务数据范围ORA-01654无法扩展段表空间不足扩容数据文件清理表空间ORA-01555快照过旧UNDO 不够长缩短事务或查询时长增大 UNDO 保留策略ORA-00060检测到死锁检查并发加锁顺序考虑应用层调整这张表不要求背下来遇到问题先按现象查表再往具体方向深挖。排查时关键要看完整错误信息中的附加内容例如 ORA-00001 通常带约束名知道约束名才能快速定位谁挡住了。7.3 误操作之后的闪回查询补救误删数据、误改数据后Oracle 的闪回查询是一个很实用的补救手段。它允许你按某个时间点查看历史数据SELECT * FROM emp AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL 15 MINUTE);这条语句查的是 15 分钟前 emp 表的数据状态。如果刚才误 UPDATE、误 DELETE 还没来得及发现太晚可以拿这个快照把受影响的数据捞回来。但闪回查询依赖 UNDO保留期一过就查不到了所以发现误操作后要马上行动不要磨蹭。把误删的数据插回去可以这样组合INSERT INTO emp SELECT * FROM emp AS OF TIMESTAMP (SYSTIMESTAMP - INTERVAL 10 MINUTE) WHERE emp_id 1001;注意主键可能和新数据冲突插之前先查一遍。闪回技术还有很多变体比如闪回表、闪回版本查询对新手来说先掌握这一招就够应急了。7.4 实操习惯清单把这些变成肌肉记忆最后这部分我把自己的操作习惯整理成一个精简清单你可以直接用来做工作规范。执行 DML 之前先看一眼影响行数用 SELECT 说明确认数据范围和数量。永远不要在一条 UPDATE 里省略 WHERE 条件就算你真的要全表更新也要写成显式的确认过程。普通 DML 执行完确认无误后再提交不要开着自动提交乱跑。操作大表前检查数据库是否存在等待和锁竞争避开高峰窗口。遇到慢查询先看统计信息、执行计划再谈改写。这些习惯看起来都是小事但在关键时刻能救命。我自己也是从踩坑里学来的每次事故复盘后都会看看清单缺了哪条然后补进去。时隔几年它已经从一张纸变成下意识动作。再分享一个我觉得最有用的小习惯在日常工作中改数据之前把 SELECT 写出来执行 DML 之后看影响行数两者必须匹配。这个习惯不需要很高深的知识只需要一点耐心但它能拦截掉我见到过的绝大多数低级事故。Oracle 入门看起来是学语法实际上是在学纪律这第二部分讲完你如果再回到自己的数据库上不妨先拿这几条清单走一遍流程感受会和以前很不一样。