Oracle数据库的锁机制是并发控制的核心,很多人在写代码时只知道“更新会加锁”,却不清楚锁到底落在哪、不同语句产生什么类型的锁、会话之间如何相互阻塞。通过搭建简单的锁实验环境,我们可以用最朴素的SQL会话观察真实行为。

一、实验环境准备
做锁实验不需要复杂架构,本机安装Oracle 11g或以上版本即可。我们打开两个独立的SQL Plus窗口,分别称之为会话A和会话B。两个会话使用同一账号连接同一实例,这样能直接看到彼此的锁冲突。
先创建一张极简的测试表,并插入几条数据,避免实验受业务表干扰。建表语句如下,注意commit让数据真正落盘:
create table lock_demo ( id number primary key, name varchar2(20) ); insert into lock_demo values (1, 'a'); insert into lock_demo values (2, 'b'); commit;
此时表中存在两行记录。我们需要记住主键值,后续实验会针对id=1这一行做文章。建议两个会话都执行set linesize 200和col column format a20,方便观察宽字段输出。
二、行锁实验:观察TX锁与阻塞
在会话A中执行一条更新但不提交,Oracle会在被修改行上放置行级排他锁,同时在表上放置行共享级TM锁。行锁在Oracle内部以TX锁表示,它并不存储在行头,而是通过事务槽和兴趣链定位。
-- 会话A:更新id=1,不提交 update lock_demo set name = 'a1' where id = 1;
切换到会话B,尝试更新同一行。此时会话B会立即挂起,因为会话A的TX锁尚未释放。这说明Oracle的行锁是排他的,同一行只能有一个事务修改。
-- 会话B:同样更新id=1,会被阻塞 update lock_demo set name = 'a2' where id = 1;
为了看清锁状态,我们在第三个会话或会话B超时前用DBA视图查询。v$lock中的LMODE表示已持有模式,REQUEST表示请求模式,0代表无,6代表排他。
select s.sid, l.type, l.lmode, l.request, l.block from v$lock l, v$session s where l.sid = s.sid and s.username = 'YOUR_USER';
查询结果中,会话A对应TX锁LMODE=6,TM锁LMODE=3;会话B对应TX锁REQUEST=6,说明它在等待排他行锁。block字段为1表示会话A阻塞了别人。这个实验直观展示了行锁的本质:锁信息在事务层,行只是被事务标记。
三、表锁实验:TM锁的级别差异
除了行锁,Oracle的DML还会加表级TM锁。不同操作加的TM锁模式不同,比如select for update加RS锁(2级),普通update加RX锁(3级),lock table加S或X锁。我们用lock命令模拟表级排斥。
-- 会话A:显式锁全表,排他模式 lock table lock_demo in exclusive mode;
这时会话A持有TM锁LMODE=6。会话B再执行任何DML都会阻塞,因为RX(3级)与X(6级)不兼容。我们可以在会话B试一条insert:
-- 会话B:被表锁阻塞 insert into lock_demo values (3, 'c');
通过对比行锁与表锁实验,能发现表锁保护的是对象结构而非数据行。应用层如果滥用lock table,会让并发骤降。正常OLTP系统应依赖行锁而非表锁。
| 锁类型 | 视图中的TYPE | 常见模式 | 冲突示例 |
|---|---|---|---|
| 行排他锁 | TX | LMODE=6 | 同行另一TX |
| 表锁 | TM | RX(3)/X(6) | X阻塞RX |
四、死锁实验与Oracle处理
死锁是两个会话互相等待对方持有的锁。我们构造场景:会话A改id=1,会话B改id=2,然后互相改对方已锁的行。
-- 会话A update lock_demo set name='x' where id=1; -- 会话B update lock_demo set name='y' where id=2; -- 会话A再执行(被B阻塞) update lock_demo set name='x' where id=2; -- 会话B再执行(被A阻塞,形成死锁) update lock_demo set name='y' where id=1;
Oracle内置死锁检测线程会识破这种循环等待,通常一秒内抛出自带错误并回滚其中一个会话的当前语句,另一个得以继续。这与我们手写超时逻辑不同,是数据库兜底机制。
观察alert日志或会话报错能看到ORA-00060。实验证明,死锁不是必须避免的灾难,但应在代码里缩小事务范围,减少交叉更新来降低概率。
五、实验总结与进阶
亲手做Oracle锁实验能破除很多误解,比如“锁在行上”其实只是逻辑说法,物理上锁属于事务。借助v$lock能定位线上阻塞源,结合v$session的blocking_session字段可快速找到罪魁。
进阶可以尝试在PL/SQL里用dbms_lock包自定义锁,或开启trace看更底层Enqueue。掌握这些,调优并发与排查hang问题就有了扎实根基。