行锁和表锁在 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 被某个忘了关的连接死死攥着。
