[面试题] InnoDB 中的表级锁、页级锁、行级锁?
📖 版块 A:知识讲解版 (温故知新)
-
表级锁 (Table Lock):
- 特点:锁定整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- InnoDB 中的应用:除了显式的
lock tables,还包括意向锁(IS/IX)、自增锁(AUTO-INC)和元数据锁(MDL)。
-
行级锁 (Row Lock):
- 特点:锁定单行或多行记录。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。
- 实现机制:InnoDB 的行锁是加在索引项上的,而不是加在记录本身上的。如果查询没有走索引,InnoDB 会退化为表锁(通过隐藏聚集索引锁定全表)。
- 具体类型:记录锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next-Key Lock)。
-
页级锁 (Page Lock):
- 特点:介于表锁和行锁之间。锁定数据库页(通常是 16KB)。
- 应用:在 MySQL 的 BDB(BerkeleyDB)存储引擎中应用较多,但在现主流的 InnoDB 存储引擎中,并不直接支持传统意义上的页级锁。不过,InnoDB 在操作 B+ 树页分裂或合并时,会对索引页加闩锁(Latch),这属于内核层面的保护。
🎤 版块 B:面试实战版 (高手逐字稿)
Q:InnoDB 中的表级锁、页级锁、行级锁?
💬 高手逐字稿:
关于数据库锁,我们通常按照锁的粒度将其分为表锁、行锁和页锁。
首先是表级锁。它是 MySQL 中粒度最大的一种锁,加锁逻辑简单,开销极小,因为它不需要在每一行上维护锁状态,所以也不会产生死锁。但缺点是并发性能差。在 InnoDB 中,除了手动执行 LOCK TABLES,最常见的表锁是“意向锁”。这里有个细节:如果没有意向锁,当我们要给表加排他锁时,必须逐行扫描检查是否有行锁,这在千万级数据下是不可接受的。意向锁的存在,就是为了快速标识表中是否正有行被锁定,从而提高加表锁的效率。
其次是行级锁。这是 InnoDB 的核心优势。行锁的粒度最细,并发性能最高,但相对的开销也大,且容易产生死锁。这里有一个非常关键的底层逻辑:InnoDB 的行锁不是锁在数据记录上的,而是锁在索引上的。这意味着,如果你的 SQL 语句没有命中索引,InnoDB 无法通过索引定位到具体行,为了保证数据一致性,它会锁定聚集索引的所有记录,表现出来就像是锁住了整张表。此外,InnoDB 为了解决 RR(可重复读)隔离级别下的幻读问题,还在行锁的基础上演化出了间隙锁(Gap Lock)和临键锁(Next-Key Lock)。
最后是页级锁。它的粒度和性能介于表锁和行锁之间。虽然早期的 BDB 引擎支持页锁,但在现代生产环境下,由于 InnoDB 已经是主流,我们更关注 InnoDB 内部的 Latch(闩锁)。这种锁通常用于保护 B+ 树的物理页结构,比如在页分裂或页合并时触发,它对开发者是透明的,且持有时间极短。
💡 高手自问自答(细节进阶):
“这里我也深入思考过一个问题,既然 InnoDB 已经有了行锁,为什么在查看锁状态时(比如 show engine innodb status),还能看到大量的表锁记录?”
其实,这是因为 InnoDB 采用了锁协议(Locking Protocol)。在对某行加排他锁(X锁)之前,必须先在表级别加一个意向排他锁(IX锁)。这种设计保证了多粒度锁并存时的安全性。另外,当某些大事务导致锁资源占用过多,或者发生锁升级时(虽然 InnoDB 极少像 SQL Server 那样进行锁升级),表锁也会作为一种兜底策略存在,以保护内存不被海量的行锁对象撑爆。