数据库的锁(Lock)是用来控制并发访问数据库中的数据,以保证数据一致性和完整性的机制。锁可以防止多个事务同时操作同一数据导致的数据不一致问题。
1. 数据库锁的分类
数据库锁大致可以从锁的范围、锁的粒度、锁的模式等多个维度进行分类。
(1)按锁的范围分类
-
行级锁(Row Lock):锁定特定的行,粒度最小,并发性能最好。适用于高并发环境,但开销较大。
-
表级锁(Table Lock):锁定整张表,开销小,但并发性能较低。适用于批量操作,如
ALTER TABLE
、DROP 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):两个或多个事务互相等待对方释放锁,导致事务无法继续执行。
-
解决方案:
-
减少锁的粒度,尽量使用行锁代替表锁。
-
控制事务执行顺序,让所有事务按照相同的顺序获取锁。
-
设置超时时间,让事务在一定时间内自动回滚。
-
-
-
幻读(Phantom Read):事务在两次查询之间,看到了一些之前不存在的数据。
-
解决方案:
-
使用 Next-Key Lock 预防幻读(如 MySQL
REPEATABLE READ
隔离级别)。 -
使用 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. 总结
-
锁的粒度越小,并发性能越好,但管理开销越大。
-
行级锁并发最高,但可能导致死锁,表级锁开销小但并发低。
-
乐观锁适用于读多写少,悲观锁适用于写多的场景。
-
要避免死锁,可以控制事务顺序,减少锁的范围,并设置超时机制。