浅谈数据库的锁机制

数据库的锁(Lock)是用来控制并发访问数据库中的数据,以保证数据一致性和完整性的机制。锁可以防止多个事务同时操作同一数据导致的数据不一致问题。

1. 数据库锁的分类

数据库锁大致可以从锁的范围锁的粒度锁的模式等多个维度进行分类。

(1)按锁的范围分类

  • 行级锁(Row Lock):锁定特定的行,粒度最小,并发性能最好。适用于高并发环境,但开销较大。

  • 表级锁(Table Lock):锁定整张表,开销小,但并发性能较低。适用于批量操作,如 ALTER TABLEDROP TABLE 等。

  • 页级锁(Page Lock):锁定一页数据(如 8KB),兼顾并发与性能,但比行锁粒度粗,比表锁更灵活,一般用于老旧数据库(如 SQL Server)。

(2)按锁的模式分类

  • 共享锁(S 锁 / Share Lock):允许多个事务同时读取数据,但不允许修改数据。

  • 排他锁(X 锁 / Exclusive Lock):独占资源,既不允许其他事务读取,也不允许修改,适用于更新操作。

(3)按锁的控制方式分类

  • 乐观锁(Optimistic Lock):不加锁,而是通过版本号或时间戳等机制控制并发,适用于读多写少的场景。

  • 悲观锁(Pessimistic Lock):直接加锁,确保只有一个事务可以访问数据,适用于写多的场景。

(4)按锁的级别分类(MySQL InnoDB 特有)

  • 意向锁(Intention Lock):表级锁,标识事务准备加行锁,提高锁的管理效率。

    • 意向共享锁(IS):事务打算在某些行上加共享锁。

    • 意向排他锁(IX):事务打算在某些行上加排他锁。

  • 间隙锁(Gap Lock):锁定某个范围但不包含本身数据,防止“幻读”(Phantom Read)。

  • 临键锁(Next-Key Lock):行锁+间隙锁,避免幻读问题。

2. 数据库锁的应用

  • MySQL InnoDB 使用行级锁(默认),支持 MVCC(多版本并发控制),避免过多加锁造成性能下降。

  • Oracle 使用乐观锁+行级锁,并且使用 Undo 进行版本回滚,支持 MVCC。

  • SQL Server 支持表锁、页锁、行锁,默认使用行锁。

3. 常见的锁问题

  • 死锁(Deadlock):两个或多个事务互相等待对方释放锁,导致事务无法继续执行。

    • 解决方案

      1. 减少锁的粒度,尽量使用行锁代替表锁。

      2. 控制事务执行顺序,让所有事务按照相同的顺序获取锁。

      3. 设置超时时间,让事务在一定时间内自动回滚。

  • 幻读(Phantom Read):事务在两次查询之间,看到了一些之前不存在的数据。

    • 解决方案

      1. 使用 Next-Key Lock 预防幻读(如 MySQL REPEATABLE READ 隔离级别)。

      2. 使用 SERIALIZABLE 隔离级别,会强制使用表锁。

4. 示例

(1)行级锁示例

BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;  -- 加排他锁(X 锁)
-- 其他事务无法修改 id=1 的记录,必须等待锁释放
COMMIT;

(2)死锁示例

事务 A:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- 锁住 id=1
UPDATE accounts SET balance = balance + 100 WHERE id = 2;  -- 等待 id=2

事务 B:

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 2;  -- 锁住 id=2
UPDATE accounts SET balance = balance + 100 WHERE id = 1;  -- 等待 id=1

 解决方案

SET innodb_lock_wait_timeout = 5;  -- 5 秒超时自动回滚

5. 总结

  • 锁的粒度越小,并发性能越好,但管理开销越大。

  • 行级锁并发最高,但可能导致死锁,表级锁开销小但并发低。

  • 乐观锁适用于读多写少悲观锁适用于写多的场景

  • 要避免死锁,可以控制事务顺序,减少锁的范围,并设置超时机制