# 第 6 章 · 代码示例：索引体系实战

本目录的脚本配合本章 `init.sql` 演示 PostgreSQL 六大索引方法 + 高级特性，
所有用到的表都加了 **`ch6_` 前缀**（`ch6_orders` / `ch6_users` / `ch6_logs` / `ch6_docs`），
索引则统一以 **`idx_ch6_...`** 命名，避免和其他章节冲突。

## 准备工作

1. 一个本地或测试用的 PostgreSQL 实例（建议 14+，部分 `INCLUDE` / 扩展统计需要新版本）。
2. 安装 `psycopg`（v3）：
   ```bash
   pip install "psycopg[binary]>=3.1"
   ```
3. 设置连接信息（与上一章相同）：
   ```bash
   export PGHOST=127.0.0.1 PGPORT=5432 PGUSER=postgres PGDATABASE=learn
   ```
4. 先初始化数据（百万行级，耗时几十秒）：
   ```bash
   psql -f ../init.sql
   ```

## 脚本一览

| 脚本 | 关键 PG 特性 |
|------|-------------|
| `01_btree_vs_seqscan.py` | B-Tree 单列 / 复合索引、`INCLUDE` 覆盖索引、`EXPLAIN (ANALYZE, BUFFERS)` 对比 |
| `02_gin_jsonb.py` | JSONB GIN 索引（`jsonb_ops` vs `jsonb_path_ops`），`@>` 包含查询 |
| `03_partial_expression_index.py` | 部分索引（`WHERE deleted_at IS NULL`）、表达式索引（`LOWER(email)`） |
| `04_explain_analyze.py` | `EXPLAIN` 三种形态、读 cost / actual / Buffers / Rows Removed by Filter |
| `05_brin_timeseries.py` | BRIN 在时序大表上的体积优势、`pages_per_range` 调优 |

`_common.py` 提供统一的连接函数 `connect()`，所有脚本共享。

## 预期输出

以 `01_btree_vs_seqscan.py` 为例（百万行 `ch6_orders`）：

```
== 无索引：Seq Scan ==
Seq Scan on ch6_orders  (cost=0.00..18334.00 rows=200 width=24)
                        (actual time=0.012..89.45 rows=187 loops=1)
  Filter: (user_id = 12345)

== 建 idx_ch6_orders_user 后：Index Scan ==
Index Scan using idx_ch6_orders_user on ch6_orders
  (cost=0.42..12.30 rows=200) (actual time=0.018..0.21 rows=187)
```

`05_brin_timeseries.py` 会展示 BRIN 体积只有 B-Tree 的 1/100 量级；
`02_gin_jsonb.py` 会显示 `jsonb_path_ops` 比默认 `jsonb_ops` 更小、查询更快。

## 常见报错

- `relation "ch6_xxx" does not exist`：先跑 `psql -f ../init.sql` 初始化。
- `EXPLAIN ANALYZE` 看到的 `Buffers: read` 远多于 `hit`：缓存还是冷的，多跑两次再观察。
- `CREATE INDEX CONCURRENTLY` 报错 `cannot run inside a transaction block`：
  改用 `psycopg` 的 `autocommit=True` 模式，脚本里已经处理。
