在日常开发和线上问题排查中,手动加锁和查看锁状态是非常实用的技能。无论是验证锁冲突、分析慢查询、还是追踪死锁,掌握这些操作能让你更直观地理解锁的机制,也更容易定位并发问题。
手动加锁的常用操作
全局锁:让整个数据库变成只读
全局锁会锁定整个数据库实例,阻塞所有写操作,常用于全库备份。加锁和解锁的命令如下:
-- 加全局读锁,此时所有表的写操作全部被阻塞
FLUSH TABLES WITH READ LOCK;
-- 解除当前会话持有的全局锁
UNLOCK TABLES;
执行第一句后,其他会话的任何写入(INSERT、UPDATE、DELETE、DDL)都会挂起,直到本会话执行 UNLOCK TABLES 或连接断开。注意,这个命令非常“重”,在生产环境应谨慎使用。现在更推荐使用 mysqldump --single-transaction(使用事务快照,不锁表)或物理备份工具,避免全局锁对业务的影响。
表级锁:精细到某一张表
通过 LOCK TABLES 可以显式给表加锁,指定读锁或写锁:
-- 为 orders 表加上读锁,其他会话也能读,但都不能写
LOCK TABLES orders READ;
-- 为 orders 表加上写锁,其他会话既不能读也不能写
LOCK TABLES orders WRITE;
-- 一次锁多张表(按顺序锁定)
LOCK TABLES orders READ, users WRITE;
-- 释放当前会话持有的所有表锁
UNLOCK TABLES;
InnoDB 引擎本身使用行锁,很少需要手动加表锁。但某些场景,如 MyISAM 表或数据修复时,表锁仍然可能用到。需要注意:LOCK TABLES 会隐式提交当前事务,并且会解除之前已上过的所有锁(包括行锁),所以不要在事务中途使用。
行级锁:精准控制并发
行级锁是 InnoDB 事务中自动使用的,但你也可以通过 SELECT 语句显式加上锁,以便控制业务逻辑:
-- 共享锁(S锁),允许其他事务读,但不允许修改
SELECT * FROM orders WHERE id = 10 LOCK IN SHARE MODE;
-- MySQL 8.0 推荐写法,语义更明确
SELECT * FROM orders WHERE id = 10 FOR SHARE;
-- 排他锁(X锁),不允许其他事务读(除非未读提交下读快照)或写
SELECT * FROM orders WHERE id = 10 FOR UPDATE;
FOR UPDATE是最常用的手动行锁方式,特别适合“先查询再修改”的场景,防止两事务同时读到旧值然后分别更新(即乐观锁失败转而使用悲观锁)。- 这两个语句必须在事务中(或开启自动提交关闭后)执行,锁会在事务提交或回滚时释放。
- 如果被查询的行已经被其他事务加了排他锁,
SELECT ... FOR UPDATE会进入等待(直到锁超时或对方提交),LOCK IN SHARE MODE同理。
还有一种排他锁是针对“间隙”的,如果你在可重复读隔离级别下执行 SELECT ... FOR UPDATE 并且条件是范围查询(如 WHERE id > 10),InnoDB 可能会加临键锁(Next-Key Lock),锁住记录和间隙,防止插入,这也是避免幻读的手段。
查看锁信息的工具和命令
查看当前事务与行锁(MySQL 8.0 推荐)
MySQL 8.0 将锁信息迁移到了 performance_schema 下的 data_locks 和 data_lock_waits 表,比旧的 INFORMATION_SCHEMA.INNODB_LOCKS 更全面。
-- 查看当前所有锁的详细信息:锁模式、锁对象、持有锁的事务
SELECT * FROM performance_schema.data_locks\G
-- 查看正在等待锁的事务(可以用来定位阻塞源头)
SELECT
r.trx_id AS waiting_trx_id,
r.trx_mysql_thread_id AS waiting_thread,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_thread,
w.blocking_lock_id,
w.requesting_lock_id
FROM performance_schema.data_lock_waits w
JOIN information_schema.innodb_trx r ON w.requesting_engine_transaction_id = r.trx_id
JOIN information_schema.innodb_trx b ON w.blocking_engine_transaction_id = b.trx_id;
data_locks记录了引擎层所有活跃锁,包括行锁、间隙锁、意向锁等,字段有LOCK_TYPE(行锁、表锁)、LOCK_MODE(S、X、IS、IX 等)、LOCK_DATA(当前锁定的主键值或索引记录)。data_lock_waits则专门展示等待关系,告诉你谁在等谁。
对于仍在使用 MySQL 5.7 的环境,可用的表是 INFORMATION_SCHEMA.INNODB_LOCKS 和 INNODB_LOCK_WAITS,但它们显示的是格式化后的数据,并不显示所有锁,且在高并发下查询较慢。8.0 的 performance_schema 表是内存表,性能更好。
查看当前运行的事务
SELECT * FROM information_schema.innodb_trx\G
该表显示所有当前活跃的事务,包括事务 ID、开始时间、锁定行数(近似)、事务状态、MySQL 线程 ID。当系统出现大量锁等待时,这个表可以帮助你迅速找到长事务或者阻塞源。
查看元数据锁(MDL)
DDL 语句(如 ALTER TABLE)需要获取表上的元数据锁,这会阻塞其他会话对本表的读写。在 MySQL 8.0 中可以通过以下命令查看:
SELECT * FROM performance_schema.metadata_locks;
如果某个会话长时间持有 MDL 锁(比如未提交的事务对表做了查询,然后另一个会话发起 DDL),这里的 LOCK_STATUS 会是 PENDING,你可以结合 innodb_trx 中的事务 ID 揪出未提交的源头。
查看死锁信息
当两个或多个事务相互等待对方持有的锁,形成循环等待时,InnoDB 会自动检测死锁并回滚其中一个事务。要查看死锁详情,使用:
SHOW ENGINE INNODB STATUS\G
输出内容包含 LATEST DETECTED DEADLOCK 片段,它会记录最近一次死锁发生的时间、涉及的事务、各自的 SQL 语句,以及它们持有和等待的锁。这是分析死锁最重要的原始信息。
也可以将死锁信息记录到 MySQL 错误日志中,通过设置 innodb_print_all_deadlocks = ON,所有死锁都会写入错误日志,方便事后追溯。
注意事项与最佳实践
- 手动加锁意味着承担锁的释放责任。加锁后必须在事务结束(COMMIT/ROLLBACK)或连接断开时释放,否则会导致严重阻塞。
LOCK TABLES和事务不要混用。LOCK TABLES会隐式提交事务,如果事务中已经做了一些修改,突然加表锁会导致数据中途提交,破坏原子性。- 避免在持有锁期间执行耗时操作,例如发起远程调用或复杂计算。持有锁的时间越短,并发度越高。
- 排查阻塞时优先看
data_lock_waits,它能直接定位到阻塞链,比逐条检查快得多。 - 线上监控建议:部署监控工具(如 PMM、Prometheus+Grafana)采集
performance_schema锁等待指标,当等待次数或时长超过阈值时告警,比人工查看及时得多。
掌握这些手动的加锁和查看操作,足以应对从开发调试到线上紧急排查的绝大多数场景。它们是深入理解锁机制的最佳实践入口,也是数据库高并发运维的基本功。