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

07 - 总结:一张表看懂所有锁

走到最后一章。前面的锁概念看起来很多,其实可以用几个维度串起来。这一章把它们汇总成速查表和决策图,方便你随时回看。

一句话总结全系列:锁是「用排队换正确」;PostgreSQL 靠 MVCC 让读写不互相阻塞,只在写写冲突时才真正排队。


7.1 锁的分类全景图

                        数据库锁

        ┌──────────────────┼──────────────────┐
     按粒度分            按模式分            按策略分(思路)
   (锁多大范围)       (读锁还是写锁)      (怎么应对冲突)
        │                  │                   │
   ┌────┴────┐        ┌────┴────┐        ┌─────┴─────┐
   表锁      行锁      共享锁    排他锁     悲观锁      乐观锁
 (整张表)  (几行)     (读锁)    (写锁)   (先锁再改)  (版本号检测)
   并发差   并发好    读读相容  独占       冲突多用    冲突少用

7.2 核心概念速查表

概念一句话关键区别
表锁锁整张表简单、并发差,用于改表结构
行锁只锁涉及的行并发好、开销大,日常主力
共享锁(S/读锁)我在读,你也能读读读相容
排他锁(X/写锁)我在改,谁都别碰读写、写写互斥
悲观锁假设会冲突,先锁再改FOR UPDATE,冲突多时用
乐观锁假设不冲突,版本号检测不加锁 + 重试,冲突少时用
MVCC多版本,读写不阻塞PG 并发核心,写写仍靠行锁
死锁互相等锁成环统一加锁顺序来避免

7.3 兼容性规则(背下来)

读 + 读  → ✅ 相容(可以同时)
读 + 写  → ❌ 互斥(要排队)
写 + 写  → ❌ 互斥(要排队)

口诀:读读相容,沾写就斥。

在 PostgreSQL 里,因为 MVCC 的存在,还要补一句:

普通读(SELECT)不加锁 → 靠 MVCC 读快照,读写实际不阻塞
只有显式 FOR UPDATE / FOR SHARE,或 UPDATE/DELETE,才加行锁

7.4 实战决策:并发场景该怎么办

遇到「先查后改」的并发场景?


能不能用一条 UPDATE 搞定?(如 SET x = x - 1 WHERE ...)

   ┌────┴────┐
  能         不能(必须在应用里读出来算)
   │              │
   ▼              ▼
直接 UPDATE    冲突激烈吗?
(天然原子)         │
              ┌────┴────┐
            激烈       不激烈
              │          │
              ▼          ▼
           悲观锁      乐观锁
        (FOR UPDATE)  (版本号+重试)

7.5 PostgreSQL vs MySQL(InnoDB) 的关键差异(拓展)

如果你以后接触 MySQL,注意这些不同:

方面PostgreSQLMySQL InnoDB
并发核心MVCC(旧版本单独存,靠 VACUUM 清理)MVCC(旧版本存在 undo log)
幻读处理Repeatable Read 就防住了幻读间隙锁(Gap Lock) 防幻读
间隙锁没有 InnoDB 那种间隙锁概念有,锁「行之间的间隙」防插入
意向锁概念较弱明确的表级意向锁

结论:通用理论(粒度、模式、乐观悲观)两者相通,但具体实现细节(尤其是幻读和间隙锁)差别较大。学 PG 时不必纠结 InnoDB 的间隙锁。


7.6 五条核心心法

1. 没有并发就没有锁 —— 锁是为了并发正确
2. 读读相容,沾写就斥 —— 兼容性的核心
3. PG 靠 MVCC,读写不互相挡 —— 只有写写才真正排队
4. 冲突少用乐观锁,冲突多用悲观锁 —— 实战选择
5. 统一加锁顺序 + 缩短事务 + 应用重试 —— 防死锁三件套

结语

锁的世界看起来名词繁多,但拆开看,无非是:

在「多大范围」上、加「读锁还是写锁」、用「先锁还是后检测」的策略,来保证并发下的数据正确。而 PostgreSQL 用 MVCC 把「读」这条最频繁的路径从锁里解放了出来。

把这句话理解透,再配合本章的速查表,你就能从容应对绝大多数并发场景。


上一章 ← 06 - 死锁:产生、排查与避免 | 回到 README