人人都会AI编程

全局锁、表级锁、行级锁粒度划分

更新时间:2026-07-11

MySQL 的锁体系是按照加锁范围(或者说粒度)来分层的:从整个数据库,到某张表,再到表中的某些行。三种粒度各有其使用场景和性能影响,理解它们的区别是处理并发问题和优化性能的基础。

全局锁:锁住整个数据库实例

全局锁把整个 MySQL 实例变成只读状态,任何对数据的修改操作(DML、DDL)都会被阻塞。最典型的获得全局锁的命令是:

FLUSH TABLES WITH READ LOCK;

执行后,整个数据库实例处于只读状态,其他会话的 INSERT、UPDATE、DELETE 以及 ALTER TABLE、DROP TABLE 等操作都会被阻塞,直到释放锁(通过 UNLOCK TABLES 或持有锁的会话断开)。

实际开发中什么场景会用?

全局锁主要用于全库逻辑备份。在不锁表的情况下备份,可能会导致备份出来的数据与某一时刻不一致——比如备份过程经历了部分表的修改,导致备份文件无法用于精确恢复。而全局锁可以确保在备份期间数据完全静止,得到的是一个稳定的一致性快照。

不过,这种“一刀切”的代价是巨大的:

  • 备份期间整个库不可写,对线上业务来说是灾难性的中断。
  • 如果备份在主库上执行,意味着停服;在从库上执行,备份期间的从库也无法执行同步过来的写操作,可能造成主从延迟。

因此,全局锁在今天的生产环境中已经极少使用,它被更友好的备份工具取代。例如:

  • mysqldump 在启用 --single-transaction 参数时,会开启一个事务并利用 MVCC 的快照读能力,在 InnoDB 引擎下获得一致性备份,而无需加全局锁。
  • XtraBackup 等物理热备工具可以做到完全不阻塞读写。

不过,在某些极特殊的运维场景 – 比如进行全库导出的同时绝对不允许任何数据变化 – 全局锁仍然是一种理论上的选择,但实际极少有人这么做了。

表级锁:锁定整张表

表级锁是最古老的 MySQL 锁形式,MyISAM 引擎正是依赖它实现并发控制。InnoDB 虽然以行锁著称,但某些情况下也会使用表级锁。

MySQL 的表级锁主要有两种:

  • 表共享读锁(Table Read Lock):锁住后,当前会话和其他会话都可以读表,但不能写表。
  • 表独占写锁(Table Write Lock):锁住后,只有当前会话可以读和写表,其他会话无法进行任何读写操作。

手动加表锁的命令:

LOCK TABLES orders READ;   -- 加读锁
LOCK TABLES orders WRITE;  -- 加写锁
UNLOCK TABLES;             -- 释放当前会话所有表锁

哪些情况会遇到表级锁?

  1. 显式使用 LOCK TABLES:某些旧系统或特殊脚本会使用 LOCK TABLES 来模拟事务(因为在非事务引擎中无法使用 BEGIN/COMMIT)。但在 InnoDB 上,这样做完全没有必要,因为事务和行锁已经提供了更精细的控制。除非你明确想阻止任何其他会话对表的访问,否则不要使用。
  1. DDL 操作:许多 DDL 语句(如 ALTER TABLE)会对表加一个元数据锁(MDL),虽然不是传统的表读写锁,但效果类似。在 5.7 及以前,DDL 可能会用表拷贝方式执行,长时间锁表;8.0 的原子 DDL 和部分在线 DDL 大大缓解了这个问题。
  1. MyISAM 引擎的操作:如果你还在维护旧的 MyISAM 表,那么每一次读都自动加表级共享锁,每一次写都自动加表级独占锁。写操作非常容易阻塞所有读请求,并发能力极差。
  1. InnoDB 在某些情况下的降级:InnoDB 通常使用行锁,但如果一条 SQL 无法使用索引(例如全表扫描的 UPDATE),为了确保事务安全,它可能会锁住扫描过的所有记录,实际效果接近表锁。这种隐式的表级行为是性能杀手,应当通过索引优化来避免。

表级锁的优缺点:

  • 优点:实现简单,内存开销小,不会出现死锁(因为一次锁一张表,不存在循环等待)。
  • 缺点:并发度极低,只要有一个写锁,所有读写全部排队,吞吐量上不去。

在现代 InnoDB 为主的架构中,表级锁几乎成了“退役选手”,只有在极特殊场景或不小心踩坑时才会遇到。

行级锁:只锁定必要的数据行

行级锁是现代数据库高并发能力的核心。InnoDB 实现了完善的行级锁机制,使得不同事务修改不同行时,完全不会相互阻塞。这正是 MySQL 能够支撑数十万 QPS 写入的重要保障。

行级锁的特点:

  • 精确锁定:只锁定被访问到的那些行,而不是整个表甚至整个库。
  • 支持多种锁模式:主要包括共享锁(S 锁)和排他锁(X 锁)。共享锁之间兼容,可以同时读;排他锁与任何锁都不兼容,保证写操作的独占性。
  • 通过索引实现:行级锁必须基于索引。如果一条 SQL 扫描时走了索引,它只锁定命中的索引记录;如果没走索引,InnoDB 会退化为锁定所有扫描过的行,甚至因为间隙锁而锁住更大的范围,导致并发度骤降。

在实际开发中,你应该时刻确保关键的 UPDATE/DELETE/SELECT ... FOR UPDATE 语句命中合适的索引,以保证行锁的粒度真正“行级”。

粒度选择:该用哪种锁?

绝大多数情况下,你不需要也不应该手动选择锁粒度,让 InnoDB 自动决定即可。但理解它们有助于判断并发瓶颈发生在哪里:

  • 全局锁:仅在极端情况的全库一致性快照需求才考虑,现代备份工具已基本替代。
  • 表级锁:尽量避免。如果延迟发现线上有长事务在等表锁,往往是 DDL 或误操作导致,应急时可能需要 KILL 掉持有锁的会话。
  • 行级锁:InnoDB 的默认和主力武器,保护数据一致性的同时保证并发。你需要的是通过优化索引让行锁真正生效,而不是退化为表锁。

一组实用的日常规则:

  • 永远不要在 InnoDB 表上显式使用 LOCK TABLES,有事务就足够了。
  • 关注慢查询日志中的 Waiting for table metadata lockWaiting for table level lock 错误,尽早发现锁冲突。
  • 使用 SHOW ENGINE INNODB STATUSPERFORMANCE_SCHEMA 监控行锁等待,及时优化热点行。

总而言之,锁的粒度越细,并发度越高,但实现复杂度和系统开销也会相应增加。MySQL 通过 InnoDB 的行级锁,在大部分场景下取得了良好的平衡。作为技术人员,理解和尊重这个分级体系,就能在正确的层级排查问题和设计方案。