面试题答案
一键面试索引字段顺序考量
- 按照查询频率和选择性排序:将在
WHERE
子句中最常使用且选择性高(能筛选出较少数据)的字段放在复合索引的前面。例如,在一个电商订单系统中,订单查询经常根据用户ID和订单状态进行,且不同用户ID的订单数量差异较大,用户ID选择性高,所以复合索引可以设计为{userId: 1, orderStatus: 1}
。这样,当执行类似db.orders.find({userId: "123", orderStatus: "completed"})
的查询时,MongoDB 能快速定位到相关数据。 - 遵循前缀匹配原则:复合索引遵循前缀匹配,只有当查询条件匹配索引的最左前缀时,索引才会被有效利用。例如
{a: 1, b: 1, c: 1}
索引,查询{a: 1}
、{a: 1, b: 1}
、{a: 1, b: 1, c: 1}
能利用该索引,但{b: 1}
或{b: 1, c: 1}
则无法利用。所以在设计时要考虑常用查询的前缀模式。
覆盖索引应用
- 定义:覆盖索引是指查询所需要的所有字段都包含在索引中,这样 MongoDB 无需回表查询文档就能返回结果,大大提高查询效率。
- 应用场景:在多文档事务场景下,如果事务中有频繁的读操作,可以设计覆盖索引。例如,在一个博客系统中,文章列表页展示文章标题、作者和发布时间,查询语句为
db.articles.find({}, {title: 1, author: 1, publishTime: 1, _id: 0})
,此时可以创建复合索引{title: 1, author: 1, publishTime: 1}
,这个索引覆盖了查询所需字段,避免了回表操作。
示例
假设有一个存储用户订单和订单详情的 MongoDB 数据库。订单文档结构如下:
{
"_id": ObjectId("64d188a76e6e2d8f0d9e4f16"),
"userId": "12345",
"orderStatus": "completed",
"orderDate": ISODate("2023-10-01T12:00:00Z"),
"totalAmount": 100.00,
"orderItems": [
{
"productId": "p1",
"quantity": 2,
"price": 50.00
},
{
"productId": "p2",
"quantity": 1,
"price": 30.00
}
]
}
假设经常按用户ID和订单状态查询订单,并统计总金额,可创建复合索引:
db.orders.createIndex({userId: 1, orderStatus: 1, totalAmount: 1});
这里 userId
和 orderStatus
作为最左前缀满足查询筛选需求,totalAmount
加入索引实现覆盖索引,统计时无需回表。对于订单详情中对 productId
的查询,如果频繁,可在订单详情数组内部文档上创建索引(MongoDB 支持对数组内文档字段创建索引),例如:
db.orders.createIndex({"orderItems.productId": 1});
这样在处理涉及订单详情中产品ID的操作时也能提高效率。