人人都会AI编程

16.1 各类锁的手动使用与查看

更新时间:2026-07-11

在日常开发和线上问题排查中,手动加锁和查看锁状态是非常实用的技能。无论是验证锁冲突、分析慢查询、还是追踪死锁,掌握这些操作能让你更直观地理解锁的机制,也更容易定位并发问题。

手动加锁的常用操作

全局锁:让整个数据库变成只读

全局锁会锁定整个数据库实例,阻塞所有写操作,常用于全库备份。加锁和解锁的命令如下:

-- 加全局读锁,此时所有表的写操作全部被阻塞
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_locksdata_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_LOCKSINNODB_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 锁等待指标,当等待次数或时长超过阈值时告警,比人工查看及时得多。

掌握这些手动的加锁和查看操作,足以应对从开发调试到线上紧急排查的绝大多数场景。它们是深入理解锁机制的最佳实践入口,也是数据库高并发运维的基本功。