1. 首页 > MongoDB教程 > 正文

MongoDB教程FG014-MongoDB分页与索引优化实战

本文档风哥主要介绍MongoDB分页与索引优化相关知识,包括MongoDB分页的概念、索引类型、索引原理、规划、实现方法、性能调优以及生产案例等内容,风哥教程参考MongoDB官方文档Indexing Strategies内容编写,适合DBA人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。

Part01-基础概念与理论知识

1.1 MongoDB分页的概念

MongoDB分页是指将查询结果分成多个页面进行显示的操作。在MongoDB中,分页通常通过skip()和limit()方法实现,或者使用游标进行分页。分页是Web应用和移动应用中常见的功能,对于处理大量数据的查询非常重要。更多视频教程www.fgedu.net.cn

MongoDB分页的特点:

  • 使用skip()和limit()方法实现基本分页
  • 使用游标进行高效分页
  • 支持基于时间戳或ID的分页
  • 可以结合排序和过滤条件使用
  • 需要考虑性能问题,特别是大数据量的分页

1.2 MongoDB索引类型

MongoDB支持多种类型的索引,主要包括:

# 1. 单字段索引
– 基于单个字段创建的索引
– 最常用的索引类型
– 语法:db.collection.createIndex({ field: 1 })

# 2. 复合索引
– 基于多个字段创建的索引
– 字段顺序很重要
– 语法:db.collection.createIndex({ field1: 1, field2: -1 })

# 3. 文本索引
– 用于全文搜索
– 支持多语言
– 语法:db.collection.createIndex({ field: “text” })

# 4. 地理空间索引
– 用于地理空间查询
– 包括2d、2dsphere等类型
– 语法:db.collection.createIndex({ location: “2dsphere” })

# 5. 哈希索引
– 基于字段的哈希值创建索引
– 用于等值查询
– 语法:db.collection.createIndex({ field: “hashed” })

# 6. 唯一索引
– 确保索引字段的值唯一
– 语法:db.collection.createIndex({ field: 1 }, { unique: true })

# 7. 稀疏索引
– 只包含有索引字段的文档
– 语法:db.collection.createIndex({ field: 1 }, { sparse: true })

# 8. 部分索引
– 只包含满足特定条件的文档
– 语法:db.collection.createIndex({ field: 1 }, { partialFilterExpression: { condition } })

1.3 MongoDB索引原理

MongoDB索引基于B树数据结构,具有以下特点:

  • B树结构:索引使用B树结构,支持高效的范围查询和排序
  • 索引键:索引键是文档中的字段值
  • 索引条目:每个索引条目包含索引键和指向文档的指针
  • 索引大小:索引会占用额外的存储空间
  • 索引维护:写入操作会更新索引,可能影响写入性能
风哥提示:MongoDB索引是提高查询性能的关键,合理的索引设计可以显著提升查询速度,减少系统资源消耗。学习交流加群风哥微信: itpux-com

Part02-生产环境规划与建议

2.1 MongoDB分页规划

MongoDB分页规划要点:

# 1. 分页策略选择
– 基于skip/limit的分页:适用于小数据量
– 基于游标或ID的分页:适用于大数据量
– 基于时间戳的分页:适用于时间序列数据

# 2. 分页参数设置
– 页面大小:根据业务需求设置合理的页面大小
– 排序字段:确保排序字段有索引
– 过滤条件:优化过滤条件,使用索引

# 3. 性能考虑
– 避免使用大skip值,会导致性能下降
– 考虑使用覆盖索引减少IO操作
– 监控分页查询的执行时间

# 4. 客户端分页
– 前端分页:减少数据传输量
– 后端分页:减轻前端负担
– 缓存策略:缓存热门页面

2.2 MongoDB索引规划

MongoDB索引规划要点:

# 1. 分析查询模式
– 识别常用查询
– 分析查询条件和排序需求
– 评估数据量和增长趋势

# 2. 索引设计原则
– 为常用查询创建索引
– 考虑复合索引的顺序
– 避免过度索引
– 定期审查和优化索引

# 3. 索引类型选择
– 单字段索引:适用于简单查询
– 复合索引:适用于多字段查询
– 文本索引:适用于全文搜索
– 地理空间索引:适用于地理查询

# 4. 索引维护策略
– 定期重建索引
– 监控索引使用情况
– 移除未使用的索引
– 优化索引存储

2.3 MongoDB优化策略

MongoDB优化策略:

  • 查询优化:优化查询语句,使用合适的操作符
  • 索引优化:创建合适的索引,避免全集合扫描
  • 分页优化:使用高效的分页策略,避免大skip值
  • 服务器优化:增加内存,优化存储系统
  • 应用程序优化:使用连接池,实现查询缓存
  • 监控与调优:定期监控查询性能,调整优化策略
生产环境建议:MongoDB分页与索引优化应结合业务需求和数据模型,选择合适的策略,确保系统性能满足业务要求。学习交流加群风哥QQ113257174

Part03-生产环境项目实施方案

3.1 MongoDB分页实现

3.1.1 基于skip/limit的分页

# 基本语法
db.collection.find(
, ).skip().limit()

# 示例:分页查询用户数据
fgedudb> db.fgedu_users.find().sort({ created_at: -1 }).skip(0).limit(10)
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h9”), name: “fgedu02”, created_at: ISODate(“2026-04-09T08:00:00Z”) },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”), name: “fgedu01”, created_at: ISODate(“2026-04-08T08:00:00Z”) },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h0”), name: “fgedu03”, created_at: ISODate(“2026-04-07T08:00:00Z”) }
]

# 示例:带过滤条件的分页
fgedudb> db.fgedu_users.find({ age: { $gt: 25 } }).sort({ age: -1 }).skip(0).limit(5)
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h9”), name: “fgedu02”, age: 28 },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h1”), name: “fgedu04”, age: 30 }
]

3.1.2 基于ID或时间戳的分页

# 基于ID的分页
fgedudb> db.fgedu_users.find({ _id: { $gt: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”) } }).sort({ _id: 1 }).limit(5)

# 基于时间戳的分页
fgedudb> db.fgedu_users.find({ created_at: { $gt: ISODate(“2026-04-08T00:00:00Z”) } }).sort({ created_at: 1 }).limit(5)

# 示例输出
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h9”), name: “fgedu02”, created_at: ISODate(“2026-04-09T08:00:00Z”) },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h1”), name: “fgedu04”, created_at: ISODate(“2026-04-10T08:00:00Z”) }
]

3.2 MongoDB索引实现

3.2.1 单字段索引

# 创建单字段索引
fgedudb> db.fgedu_users.createIndex({ name: 1 })
name_1

# 查看索引
fgedudb> db.fgedu_users.getIndexes()
[
{ v: 2, key: { _id: 1 }, name: ‘_id_’ },
{ v: 2, key: { name: 1 }, name: ‘name_1’ }
]

# 使用索引查询
fgedudb> db.fgedu_users.find({ name: “fgedu01” })
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”), name: “fgedu01”, age: 25 }
]

# 分析查询执行计划
fgedudb> db.fgedu_users.find({ name: “fgedu01” }).explain()
{
queryPlanner: {
plannerVersion: 1,
namespace: ‘fgedudb.fgedu_users’,
indexFilterSet: false,
parsedQuery: { name: { ‘$eq’: ‘fgedu01’ } },
winningPlan: {
stage: ‘FETCH’,
inputStage: {
stage: ‘IXSCAN’,
keyPattern: { name: 1 },
indexName: ‘name_1’,
isMultiKey: false,
multiKeyPaths: { name: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: ‘forward’,
indexBounds: { name: [“fgedu01”, “fgedu01”] }
}
},

},

}

3.2.2 复合索引

# 创建复合索引
fgedudb> db.fgedu_orders.createIndex({ user_id: 1, created_at: -1 })
user_id_1_created_at_-1

# 查看索引
fgedudb> db.fgedu_orders.getIndexes()
[
{ v: 2, key: { _id: 1 }, name: ‘_id_’ },
{ v: 2, key: { user_id: 1, created_at: -1 }, name: ‘user_id_1_created_at_-1’ }
]

# 使用复合索引查询
fgedudb> db.fgedu_orders.find({ user_id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”) }).sort({ created_at: -1 })
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h0”), user_id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”), created_at: ISODate(“2026-04-10T08:00:00Z”) },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h9”), user_id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”), created_at: ISODate(“2026-04-09T08:00:00Z”) }
]

# 分析查询执行计划
fgedudb> db.fgedu_orders.find({ user_id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”) }).sort({ created_at: -1 }).explain()
{
queryPlanner: {
plannerVersion: 1,
namespace: ‘fgedudb.fgedu_orders’,
indexFilterSet: false,
parsedQuery: { user_id: { ‘$eq’: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”) } },
winningPlan: {
stage: ‘FETCH’,
inputStage: {
stage: ‘IXSCAN’,
keyPattern: { user_id: 1, created_at: -1 },
indexName: ‘user_id_1_created_at_-1’,
isMultiKey: false,
multiKeyPaths: { user_id: [], created_at: [] },
isUnique: false,
isSparse: false,
isPartial: false,
indexVersion: 2,
direction: ‘forward’,
indexBounds: { user_id: [ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”), ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”)], created_at: [MaxKey, MinKey] }
}
},

},

}

3.2.3 文本索引

# 创建文本索引
fgedudb> db.fgedu_products.createIndex({ name: “text”, description: “text” })
name_text_description_text

# 查看索引
fgedudb> db.fgedu_products.getIndexes()
[
{ v: 2, key: { _id: 1 }, name: ‘_id_’ },
{ v: 2, key: { _fts: “text”, _ftsx: 1 }, name: ‘name_text_description_text’ }
]

# 使用文本索引查询
fgedudb> db.fgedu_products.find({ $text: { $search: “mongodb database” } })
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”), name: “MongoDB Database”, description: “A NoSQL database” },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h9”), name: “Database Course”, description: “Learn MongoDB and other databases” }
]

# 分析查询执行计划
fgedudb> db.fgedu_products.find({ $text: { $search: “mongodb database” } }).explain()
{
queryPlanner: {
plannerVersion: 1,
namespace: ‘fgedudb.fgedu_products’,
indexFilterSet: false,
parsedQuery: { ‘$text’: { ‘$search’: ‘mongodb database’ } },
winningPlan: {
stage: ‘TEXT’,
indexPrefix: {},
indexName: ‘name_text_description_text’,
parsedTextQuery: { terms: [“mongodb”, “database”], negatedTerms: [], phrases: [] },
inputStage: {
stage: ‘TEXT_MATCH’,
inputStage: {
stage: ‘FETCH’,
inputStage: {
stage: ‘OR’,
inputStages: [
{ stage: ‘IXSCAN’, keyPattern: { _fts: “text”, _ftsx: 1 }, indexName: ‘name_text_description_text’, … },
{ stage: ‘IXSCAN’, keyPattern: { _fts: “text”, _ftsx: 1 }, indexName: ‘name_text_description_text’, … }
]
}
}
}
},

},

}

3.2.4 地理空间索引

# 创建地理空间索引
fgedudb> db.fgedu_locations.createIndex({ location: “2dsphere” })
location_2dsphere

# 查看索引
fgedudb> db.fgedu_locations.getIndexes()
[
{ v: 2, key: { _id: 1 }, name: ‘_id_’ },
{ v: 2, key: { location: “2dsphere” }, name: ‘location_2dsphere’ }
]

# 插入地理空间数据
fgedudb> db.fgedu_locations.insertMany([
{ name: “Beijing”, location: { type: “Point”, coordinates: [116.4074, 39.9042] } },
{ name: “Shanghai”, location: { type: “Point”, coordinates: [121.4737, 31.2304] } }
])

# 使用地理空间索引查询
fgedudb> db.fgedu_locations.find({
location: {
$near: {
$geometry: {
type: “Point”,
coordinates: [116.4074, 39.9042]
},
$maxDistance: 1000000 // 1000公里
}
}
})
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”), name: “Beijing”, location: { type: “Point”, coordinates: [116.4074, 39.9042] } },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h9”), name: “Shanghai”, location: { type: “Point”, coordinates: [121.4737, 31.2304] } }
]

3.3 MongoDB性能调优

3.3.1 索引优化

# 1. 查看索引使用情况
fgedudb> db.collection.aggregate([
{ $indexStats: { } }
])

# 2. 重建索引
fgedudb> db.fgedu_users.reIndex()
{
“nIndexesWas”: 2,
“nIndexesAfter”: 2,
“indexes”: [
{ “key”: { “_id”: 1 }, “name”: “_id_” },
{ “key”: { “name”: 1 }, “name”: “name_1” }
],
“ok”: 1
}

# 3. 移除未使用的索引
fgedudb> db.fgedu_users.dropIndex(“name_1”)
{ “nIndexWas”: 1, “ok”: 1 }

# 4. 优化复合索引顺序
# 好的顺序:db.collection.createIndex({ high cardinality field: 1, low cardinality field: 1 })
# 差的顺序:db.collection.createIndex({ low cardinality field: 1, high cardinality field: 1 })

3.3.2 分页优化

# 1. 避免大skip值
# 不推荐
fgedudb> db.fgedu_users.find().skip(10000).limit(10)
# 推荐
fgedudb> db.fgedu_users.find({ _id: { $gt: lastId } }).limit(10)

# 2. 使用覆盖索引
fgedudb> db.fgedu_users.createIndex({ age: 1, name: 1, email: 1 })
fgedudb> db.fgedu_users.find({ age: { $gt: 25 } }, { name: 1, email: 1, _id: 0 })

# 3. 预加载数据
# 使用cursor.batchSize()设置批量大小
fgedudb> var cursor = db.fgedu_users.find().batchSize(100)

# 4. 监控分页性能
fgedudb> db.fgedu_users.find().skip(100).limit(10).explain(“executionStats”)

风哥提示:MongoDB性能调优是一个持续的过程,需要根据业务需求和数据变化不断调整和改进,以确保系统的高效运行。更多学习教程公众号风哥教程itpux_com

Part04-生产案例与实战讲解

4.1 MongoDB分页案例

4.1.1 需求分析

需要实现一个用户列表的分页功能,每页显示10条记录,支持按注册时间排序和按年龄范围过滤。

4.1.2 解决方案

# 1. 创建索引
fgedudb> db.fgedu_users.createIndex({ created_at: -1 })
fgedudb> db.fgedu_users.createIndex({ age: 1, created_at: -1 })

# 2. 基于skip/limit的分页
fgedudb> function paginateUsers(page, pageSize, ageFilter) {
var skip = (page – 1) * pageSize;
var query = {};
if (ageFilter) {
query.age = ageFilter;
}
return db.fgedu_users.find(query).sort({ created_at: -1 }).skip(skip).limit(pageSize).toArray();
}

# 3. 调用分页函数
fgedudb> paginateUsers(1, 10, { $gt: 25 })
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h9”), name: “fgedu02”, age: 28, created_at: ISODate(“2026-04-09T08:00:00Z”) },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h1”), name: “fgedu04”, age: 30, created_at: ISODate(“2026-04-10T08:00:00Z”) }
]

# 4. 基于ID的分页
fgedudb> function paginateUsersById(lastId, pageSize, ageFilter) {
var query = {};
if (lastId) {
query._id = { $gt: lastId };
}
if (ageFilter) {
query.age = ageFilter;
}
return db.fgedu_users.find(query).sort({ _id: 1 }).limit(pageSize).toArray();
}

# 5. 调用基于ID的分页函数
fgedudb> var lastId = ObjectId(“6614f8a0a1b2c3d4e5f6g7h8”);
fgedudb> paginateUsersById(lastId, 10, { $gt: 25 })
[
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h9”), name: “fgedu02”, age: 28, created_at: ISODate(“2026-04-09T08:00:00Z”) },
{ _id: ObjectId(“6614f8a0a1b2c3d4e5f6g7h1”), name: “fgedu04”, age: 30, created_at: ISODate(“2026-04-10T08:00:00Z”) }
]

4.2 MongoDB索引优化案例

4.2.1 需求分析

需要优化一个电商系统的商品查询性能,主要查询场景包括:按商品名称搜索、按分类和价格范围查询、按销量排序。

4.2.2 解决方案

# 1. 分析查询模式
# 常用查询:
# – 按商品名称搜索:db.fgedu_products.find({ $text: { $search: “keyword” } })
# – 按分类和价格范围查询:db.fgedu_products.find({ category: “electronics”, price: { $gte: 100, $lte: 1000 } })
# – 按销量排序:db.fgedu_products.find().sort({ sales: -1 })

# 2. 创建索引
fgedudb> db.fgedu_products.createIndex({ name: “text”, description: “text” })
fgedudb> db.fgedu_products.createIndex({ category: 1, price: 1 })
fgedudb> db.fgedu_products.createIndex({ sales: -1 })

# 3. 测试查询性能
# 测试文本搜索
fgedudb> db.fgedu_products.find({ $text: { $search: “laptop” } }).explain(“executionStats”)

# 测试分类和价格范围查询
fgedudb> db.fgedu_products.find({ category: “electronics”, price: { $gte: 100, $lte: 1000 } }).explain(“executionStats”)

# 测试按销量排序
fgedudb> db.fgedu_products.find().sort({ sales: -1 }).limit(10).explain(“executionStats”)

# 4. 优化索引
# 为分类、价格和销量创建复合索引
fgedudb> db.fgedu_products.createIndex({ category: 1, price: 1, sales: -1 })

# 测试复合索引效果
fgedudb> db.fgedu_products.find({ category: “electronics”, price: { $gte: 100, $lte: 1000 } }).sort({ sales: -1 }).explain(“executionStats”)

4.3 MongoDB性能调优案例

4.3.1 需求分析

需要优化一个社交媒体应用的数据库性能,主要问题是查询速度慢,特别是在大数据量的情况下。

4.3.2 解决方案

# 1. 分析慢查询
fgedudb> db.setProfilingLevel(1, { slowms: 100 })

# 查看慢查询日志
fgedudb> db.system.profile.find({ millis: { $gt: 100 } }).sort({ ts: -1 })

# 2. 优化索引
# 为常用查询创建索引
fgedudb> db.fgedu_posts.createIndex({ user_id: 1, created_at: -1 })
fgedudb> db.fgedu_comments.createIndex({ post_id: 1, created_at: -1 })
fgedudb> db.fgedu_followers.createIndex({ user_id: 1, followed_id: 1 })

# 3. 优化分页
# 使用基于ID的分页
fgedudb> function getPosts(user_id, lastId, pageSize) {
var query = { user_id: user_id };
if (lastId) {
query._id = { $gt: lastId };
}
return db.fgedu_posts.find(query).sort({ _id: -1 }).limit(pageSize).toArray();
}

# 4. 优化查询
# 使用投影减少返回数据
fgedudb> db.fgedu_posts.find({ user_id: user_id }, { content: 1, created_at: 1, _id: 1 })

# 使用覆盖索引
fgedudb> db.fgedu_posts.createIndex({ user_id: 1, created_at: -1, content: 1 })

# 5. 监控性能
# 查看索引使用情况
fgedudb> db.fgedu_posts.aggregate([{ $indexStats: {} }])

# 查看查询执行计划
fgedudb> db.fgedu_posts.find({ user_id: user_id }).sort({ created_at: -1 }).explain(“executionStats”)

生产环境建议:MongoDB性能调优应结合具体业务场景和数据模型,选择合适的优化策略,确保系统性能满足业务需求。from MongoDB视频:www.itpux.com

Part05-风哥经验总结与分享

5.1 MongoDB分页技巧

MongoDB分页技巧:

  • 选择合适的分页策略:小数据量使用skip/limit,大数据量使用基于ID或时间戳的分页
  • 优化排序字段:为排序字段创建索引,提高排序性能
  • 使用覆盖索引:减少IO操作,提高查询速度
  • 避免大skip值:大skip值会导致性能下降,应使用基于ID的分页
  • 设置合理的页面大小:根据业务需求和数据量设置合适的页面大小
  • 监控分页性能:定期分析分页查询的执行时间,调整优化策略
  • 使用游标:对于大量数据的分页,使用游标可以提高性能
  • 实现客户端缓存:缓存热门页面,减少数据库查询

5.2 MongoDB索引技巧

MongoDB索引技巧:

  • 分析查询模式:根据常用查询创建合适的索引
  • 合理设计复合索引:考虑字段顺序,高基数字段在前
  • 使用文本索引:对于全文搜索场景,创建文本索引
  • 使用地理空间索引:对于地理查询场景,创建地理空间索引
  • 定期审查索引:移除未使用的索引,减少存储开销
  • 监控索引使用情况:使用$indexStats查看索引使用情况
  • 优化索引存储:定期重建索引,减少碎片
  • 考虑索引的选择性:选择选择性高的字段创建索引

5.3 MongoDB优化技巧

MongoDB优化技巧:

  • 硬件优化:增加内存,使用SSD存储,优化网络连接
  • 配置优化:调整WiredTiger缓存大小,优化写入策略
  • 查询优化:优化查询语句,使用合适的操作符
  • 索引优化:创建合适的索引,避免全集合扫描
  • 分页优化:使用高效的分页策略,避免大skip值
  • 连接池优化:使用连接池,减少连接开销
  • 监控与调优:定期监控系统性能,调整优化策略
  • 数据模型优化:合理设计数据模型,减少查询复杂度
风哥提示:MongoDB优化是一个系统工程,需要从硬件、配置、查询、索引等多个方面入手,综合考虑各种因素,才能达到最佳性能。更多视频教程www.fgedu.net.cn

持续改进:MongoDB性能优化是一个持续的过程,需要根据业务需求和数据变化不断调整和改进,以确保系统的高效运行。

本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html

联系我们

在线咨询:点击这里给我发消息

微信号:itpux-com

工作日:9:30-18:30,节假日休息