问题

InnoDB中的表级锁、页级锁、行级锁?

答案

核心概念

锁粒度是指锁定资源的大小范围。粒度越小,并发度越高,但锁管理的开销越大;粒度越大,并发度越低,但锁管理的开销越小。InnoDB主要支持表级锁和行级锁,页级锁在早期版本BDB引擎中使用。

三种锁粒度对比

1. 表级锁(Table-Level Lock)

特点:

  • 锁定整张表,是最大粒度的锁
  • 开销最小,加锁快
  • 不会出现死锁
  • 并发度最低

InnoDB中的表级锁类型:

-- 意向共享锁(IS)
SELECT ... LOCK IN SHARE MODE;  -- 自动加IS锁到表

-- 意向排他锁(IX)
SELECT ... FOR UPDATE;          -- 自动加IX锁到表

-- 表级共享锁(显式)
LOCK TABLES table_name READ;

-- 表级排他锁(显式)
LOCK TABLES table_name WRITE;

-- AUTO-INC锁(自增列)
INSERT INTO table_name VALUES (...);  -- 自动加AUTO-INC锁

适用场景:

  • 大批量数据更新操作(如整表导入)
  • 需要保证整表数据一致性
  • 查询涉及大部分表数据

2. 页级锁(Page-Level Lock)

特点:

  • 锁定数据页(通常16KB)
  • 开销和加锁速度介于表锁和行锁之间
  • 会出现死锁
  • 并发度中等

现状:

  • InnoDB不直接使用页级锁
  • BDB引擎支持页级锁(MySQL 5.1后已移除)
  • 某些特殊场景下,多个行锁可能锁定同一页,实际效果类似页级锁

3. 行级锁(Row-Level Lock)

特点:

  • 锁定单行记录(实际是索引记录)
  • 开销最大,加锁慢
  • 会出现死锁
  • 并发度最高,是InnoDB的核心优势

InnoDB行级锁类型:

-- 记录锁(Record Lock):锁定索引记录
SELECT * FROM users WHERE id = 1 FOR UPDATE;  -- 主键查询

-- 间隙锁(Gap Lock):锁定索引记录之间的间隙
SELECT * FROM users WHERE id = 5 FOR UPDATE;  -- id=5不存在

-- 临键锁(Next-Key Lock):记录锁+间隙锁
SELECT * FROM users WHERE age > 20 FOR UPDATE;  -- 范围查询

-- 共享行锁
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;

-- 排他行锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;

性能对比与选择

锁粒度 加锁开销 并发性能 死锁风险 内存占用 主要引擎
表级锁 最小 最低 最小 MyISAM
页级锁 中等 中等 中等 BDB(已弃用)
行级锁 最大 最高 最大 InnoDB

InnoDB为何主要使用行级锁

/**
 * InnoDB锁管理示例(伪代码)
 */
public class InnoDBLockManager {

    // 行级锁允许高并发
    public void updateUser(int id, String name) {
        // 只锁定id对应的索引记录
        lockRow("users", id);  // 其他行可并发修改
        update("UPDATE users SET name = ? WHERE id = ?", name, id);
        unlockRow("users", id);
    }

    // 表级锁阻塞所有操作
    public void updateUserWithTableLock(int id, String name) {
        lockTable("users");    // 整表被锁,其他事务全部等待
        update("UPDATE users SET name = ? WHERE id = ?", name, id);
        unlockTable("users");
    }
}

锁升级机制

注意:InnoDB不支持锁升级(Lock Escalation)。

  • 即使锁定大量行,也不会自动升级为表锁
  • 锁信息存储在缓冲池中,由innodb_buffer_pool_size控制
  • 当内存不足时,InnoDB会报错而不是升级锁粒度

实际应用建议

  1. 优先使用行级锁:
    • 保证查询条件有索引
    • 避免全表扫描导致锁表
  2. 避免隐式表锁:
    -- 错误:无索引导致锁表
    UPDATE users SET status = 1 WHERE name = 'Alice';  -- name无索引
    
    -- 正确:有索引只锁行
    UPDATE users SET status = 1 WHERE id = 10;  -- id是主键
    
  3. 合理使用意向锁:
    • 意向锁是表级锁,但不影响行级锁的并发
    • 意向锁用于协调行锁和表锁的兼容性

答题总结

InnoDB主要使用行级锁以支持高并发,通过意向锁(表级锁)实现多粒度锁定,实际不使用页级锁。行级锁虽然开销大,但在OLTP场景下能显著提高并发性能。关键是保证查询走索引,避免因全表扫描导致锁定过多行甚至锁表,这是InnoDB性能优化的核心原则。