MySQL 9.2 向量搜索内存真相:6KB/行 × 100 万行 = 6GB,你的 BufferPool 撑得住吗?
一个被忽视的容量问题
“MySQL 9.2 有原生向量搜索了”——这消息传开之后,很多团队的迁移冲动来了:
“我们之前用的 Milvus 太重了,把向量数据迁回 MySQL 吧,运维简单很多。”
然后开干:
- 创建
embedding VECTOR(1536)列 - 把 100 万条文档的 embedding 灌进去
- 跑了一次相似度查询
- 应用层开始抖动
- 查 MySQL 监控:
Innodb_buffer_pool_wait_free飙到 50+ - 内存告警
MySQL 9.2 真的能扛百万级向量吗?能。但你必须算清楚内存。
先算账:1536 维 × 4 字节 = 6KB/行
向量的物理大小:
1 | embedding VECTOR(1536) |
这只是纯向量数据。加上 InnoDB 的页结构(默认 16KB 一页)、行头(compact 格式约 27 字节)、主键索引、二级索引,每行实际占 7-8KB。
| 向量规模 | 向量数据本身 | 含索引和行结构 |
|---|---|---|
| 10 万行 | 600 MB | 800 MB |
| 100 万行 | 6 GB | 8 GB |
| 500 万行 | 30 GB | 40 GB |
| 1000 万行 | 60 GB | 80 GB |
这只是HNSW 索引之外的基础存储。HNSW 索引本身还要再占一份。
HNSW 索引的内存占用
HNSW(Hierarchical Navigable Small World)是一个多层图结构,每个节点存 M 个邻居的引用。
公式(粗略):
1 | HNSW 内存 ≈ 节点数 × M × 8 bytes(指针) × 层数因子 |
默认值 M=16,ef_construction=100:
| 向量规模 | HNSW 索引内存(估算) |
|---|---|
| 10 万行 | 200-400 MB |
| 100 万行 | 2-4 GB |
| 500 万行 | 10-20 GB |
| 1000 万行 | 20-40 GB |
所以百万级向量的总内存消耗:业务数据 8GB + HNSW 索引 4GB = 12GB。这只是向量相关的部分,业务数据本身的内存还没算。
InnoDB BufferPool 的真实压力
MySQL 的 BufferPool 缓存的是数据页。默认配置下:
1 | SHOW VARIABLES LIKE 'innodb_buffer_pool_size'; |
要扛住百万级向量 + 业务数据 + HNSW 索引,BufferPool 至少要:
1 | BufferPool = (业务数据 + 向量数据 + 索引) × 1.5(留 50% 余量) |
配置:
1 | # my.cnf |
30GB 的 BufferPool 意味着你的 MySQL 服务器至少 64GB 物理内存(留出其他开销)。
量化压缩:内存优化的关键开关
HNSW 索引可以量化到 int8,内存减少 4 倍:
1 | -- MySQL 9.2 暂未原生支持 int8 量化(参考 PolarDB-X 等实现) |
或者用 SQ8 量化(Scalar Quantization 8-bit):
1 | CREATE INDEX idx_vec ON documents(embedding) USING HNSW ( |
效果:
- 内存减少 4 倍(30GB → 7.5GB)
- 召回率下降 1-3%
- 查询速度提升 1.5-2 倍
生产环境强烈推荐开量化。除非你的召回率要求是 0.999+。
多租户场景:Scoped Vector Search 的内存意义
MySQL 9.2 支持带过滤条件的向量检索(Scoped Vector Search):
1 | SELECT id, content FROM document_embeddings |
这看起来是 SQL 能力,但本质是”安全 + 内存”问题:
- 安全:多租户 RAG 必须确保用户 A 搜不到用户 B 的文档
- 内存:如果每次查询都”全表扫 + 过滤”,1000 万行的总内存会频繁被访问
MySQL 9.2 的优化是:在 HNSW 搜索前先用 user_id 索引过滤,只在子集上做 ANN。
但这要求:
1 | CREATE INDEX idx_user ON document_embeddings(user_id); |
两个索引并存会进一步增加内存占用。所以:
- 业务规模小(百万级):可以放心开
- 业务规模大(千万级):考虑”按 user_id 分表”或”专门的租户隔离设计”
真实生产案例:100 万行 RAG 的内存规划
我自己在做的一个 RAG 知识库:
- 数据量:100 万文档切片,每片 1536 维 embedding
- 业务表:用户、权限、文档元数据
- 查询模式:按 user_id 过滤 + 向量搜索 top 10
最终内存规划:
1 | [mysqld] |
实测数据:
- 向量数据 + HNSW 索引总占用:22GB(含量化)
- 业务表 + 二级索引:8GB
- BufferPool 命中率:98.5%
- P99 延迟:85ms
百万级向量 + 简单业务场景,64GB BufferPool 配量化是合理配置。
千万级怎么办?
如果向量规模到了 1000 万+,MySQL 9.2 的边界就到了:
| 维度 | MySQL 9.2 | 专用向量库(Milvus/Qdrant) |
|---|---|---|
| 千万级查询延迟 | 500ms+ | 50-100ms |
| 内存成本 | 高(关系型 + 向量混合) | 低(专门优化) |
| 运维成本 | 低(已有 MySQL 团队) | 高(新增组件) |
| 召回率 | 0.93-0.95(量化后) | 0.95+ |
| 多租户隔离 | 弱(靠 SQL 过滤) | 强(分区、租户感知) |
千万级是 MySQL 9.2 的真实边界。超过这个量,专用向量库的优势重新显现。
4 个容量规划的反模式
反模式 1:默认 BufferPool 配 128MB
部署完 MySQL 9.2 就直接灌百万级向量,BufferPool 还是默认的 128MB。结果是:
- HNSW 索引频繁从磁盘加载
- 查询延迟 1-3 秒
- 磁盘 IO 100%
必须根据向量规模调大 BufferPool。
反模式 2:不开量化,强行用 float32
有些团队担心”量化降低召回率”,所以全用 float32。结果内存爆炸。
正确做法:默认开量化,只在召回率不达标时关闭。这是 1-3% 召回率换 4 倍内存的交易,绝大多数场景划算。
反模式 3:业务表和向量表用同一个表
1 | -- 错误:业务字段和向量混在一起 |
问题:
- 按 user_id 查询时,每行 8KB 大小,BufferPool 命中率暴跌
- 业务查询和向量查询互相挤占 BufferPool
正确做法:
1 | -- 业务表 |
两个表 BufferPool 隔离,互不干扰。
反模式 4:用 HNSW 索引在写密集场景
HNSW 索引的更新代价高:
- 每条 INSERT 要更新 HNSW 图(ef_construction 决定搜索深度)
- 频繁写入会让 HNSW 图频繁 rebalance
如果你的 RAG 场景是”写少读多”(典型的离线知识库):
- HNSW 合适
- 可以凌晨批量灌入,白天查询
如果是”写密集”(比如用户实时上传文档):
- HNSW 不合适
- 考虑 IVFFlat(更快构建、更低质量)或定期重建 HNSW
容量规划清单
部署 MySQL 9.2 做 RAG 前,必算:
- 向量总行数 × 6KB = 向量数据大小
- HNSW 索引大小 ≈ 向量行数 × 50 bytes
- BufferPool = (业务表 + 向量表 + 索引) × 1.5
- 物理内存 ≥ BufferPool + 16GB(其他开销)
- 磁盘 ≥ (BufferPool × 2)(双缓冲)
百万级向量需要 64GB 内存 + SSD 磁盘。
千万级向量需要 256GB 内存 + NVMe 磁盘 + 考虑专用向量库。
意味着什么?
MySQL 9.2 的向量搜索”能用”和”用好”之间的距离很大:
- 能用:建个 HNSW 索引、跑个 query,500ms 出结果
- 用好:理解内存模型、配置 BufferPool、量化压缩、隔离表设计
没有容量规划的”MySQL 9.2 替代 Milvus”决策,一定会撞上内存墙。
数据密集型 AI 后端的工程师,对内存模型的直觉是核心竞争力。不是”建索引能用就行”,是”上线前算清楚:100 万行 6GB、千万级 60GB、BufferPool 要多大”。
这套直觉需要在真实项目里练出来。文章是知识,踩坑是经验。
