一、按锁的粒度(锁定范围)划分

这是最核心的一种分类方式,锁的粒度决定了系统的并发性能和开销。粒度越小,并发度越高,但管理锁的开销也越大。

  1. 全局锁

    • 作用范围:整个数据库实例。
    • 典型命令FLUSH TABLES WITH READ LOCK (FTWRL)。
    • 理解:它会让整个数据库处于只读状态,所有数据变更操作(DML)和表结构变更操作(DDL)都会被阻塞。
    • 使用场景:非常少,主要用于全库逻辑备份。但请注意,在支持事务的引擎(如InnoDB)中,使用mysqldump --single-transaction进行一致性读备份是更好、更非阻塞的选择。
  2. 表级锁

    • 作用范围:整张表。
    • 理解:MySQL服务器层实现的锁,与存储引擎无关。锁定整张表后,其他会话对这张表的写操作会被阻塞。
    • 分类
      • 表锁LOCK TABLES ... READ/WRITE。显式使用,现在很少用。
      • 元数据锁Metadata Lock (MDL),这是最重要、最常见的表级锁
        • 作用:防止一个事务在读数据时,另一个会话修改了表结构(DDL操作),导致查询得到的结果不一致。
        • 规则
          • 当对一个表做增删改查(DML) 操作时,会自动加MDL读锁(共享锁)。
          • 当要对表做结构变更(DDL) 操作时,会自动加MDL写锁(排他锁)。
        • 常见问题:长事务或未提交的事务一直占着MDL读锁,会导致后续所有的DDL操作(甚至后续的DML操作)被阻塞,整个表可能就完全写不入了。这是线上需要非常小心的问题。
  3. 行级锁

    • 作用范围:单行记录或行之间的间隙。
    • 理解这是InnoDB引擎实现高并发的基石。它只在存储引擎层实现,MyISAM引擎就不支持行锁。
    • 分类(InnoDB的行锁是基于索引实现的!)
      • 记录锁:锁定索引中的一条具体记录
        • 例如:SELECT * FROM t WHERE id = 10 FOR UPDATE; 会在id=10这条记录的索引上加记录锁,防止其他事务修改或删除它。
      • 间隙锁:锁定一个索引记录之间的范围,但不包括记录本身。开区间,如 (5, 10)
        • 目的:解决幻读问题(在可重复读隔离级别下生效)。
        • 例如:SELECT * FROM t WHERE id BETWEEN 5 AND 10 FOR UPDATE; 不仅会锁住id=5和10的记录,还会锁住(5,10)这个区间,防止其他事务插入id=6,7,8,9的新记录。
      • 临键锁记录锁 + 间隙锁的组合。锁定一个范围,并且包含记录本身。左开右闭区间,如 (5, 10]
        • 这是InnoDB在可重复读(RR) 隔离级别下的默认行锁算法。
        • 例如:如果索引有值10, 11, 13。那么执行SELECT * FROM t WHERE id > 11 FOR UPDATE;,可能会锁住 (11, 13](13, +∞] 这两个临键锁范围。
      • 插入意向锁:一种特殊的间隙锁,表示事务打算在某个间隙插入记录。多个事务只要插入的位置不冲突(例如插入不同的值),它们可以同时持有插入意向锁。它的存在是为了提高插入并发度。

二、按锁的模式(兼容性)划分

这种分类描述了多个锁之间的相互关系。

  1. 共享锁

    • 简称:S锁。
    • 理解:又称为“读锁”。一个事务获取了一条记录的共享锁后,其他事务也可以获得这条记录的共享锁(大家可以一起读),但不能获得排他锁(不能有人修改)。
    • 加锁方式SELECT ... LOCK IN SHARE MODE;
  2. 排他锁

    • 简称:X锁。
    • 理解:又称为“写锁”。一个事务获取了一条记录的排他锁后,其他事务既不能获得共享锁也不能获得排他锁(即不能读(这里指加锁读)也不能写)。
    • 加锁方式SELECT ... FOR UPDATE; 以及 UPDATE, DELETE, INSERT 语句会自动给涉及的行加排他锁。

兼容性矩阵

当前锁 请求 S锁 请求 X锁
持有 S锁 ✅ 兼容 ❌ 冲突
持有 X锁 ❌ 冲突 ❌ 冲突
  1. 意向锁
    • 理解意向锁是表级锁,它的存在是为了让行锁和表锁能够高效地共存。
    • 目的:如果一个事务想要给某一行加S/X锁,它必须首先给这张表加上对应的意向锁。这样,当另一个事务想给整张表加表锁时,就不需要逐行检查是否有行锁冲突,只需检查表上是否有意向锁即可,大大提高了效率。
    • 分类
      • 意向共享锁:IS锁。表示事务准备给表中的某些行加共享锁(S锁)。
      • 意向排他锁:IX锁。表示事务准备给表中的某些行加排他锁(X锁)。
    • 兼容性:意向锁之间是互相兼容的(因为大家只是“有意向”,实际锁定的行可能并不冲突)。但IX锁和表级S锁不兼容(因为表S锁要求整个表只读,而IX锁表示有事务想写)。

三、如何理解和应用

  1. 隔离级别是背景:锁的存在很大程度上是为了实现不同的事务隔离级别。

    • 读未提交:通常不加锁。
    • 读已提交:使用记录锁,但没有间隙锁,所以可能发生幻读。
    • 可重复读:使用临键锁(记录锁+间隙锁),解决了幻读问题。
    • 串行化:直接使用表级锁,并发度最低。
  2. 锁是基于索引的:这是理解行锁的关键!

    • 有索引:InnoDB会只锁定满足条件的索引项和间隙。这是高效且高并发的方式。
    • 无索引/未用索引:InnoDB无法精确定位到行,无奈之下会升级锁的粒度,比如锁住整个索引树,甚至退化为锁表。这是导致并发性能急剧下降的常见原因。所以,WHERE条件一定要用好索引!
  3. 死锁:行锁必然带来死锁问题。

    • 原因:两个或多个事务互相等待对方释放锁。
    • 例如:事务A锁了行1,试图锁行2;同时事务B锁了行2,试图锁行1。
    • 解决方案:InnoDB有死锁检测机制,一旦发现死锁,会立即回滚其中一个代价最小的事务,让另一个事务完成。应用程序需要能处理这种异常并进行重试。

总结

锁类型 粒度 主要作用 备注
全局锁 数据库 全库备份 尽量用--single-transaction替代
表锁 服务器层通用锁 显式使用,不常用
MDL锁 防止读写和DDL冲突 非常重要,长事务是杀手
行锁-记录锁 锁定单行 InnoDB高并发基础
行锁-间隙锁 间隙 解决幻读 RR隔离级别特有
行锁-临键锁 行+间隙 默认行锁算法 RR隔离级别
意向锁 协调行锁与表锁的关系 提高表锁检查效率

建议

  • 大多数场景下,使用InnoDB引擎和可重复读(RR) 隔离级别。
  • 写SQL时,一定要确保WHERE条件能用上索引,避免行锁升级为表锁。
  • 避免大事务,事务要尽快提交,这是预防MDL锁问题、死锁问题和锁等待的最有效方法。
  • 分析复杂锁问题可以使用命令 SHOW ENGINE INNODB STATUS\G 查看最近的死锁信息。