当选择度低于 5% 时,如何创建复合索引避免回表?
解读
国内互联网与金融场景下,CouchDB 常被用来做离线-在线数据同步或微服务日志归档。面试官抛出“选择度低于 5%”这一极端过滤条件,核心想验证两点:
- 你是否理解 CouchDB 无回表的真正含义——B+Tree 索引即文档,只要索引里包含查询与排序所需的全部字段,就无需二次 fetch;
- 你是否能在 Mango 索引语法 与 视图设计 之间做权衡,把高过滤列放在最左,同时把返回列打包进索引,实现 Covering Index。
答不到“把返回字段塞进索引”就基本掉分,答不到“选择度低字段放最左”会被追问代价。
知识点
- 选择度(Selectivity):distinct 值 ÷ 总行数;低于 5% 说明该列极稀疏,是过滤黄金列,必须放在复合索引最左侧。
- CouchDB 回表本质:Mango 查询若所需字段不在同一索引内,内部会触发
_find二次读 doc,造成回表 IO;视图同理,若 emit 值不含后续使用字段,需include_docs=true回表。 - Covering Index:索引片里直接携带查询投影与排序字段,CouchDB 可直接从 .view 或 .idx 文件返回结果,无需再打开文档本体。
- Mango 复合索引语法:
{"index": {"fields": [{"high_selectivity:asc"}, {"low_selectivity:asc"}, {"return_fieldA:asc"}, {"return_fieldB:asc"}]}};字段顺序决定前缀匹配,低选择度列不能放最左。 - 视图方案:
emit([highSelectivity, lowSelectivity], {fieldA: doc.fieldA, fieldB: doc.fieldB});value 用 JSON 对象一次性携带返回字段,查询时startkey=[v1,v2]&endkey=[v1,v2,{}]&include_docs=false即可覆盖。 - 国内部署注意:
- 磁盘多使用 SSD,但 mobile 边缘节点 IO 弱,covering 收益更大;
- 复合索引字段数建议 ≤ 5,避免写入放大;
- 生产环境务必在 test 库 用
/_explain验证是否covering: true。
答案
步骤一:确认最高选择度列
假设订单表 1 亿条,status="PENDING" 仅 300 万条(3%),则 status 为最高选择度列。
步骤二:把查询与排序所需的所有字段按“过滤列 → 排序列 → 返回列”顺序打入复合索引
Mango 示例:
{
"index": {
"fields": [
{"status": "asc"},
{"createTime": "desc"},
{"orderId": "asc"},
{"userName": "asc"}
]
},
"ddoc": "idx_cover_order",
"type": "json"
}
查询语句:
{
"selector": {
"status": "PENDING"
},
"sort": [{"createTime": "desc"}],
"fields": ["orderId", "userName", "createTime"],
"limit": 100,
"use_index": "idx_cover_order"
}
此时 /_explain 返回 "covering": true,证明无回表。
视图方案:
function (doc) {
if (doc.type === 'order' && doc.status === 'PENDING') {
emit([doc.status, doc.createTime], {orderId: doc.orderId, userName: doc.userName});
}
}
查询:
GET /db/_design/order/_view/cover?startkey=["PENDING",{}]&endkey=["PENDING"]&descending=true&limit=100&include_docs=false
value 已含返回字段,无需 include_docs=true,实现零回表。
拓展思考
- 若返回字段很大(如商品详情 HTML),covering 索引会膨胀,需在 IO 与空间 之间权衡;可只 cover 主键,再用 bulk fetch 批量拿 doc,减少往返。
- 当查询条件出现 regex 时,Mango 可能退化为全表扫描;此时应拆成多条视图或利用 分区数据库(db-per-user) 把数据量先降一个量级。
- 国内合规要求 敏感字段脱敏,即使做 covering 也要在索引构建阶段调用
sha256哈希,避免明文落地。