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

02 - 按粒度分:表锁与行锁

锁有两个最重要的分类维度。这一章讲第一个:粒度——锁住的范围有多大。是锁一整张表,还是只锁其中几行?这直接决定了并发能力。

一句话先记住:锁的范围越小,并发越好,但管理开销越大;范围越大,管理越简单,但并发越差。


2.1 用一个比喻理解粒度

把数据库表想象成一栋楼,每一行是一个房间:

表锁 = 锁住整栋楼的大门
   → 一个人进去,所有人都进不来
   → 简单,但太霸道,并发极差

行锁 = 只锁你要用的那个房间
   → 你用 101 房,别人照样能用 102、103
   → 并发好,但要管很多把小锁,开销大

2.2 表锁(Table Lock)

锁住整张表。粒度最大。

事务A 锁住 users 表
  ┌──────────────┐
  │  users 表     │ 🔒 整张锁住
  │  id=1 ...     │
  │  id=2 ...     │  事务B 想改 id=999 的行?
  │  ...          │  → 也得等 A 释放整张表的锁
  └──────────────┘
  • 优点:加锁/解锁开销极小(就一把锁),不会死锁(同类场景下)
  • 缺点:并发极差——哪怕两个事务操作的是完全不同的行,也得排队

什么时候用表锁

  • 批量操作整张表(如 ALTER TABLE 改表结构,PostgreSQL 会加较强的表级锁)
  • 需要「锁住整张表做一致性操作」的特殊场景

在 PostgreSQL 中,可以手动加表锁:

LOCK TABLE users IN ACCESS EXCLUSIVE MODE;  -- 最强的表锁,几乎啥都不让干

实际业务中很少手动加表锁。日常的 DDL(改表结构)操作会自动加,需要注意它可能阻塞线上读写。


2.3 行锁(Row Lock)

只锁住涉及的那几行。粒度最小,是高并发场景的主力。

事务A 只锁 id=1 这一行
  ┌──────────────┐
  │  users 表     │
  │  id=1 ... 🔒  │ ← 只锁这行
  │  id=2 ...     │ ← 事务B 可以照常改这行
  │  id=3 ...     │ ← 事务C 可以照常改这行
  └──────────────┘
  • 优点:并发高——不同的行互不影响,大家可以同时操作
  • 缺点:要维护很多把锁,内存和管理开销大;多个行锁交错请求容易死锁(见第 06 章)

在 PostgreSQL 中,UPDATE / DELETE自动对涉及的行加行锁;也可以手动加:

BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE;  -- 手动锁住 id=1 这一行
-- 这期间,别的事务改 id=1 会被阻塞,但改 id=2 不受影响
UPDATE users SET name = '新名字' WHERE id = 1;
COMMIT;  -- 释放锁

2.4 表锁 vs 行锁 对比

维度表锁行锁
锁范围整张表涉及的几行
并发能力差(全表排队)好(不同行互不影响)
加锁开销极小(一把锁)较大(很多把锁)
死锁风险较高
典型场景改表结构、全表操作日常增删改(高并发)

一句话选择:追求并发就用行锁(数据库默认在增删改时就是行锁),只有极少数需要「锁住全表」的场景才用表锁。


2.5 PostgreSQL 的一个重要特点:读不加行锁

这是 PostgreSQL(以及采用 MVCC 的数据库)和「纯锁数据库」的关键区别,先记住结论,第 05 章展开:

在 PostgreSQL 里:
  · 普通的 SELECT(读)  → 不加锁!靠 MVCC 读一个「快照」
  · UPDATE / DELETE     → 对涉及行加行锁
  · SELECT ... FOR UPDATE → 手动给读加行锁(读了就锁住,防别人改)

结果:读和写「基本不互相阻塞」,这是 PG 高并发的核心优势。

所以在 PostgreSQL 里,你很少因为「有人在读」而被阻塞。这跟你直觉里的「锁」不太一样——原因就是 MVCC。


本章小结

你要记住要点
粒度含义锁住的范围大小
表锁锁整张表,简单但并发差,用于改表结构
行锁只锁涉及的行,并发好但开销大,日常主力
权衡粒度越小并发越好,但管理开销和死锁风险越大
PG 特点普通读不加锁(靠 MVCC),读写基本不互相阻塞

下一章预告:粒度讲完了,还有第二个维度——同样是锁一行,「读锁」和「写锁」能不能共存?这就是共享锁与排他锁。


上一章 ← 01 - 为什么需要锁 | 下一章 → 03 - 按模式分:共享锁与排他锁