为什么内存是数据库的”主战场”
数据库的核心矛盾是:数据在磁盘上,但计算必须在内存里。内存 (DRAM) 比磁盘快约 10⁵ 倍,因此”把热点数据留在内存”是几乎所有性能优化的归宿——从缓冲池到列式缓存。
DRAM 存储单元
每个比特由一对电容 + 晶体管组成:
word line (行选通)
│
┌───────┴───────┐
│ 晶体管 │
│ (开关) │
└───────┬───────┘
│
╱╲ 电容 ──── bit line (列读取)
╱ ╲ (电荷)
╱____╲
充电=1 放电=0
- 电容充电 → 逻辑 1,放电 → 逻辑 0
- 晶体管像开关,行选通时连通,由列线读出电荷
- 电容会漏电,电荷随时间衰减,因此必须定期”刷新”
刷新 (Refresh)
DRAM 必须在电容漏电前重新充电,典型刷新周期约 64 ms(每 64ms 内每行至少刷新一次)。刷新期间内存不可访问,带来微小但存在的停顿。这也是 DRAM 被称为”易失性”的原因——断电即丢数据。
行列地址与访问时序
内存按矩阵寻址:先送行地址 (RAS) 打开一行到感应放大器,再送列地址 (CAS) 选中具体字节。这对应数据库内存访问的延迟组成:
访问延迟 ≈ tRAS(行激活) + tCAS(列选中) + 传输
典型: ~13ns + ~13ns + 突发传输
带宽与通道
内存不是单条,而是由通道 (Channel) 并联提升吞吐:
CPU ──┬── 通道0 (DDR5) ── DIMM
├── 通道1 (DDR5) ── DIMM
└── 通道2 (DDR5) ── DIMM
↑ 双/四通道下带宽翻倍
- 单通道 DDR5 约 30-50 GB/s,多通道叠加
- 容量瓶颈易解,带宽瓶颈难解:多核并发扫描大表时,常先撞上内存带宽上限而非容量
NUMA 架构
多路服务器中,CPU 与内存分属不同NUMA 节点,跨节点访问更慢:
┌──────────┐ ┌──────────┐
│ NUMA 0 │ │ NUMA 1 │
│ CPU0 │ │ CPU1 │
│ 本地内存 │─慢─│ 远程内存 │
└──────────┘ QPI └──────────┘
- 本地内存访问 ~100 ns,跨节点 ~150-200 ns
- 数据库绑核 (numactl) 让线程访问本地内存,避免跨节点抖动
- PostgreSQL 的
shared_buffers过大若跨 NUMA,反而因远端访问变慢
与数据库的关系
| 内存特性 | 数据库应对 |
|---|---|
| 比磁盘快 10⁵ 倍 | 缓冲池缓存热点页 |
| 易失 + 需刷新 | WAL/检查点保证持久化 |
| 64B 缓存行 | 行/页按缓存行对齐 |
| 带宽有限 | 控制并行扫描线程数 |
| NUMA 远端慢 | 绑核 + 本地内存分配 |
大页 (Huge Pages):默认 4KB 页表项过多,TLB 命中率低;用 2MB/1GB 大页减少 TLB miss,是 PostgreSQL/Oracle 常见优化。
参考
- DDR5 SDRAM Standard, JEDEC
- NUMA Best Practices, Red Hat
- PostgreSQL: Huge Pages