mysql中的行锁与表锁的互斥与配合使用

来源:这里教程网 时间:2026-02-28 20:49:09 作者:

行锁和表锁在 MySQL 中会互相阻塞吗

会,但只在特定条件下。MySQL 的行锁(如

InnoDB
的
RECORD LOCK
)和表锁(如
LOCK TABLES ... WRITE
或
FLUSH TABLES WITH READ LOCK
)属于不同层级的锁机制,它们不共享锁管理器,因此互斥行为不是“自动协调”的,而是靠 MySQL Server 层统一仲裁——一旦某线程持有表级写锁,所有试图获取该表任何行锁的事务都会被阻塞;反之,若已有事务在该表上持有了未提交的行锁(比如正在执行
UPDATE
),再执行
LOCK TABLES t1 WRITE
也会被挂起,直到行锁释放。

为什么不能一边用行锁一边加显式表锁

因为二者语义冲突:

InnoDB
行锁是事务性的、细粒度的、可回滚的;而
LOCK TABLES
是 Server 层的非事务性强制锁,它要求当前连接独占整张表,且会隐式提交当前事务(如果存在)。常见错误现象包括:

ERROR 1100 (HY000): Table 't1' was not locked with LOCK TABLES
:在未加锁的表上执行
INSERT INTO t1 ... SELECT ... FROM t1
类操作时,若启用了
binlog_format=STATEMENT
,MySQL 可能尝试加表锁校验,但连接没提前
LOCK TABLES
,就报这个错
执行
LOCK TABLES t1 WRITE
后,另一个事务对
t1
做
UPDATE
卡住不动,
SHOW PROCESSLIST
显示
Waiting for table metadata lock
—— 实际是等表锁释放,不是等行锁
已开启事务并修改了
t1
的若干行,此时执行
LOCK TABLES t1 READ
,MySQL 会先提交当前事务,导致行锁释放、变更可能被其他事务读到(破坏隔离性)

如何安全地混合使用行锁与表锁

绝大多数业务场景下,**不应混合使用**。真正需要表锁的场合极少,基本只剩两类:

备份前全局只读:用
FLUSH TABLES WITH READ LOCK
(注意它会阻塞所有 DML 和 DDL,且不兼容
InnoDB
的 MVCC 快照读)
MyISAM 表维护:如
REPAIR TABLE
或
OPTIMIZE TABLE
,必须配合
LOCK TABLES

如果你在

InnoDB
表上做数据迁移或批量更新,应该依赖事务 + 行锁 + 合理的
WHERE
条件,而不是加表锁。例如:

START TRANSACTION;
UPDATE orders SET status = 'shipped' WHERE id IN (1001, 1002, 1003);
-- 不要在这里执行 LOCK TABLES orders WRITE
COMMIT;

若真需“类表锁”效果(比如防止并发插入干扰),可用

SELECT ... FOR UPDATE
锁住主键范围,或建唯一约束+重试逻辑,比表锁更轻量、更可控。

容易被忽略的元数据锁(MDL)影响

很多人以为自己只用了行锁,就和表锁无关——其实不然。MySQL 5.5+ 引入的

Metadata Lock
(MDL)会在任何语句执行前自动加锁,它和表锁一样作用于 Server 层。例如:

一个长事务正在执行
SELECT * FROM t1
(即使没显式加锁),另一个连接执行
ALTER TABLE t1 ADD COLUMN x INT
,后者会被阻塞,
SHOW PROCESSLIST
显示
Waiting for table metadata lock
此时哪怕你没碰过
LOCK TABLES
,也相当于被“隐式表级阻塞”了
MDL 和行锁共存时,排查锁等待必须同时看
information_schema.INNODB_TRX
、
INNODB_LOCK_WAITS
和
performance_schema.metadata_locks

真正的难点不在锁类型本身,而在理解 Server 层(MDL、

LOCK TABLES
)和 Storage Engine 层(
InnoDB
行锁、间隙锁)两套锁体系如何交叉作用。一次卡顿,往往不是行锁没释放,而是 MDL 被某个忘了关的连接死死攥着。

相关推荐