在 Oracle 数据库里,锁是保证事务隔离性和数据一致性的基础机制。大多数情况下,执行 INSERT、UPDATE、DELETE 时数据库会自动加上行级锁或表级锁,但有些场景需要开发人员或 DBA 主动控制锁的粒度和模式,这时就要用到 LOCK TABLE 语句。它的语法并不复杂,真正容易出问题的是对锁模式的理解以及显式加锁带来的副作用。下面从基础语法开始,逐步拆解锁表的各种细节。

LOCK TABLE 基础语法与锁模式详解
LOCK TABLE 的基本写法是 LOCK TABLE 表名 IN 锁模式 MODE,可以加上 NOWAIT 或 WAIT n 来控制等待行为。Oracle 支持的锁模式从宽松到严格依次为:ROW SHARE、ROW EXCLUSIVE、SHARE、SHARE ROW EXCLUSIVE、EXCLUSIVE。这五种模式并不都对应传统意义上的“表锁”,其中 ROW SHARE 和 ROW EXCLUSIVE 实际上会同时影响表锁和行锁。
先看一个简单的加锁示例,假设要对员工表 employees 加共享锁,禁止其他会话执行 DDL 操作:
-- 对 employees 表加共享锁,其他会话仍可查询和加 ROW SHARE 锁 LOCK TABLE employees IN SHARE MODE; -- 如果不希望等待,可以加 NOWAIT LOCK TABLE employees IN SHARE MODE NOWAIT; -- 或者等待最多 5 秒 LOCK TABLE employees IN SHARE MODE WAIT 5;
ROW SHARE 和 ROW EXCLUSIVE 是最常用的两种模式。ROW SHARE 允许其他会话同时查询、插入、更新、删除,但不允许其他会话对该表加 EXCLUSIVE 锁。也就是说它主要阻止的是 DDL 操作,比如 ALTER TABLE 或 DROP TABLE。ROW EXCLUSIVE 比 ROW SHARE 更严格一点,它同样允许 DML 操作,但会阻止其他会话加 SHARE 锁或更高级别的锁。实际上,当你执行一条普通的 UPDATE 语句时,Oracle 会自动在表上加上 ROW EXCLUSIVE 锁,同时在被修改的行上加上行级排他锁。
SHARE 锁的作用是允许其他会话查询该表,但不允许任何 DML 操作。这在需要暂时冻结某张表的数据变更时很有用,比如做数据导出或一致性检查。SHARE ROW EXCLUSIVE 则比 SHARE 更严格,它阻止其他会话加 SHARE 锁,但允许其他会话查询。EXCLUSIVE 锁是最强的表锁,其他会话除了查询(需要看隔离级别)外几乎什么都不能做,甚至查询也会受影响,除非使用 SELECT ... FOR UPDATE 之类的特殊语法。生产环境中直接加 EXCLUSIVE 锁非常危险,容易造成大面积阻塞。
显式锁表与隐式锁的区别及适用场景
很多人以为 LOCK TABLE 只是“锁住整张表”,但实际效果取决于锁模式。隐式锁是指 DML 语句自动获取的锁,例如 UPDATE 会加 ROW EXCLUSIVE 表锁和行锁,INSERT 会加 ROW EXCLUSIVE 表锁,SELECT 在 READ COMMITTED 隔离级别下通常不加锁,而在 SERIALIZABLE 下会加共享锁。显式锁则是通过 LOCK TABLE 手动指定模式,它的目的往往不是为了“锁表”,而是为了协调多个事务之间的操作顺序。
一个典型的适用场景是主从表数据维护。假设有订单表 orders 和订单明细表 order_items,需要先删除主表记录再删除明细记录,但希望两个操作之间不允许其他会话插入新的明细。可以这样处理:
-- 事务开始 LOCK TABLE order_items IN EXCLUSIVE MODE; DELETE FROM orders WHERE order_id = 1001; DELETE FROM order_items WHERE order_id = 1001; COMMIT; -- 事务结束,锁被释放
注意这里锁的是明细表而不是主表,因为目的就是阻止其他会话在删除主表后、删除明细前向明细表插入数据。如果只靠隐式锁,DELETE 语句只会锁住它实际删除的行,其他会话仍然可以插入新的 order_id=1001 的明细行,导致数据不一致。这种场景下显式锁是合理的。
另一个常见场景是数据迁移或归档。比如每月把历史数据从主表移动到归档表,需要保证移动过程中主表数据不被修改。可以先用 LOCK TABLE 主表 IN SHARE MODE 阻止 DML,然后执行 INSERT INTO 归档表 SELECT ...,最后删除主表中已归档的行。不过要注意,SHARE 锁会阻止其他会话的 DML,如果迁移时间较长,会造成业务阻塞,所以更推荐使用在线重定义或分区交换等方案。
显式锁和隐式锁的一个关键区别是锁的持有时间。隐式锁在事务提交或回滚时自动释放,而显式锁同样遵循这个规则,它不会因为 LOCK TABLE 语句本身结束就释放,必须等到事务结束。很多开发者误以为执行完 LOCK TABLE 后锁就固定了,其实如果后续没有 COMMIT 或 ROLLBACK,锁会一直持有,这是造成锁等待的常见原因。
常见误区与排障方法
第一个误区是认为“锁表”等同于禁止其他会话读写。实际上不同锁模式限制不同,ROW SHARE 和 ROW EXCLUSIVE 模式下其他会话完全可以正常读写,只有 SHARE 及以上模式才会限制 DML,EXCLUSIVE 模式连查询都可能受影响。如果没有仔细看锁模式,很容易在测试环境加了个 ROW SHARE 锁就以为万事大吉,结果生产上依然出现并发写入冲突。
第二个误区是显式锁能“提高并发性能”。恰恰相反,大部分情况下手动加表锁只会降低并发度。Oracle 的行级锁机制已经非常高效,只有在需要跨语句保证一致性时才需要显式表锁。如果只是为了“防止别人改数据”,不加锁直接依赖事务隔离级别和行锁往往更合理。例如两个会话同时更新同一行,后提交的会话会收到 ORA-00060 死锁或 ORA-08177 等错误,这是正常的并发控制,不需要提前锁表。
第三个误区是忘记监控锁等待。当出现锁冲突时,可以通过 v$lock 和 v$session 视图定位阻塞源。下面这条 SQL 可以找出当前被阻塞的会话以及持有锁的会话:
SELECT
s1.sid AS blocked_sid,
s1.serial# AS blocked_serial,
s1.username AS blocked_user,
s2.sid AS blocking_sid,
s2.serial# AS blocking_serial,
s2.username AS blocking_user,
l1.type AS lock_type,
l1.id1 AS lock_id1,
l1.id2 AS lock_id2
FROM v$lock l1
JOIN v$session s1 ON l1.sid = s1.sid
JOIN v$lock l2 ON l1.id1 = l2.id1 AND l1.id2 = l2.id2 AND l1.request > 0 AND l2.lmode > 0
JOIN v$session s2 ON l2.sid = s2.sid
WHERE l1.block = 1 OR l2.block = 1;
如果发现某个会话长时间持有表级锁,可以先通过 v$session 查看它的 SQL 文本和状态,必要时使用 ALTER SYSTEM KILL SESSION 'sid,serial#' 终止阻塞会话。但终止之前一定要确认该会话是否在回滚,强制杀掉可能引发更长时间的恢复。
还有一个容易忽略的误区是锁与事务隔离级别的关系。在 READ COMMITTED 下,SELECT 语句不加锁,但在 SERIALIZABLE 下 SELECT 会加共享锁直到事务结束,这可能导致意想不到的表级共享锁堆积。如果应用在 SERIALIZABLE 隔离级别下执行了大量查询,又手动执行了 LOCK TABLE IN EXCLUSIVE MODE,极容易形成死锁或长时间等待。排查这类问题时,要同时关注 v$transaction 中的隔离级别信息和锁的持有时间。
最后提醒一点:LOCK TABLE 语句本身无法阻止其他会话提交已经开始的 DML 事务。如果对方在锁表之前已经修改了某些行并且尚未提交,那么你的 LOCK TABLE 可能会等待对方提交或回滚。所以在执行关键的 DDL 之前,除了锁表,最好先检查 v$locked_object 确认没有未完成的事务。
Oracle LOCK TABLE表锁锁机制修改时间:2026-09-23 04:00:59