会,但只在特定条件下;行锁与表锁由mysql server层统一仲裁,持有表级写锁会阻塞行锁请求,反之未提交的行锁也会阻塞显式表锁操作。

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










