首页 / 知识库 / python服务端进阶 / 数据库锁详解

01 - 为什么需要锁

在讲各种锁之前,先搞清楚一个根本问题:数据库为什么要有锁? 答案很简单——因为很多人同时操作同一份数据时,不加控制就会出乱子

一句话先记住:锁的本质,是用「排队等待」换「数据正确」。没有并发,就不需要锁。


1.1 并发带来的四个经典问题

当多个事务同时读写同一份数据,可能出现这四种问题。理解它们,就理解了锁存在的意义。

问题一:脏读(Dirty Read)—— 读到别人没提交的数据

事务A                        事务B
  │                           │
  │ 把余额从 100 改成 500       │
  │ (还没提交)               │
  │                           │ 读到余额 = 500  ← 脏读!
  │ 回滚!余额变回 100          │
  │                           │ B 拿着错的 500 去干活了

B 读到了一个「根本不存在(被回滚了)」的值。

问题二:不可重复读(Non-repeatable Read)—— 同一行前后读的值不一样

事务A                        事务B
  │ 读余额 = 100               │
  │                           │ 把余额改成 500 并提交
  │ 再读余额 = 500  ← 咦?变了! │

A 在同一个事务里,两次读同一行,结果不一致。

问题三:幻读(Phantom Read)—— 前后查询的行数不一样

事务A                        事务B
  │ 查"年龄>18的用户"= 10 条     │
  │                           │ 插入一条年龄 20 的用户并提交
  │ 再查"年龄>18的用户"= 11 条  │  ← 多出一条,像幻觉一样

不可重复读针对「某一行的值变了」,幻读针对「行数变了(多/少了行)」。

问题四:丢失更新(Lost Update)—— 两个人改,一个人的改动被覆盖

事务A                        事务B
  │ 读库存 = 10                │ 读库存 = 10
  │ 卖出 1 个,算出 9           │ 卖出 1 个,算出 9
  │ 写回 9                     │
  │                           │ 写回 9   ← A 的扣减被覆盖了!
  │                           │
  结果:卖了 2 个,库存却只减了 1

这是最常见、最坑的并发问题,也是「乐观锁/悲观锁」重点要解决的(见第 04 章)。


1.2 锁:用「排队」保证正确

解决这些问题的核心手段之一就是:当一个事务在操作某份数据时,先「锁住」它,别人想动就得等。

没有锁(乱):              有锁(排队):
 A ─┐                      A ─┐
 B ─┼─▶ 同时改 → 乱          B ─┘  A 先锁住,改完释放
 C ─┘                          ▼
                            B 拿到锁,再改

                            C 拿到锁,再改

代价显而易见:排队 = 变慢。所以数据库的锁设计,一直在「正确」和「性能」之间做权衡——这也是后面各种锁的由来。


1.3 锁和「事务隔离级别」的关系

你在《PostgreSQL》教程第六章学过隔离级别。它和锁是什么关系?

  • 隔离级别 = 你想要「多干净」的并发效果(脏读/不可重复读/幻读能不能发生)
  • 锁 / MVCC = 数据库为了达到这个隔离级别,底层用的手段
隔离级别(目标)          底层实现(手段)
─────────────           ─────────────
Read Committed    ←──   MVCC + 必要时加锁
Repeatable Read   ←──   MVCC 快照
Serializable      ←──   更严格的锁 / 冲突检测
隔离级别脏读不可重复读幻读
Read Uncommitted可能可能可能
Read Committed(PG 默认)不会可能可能
Repeatable Read不会不会不会*
Serializable不会不会不会

*PostgreSQL 的 Repeatable Read 比 SQL 标准更严格,也防住了幻读。

关键认知:你平时设置隔离级别,其实就是在间接决定数据库「怎么加锁、加多严」。锁是隔离级别背后的引擎。


1.4 一个重要区分:锁是数据库自动加的,也可以手动加

  • 自动加锁:你执行 UPDATEDELETE 时,数据库会自动对涉及的行加锁,你无需操心。
  • 手动加锁:某些场景(如「先查后改」要防丢失更新),你需要主动加锁,比如 SELECT ... FOR UPDATE

后面章节会反复用到这两种情况,先有个印象。


本章小结

你要记住要点
锁的动机解决并发读写同一数据带来的问题
四个经典问题脏读、不可重复读、幻读、丢失更新
锁的本质用「排队等待」换「数据正确」,代价是变慢
与隔离级别隔离级别是目标,锁/MVCC 是实现手段
两种加锁数据库自动加 + 你手动加(FOR UPDATE)

下一章预告:锁可以锁一整张表,也可以只锁几行。粒度不同,性能和并发天差地别。我们先讲「按粒度分」。


下一章 → 02 - 按粒度分:表锁与行锁