Oracle 事务锁有哪些?行锁、表锁和死锁怎么理解?
简化版
Oracle 的普通 DML 会自动加锁,常见是行级锁和表级锁。更新一行时会锁住对应行,其他事务修改同一行要等待;普通一致性读通常不被行锁阻塞。死锁是多个事务互相等待对方持有的资源,Oracle 会检测死锁并回滚其中一条语句,应用需要处理异常并优化加锁顺序。
详细版
Oracle 锁常见考点:
SELECT普通查询走一致性读,通常不阻塞 DML;SELECT ... FOR UPDATE会对查询到的行加锁;INSERT、UPDATE、DELETE会自动加行锁;- DML 还会持有表级锁,用来保护表结构不被不兼容 DDL 修改;
- 死锁通常来自多个事务访问资源顺序不一致;
- 长事务会持锁更久,放大锁等待。
行锁保护的是具体数据行,表锁更多用于协调表级操作。面试时要把“锁的粒度”和“读一致性”一起讲清楚,不能简单说 Oracle 读写永远互不影响。
完整版教学
一、Oracle 为什么还需要锁
Oracle 有读一致性,普通查询可以通过 UNDO 看到历史版本,所以读写冲突少。但写写冲突仍然必须控制。
如果两个事务同时更新同一行账户余额:
UPDATE account SET balance = balance - 100 WHERE id = 1;
数据库必须让其中一个事务等待,否则最终余额会变得不可预测。这就是行锁存在的意义。
二、行锁保护具体行
Oracle 执行 UPDATE、DELETE 时,会锁住被修改的行。锁会一直持有到事务提交或回滚。
例如事务 A:
UPDATE users SET name = 'Tom' WHERE id = 1;
事务 B 如果也更新 id = 1,就会等待 A 提交或回滚。但 B 普通查询这行时,通常可以通过一致性读看到合适版本。
三、表级锁不是一定锁整张表不能读写
Oracle DML 也会持有表级锁,但它主要用于保护表结构,防止执行 DML 时表被某些不兼容 DDL 改掉。
这和“把整张表的数据行都锁住”不是一回事。面试里说表锁时要具体说明锁模式和目的,避免把表级锁理解成所有人都不能访问表。
如果手动执行:
LOCK TABLE orders IN EXCLUSIVE MODE;
那就会产生更强的表级锁,业务系统里要非常谨慎。
四、SELECT FOR UPDATE 是显式加锁读
普通 SELECT 是一致性读,不会锁住数据行。但:
SELECT * FROM orders WHERE id = 100 FOR UPDATE;
会锁住返回的行,常用于抢单、任务领取、先查后改等场景。
使用时要注意:
- 条件要尽量走索引,避免锁范围失控;
- 事务要尽快结束;
- 可以使用
NOWAIT或SKIP LOCKED控制等待行为; - 不要在持锁事务里做耗时远程调用。
五、死锁通常来自访问顺序不一致
典型死锁:
事务 A:先更新订单 1,再更新订单 2
事务 B:先更新订单 2,再更新订单 1
A 等 B 释放订单 2,B 等 A 释放订单 1,双方互相等待。Oracle 会检测死锁,并让其中一个语句失败。
工程上的解决方法:
- 批量更新统一按主键顺序;
- 缩短事务时间;
- 减少跨多个资源的事务范围;
- 对热点资源使用更明确的队列或串行化策略;
- 捕获死锁异常并让业务安全重试。
六、常见误区与追问
| 场景 | 锁行为 | 风险 |
|---|---|---|
| 普通 SELECT | 一致性读,不锁行 | 看到的是快照版本 |
| UPDATE/DELETE | 锁住被修改行 | 同行写写等待 |
| SELECT FOR UPDATE | 显式锁返回行 | 事务过长会阻塞别人 |
易错点:Oracle “读写不互相阻塞”说的是普通一致性读和 DML 的常见关系,不代表所有读都不加锁,也不代表写写不会等待。
典型死锁时间线:
T1: 事务 A 锁住 order_id=1
T2: 事务 B 锁住 order_id=2
T3: 事务 A 请求 order_id=2,等待 B
T4: 事务 B 请求 order_id=1,等待 A
T5: Oracle 检测死锁,回滚其中一个语句
如果批量任务统一按主键升序更新,就能避免很多“你先 1 后 2、我先 2 后 1”的互相等待。
- 误区:Oracle 有一致性读,所以不需要锁。 普通读少阻塞写,但写写冲突仍必须用行锁控制。
- 误区:表级锁等于整张表不能查询。 DML 的表级锁主要协调结构变更,不等于锁住所有数据行。
- 误区:死锁只能靠数据库自动处理。 数据库能检测并打断一个语句,但应用要处理异常,并从访问顺序和事务范围上预防。
- 追问:
SELECT FOR UPDATE适合什么场景? 适合先锁定再处理的任务领取、抢单、库存校验等,但事务要短。 - 追问:
NOWAIT和SKIP LOCKED有什么价值?NOWAIT避免长时间等待,SKIP LOCKED可跳过已锁行,常用于并发任务领取。 - 追问:如何降低死锁概率? 统一资源访问顺序、缩短事务、避免持锁做远程调用、减少热点行、必要时使用队列串行化。
七、加强记忆
Oracle 锁要记住:普通读靠一致性读,写写冲突靠行锁,DML 还会有表级协调锁,FOR UPDATE 是主动加锁读。死锁不是神秘问题,多数来自多个事务访问资源顺序不一致;统一顺序、缩短事务、减少热点竞争是核心解法。