# 第 11 章 锁机制 - 配套代码

本章脚本带你「亲眼看到」表锁兼容性、行锁排队、`SKIP LOCKED` 的并发威力、PG 自动死锁检测，以及咨询锁怎么做分布式互斥。

## 准备工作

1. 跑 `psql -h 127.0.0.1 -U postgres -d learn_pg -f ../init.sql` 初始化 `ch11_seckill` / `ch11_task_queue` / `ch11_bank_account`
2. 安装依赖：`pip install "psycopg[binary]>=3.1"`
3. （可选）`export PG_DSN="host=... port=... dbname=... user=..."` 覆盖默认连接
4. 推荐先把 `deadlock_timeout` 调小到 500ms 让脚本跑得更快：

```sql
ALTER SYSTEM SET deadlock_timeout = '500ms';
ALTER SYSTEM SET log_lock_waits = on;
SELECT pg_reload_conf();
```

## 脚本一览（推荐运行顺序）

| 脚本 | 一句话说明 | 关键 PG 特性 |
|------|------------|--------------|
| `01_table_lock_compat.py` | 在 A 持锁、B 请求锁的组合下验证兼容性矩阵 | 8 种表锁 / `LOCK TABLE` |
| `02_select_for_update.py` | 5 个 worker 并发消费 100 个任务，对比 `FOR UPDATE` vs `SKIP LOCKED` | `SELECT ... FOR UPDATE SKIP LOCKED` |
| `03_deadlock_detect.py` | 两个事务交叉锁住 `ch11_bank_account` 1/2 → PG 自动回滚 victim | `deadlock_timeout` / `40P01` |
| `04_advisory_lock.py` | 5 个线程抢 `pg_try_advisory_lock`，只有一个真正执行 | 会话级 / 事务级 advisory lock |
| `05_blocking_query.py` | 故意制造阻塞，再用 `pg_blocking_pids` 把阻塞链打印出来 | `pg_locks` / `pg_stat_activity` |

运行示例：

```bash
python 01_table_lock_compat.py
python 02_select_for_update.py --workers 8 --mode both
python 03_deadlock_detect.py
python 04_advisory_lock.py
python 05_blocking_query.py
```

## 预期输出

`02_select_for_update.py` 的对比效果非常直观（云盘上常见 5～10x 差距）：

```
=== mode=wait  workers=5  耗时=2.18s ===
总共消费 100 条，按 worker 分布：{'w0': 100, 'w1': 0, 'w2': 0, ...}    ← 串行

=== mode=skip  workers=5  耗时=0.42s ===
总共消费 100 条，按 worker 分布：{'w0': 21, 'w1': 19, 'w2': 23, ...}   ← 真正并行
```

## 常见报错

- `relation "ch11_seckill" does not exist` → 没跑 `init.sql`，先 `psql ... -f ../init.sql`
- `deadlock detected (40P01)` → `03_deadlock_detect.py` 故意触发，**这就是预期输出**
- `could not obtain lock on row in relation "ch11_seckill"` → `NOWAIT` 拿不到锁立即报错，预期内
- `connection refused` → PG 未启动，检查 `pg_isready -h 127.0.0.1`
- 脚本卡住 → 上一次脚本留下的 `idle in transaction` 还持着锁；用 `SELECT pg_terminate_backend(pid)` 清理
