ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Eggjs笔记:MongoDB索引、explain及Mongoose索引、内置CURD、扩展Mongoose Model的静态和实例方法

2026/9/28 18:20:09 拓冰建站 浏览量
Eggjs笔记:MongoDB索引、explain及Mongoose索引、内置CURD、扩展Mongoose Model的静态和实例方法 1. Eggjs 项目里 MongoDB 索引到底该怎么设计在 Eggjs 里写数据层绕不开 MongoDB 和 Mongoose。刚开始做项目时我基本是「先跑通再说」Schema 里字段随手一写查询能出结果就行。等到数据量从几百条涨到几十万条接口响应从几十毫秒变成两三秒才发现问题全出在索引上。MongoDB 的索引和传统关系型数据库思路一致都是对一列或多列的值做排序结构让查询更快但落到 Mongoose 的 Schema 里写法、验证方式、以及怎么确认索引真的生效很多人是模糊的。这篇笔记聚焦 Eggjs 项目中的 MongoDB 索引设计与 Mongoose 建模实践。你会看到怎么在 Schema 里配置单字段索引、复合索引、唯一索引怎么用 explain 执行计划确认查询走了索引还是全表扫描Mongoose 内置的 CURD 方法有哪些、各自适用什么场景以及怎么扩展 Model 的静态方法和实例方法把常用查询封装成可复用的骨架。适合已经能在 Eggjs 里连上 MongoDB、但查询性能开始吃紧、想系统梳理索引和建模的开发者。我试过在一个设备管理模块里因为 sn 字段没加索引列表接口在 20 万数据下直接超时加上唯一索引后降到 30ms 以内。索引不是越多越好写多读少的字段加索引反而拖慢插入这个度需要靠 explain 来量化判断。2. 前置准备TaoToken 统一通道与 Eggjs 环境在动手写配置之前先把两件事准备好一个是 Eggjs 项目本身的 MongoDB 连接另一个是后续用来生成和校验索引配置的 AI 工具通道。Eggjs 这边你需要装好 egg、egg-mongoose并在 config/plugin.js 里启用 mongoose 插件config/config.default.js 里配好 mongoose 的 url、db 等参数。这部分是常规操作不展开。重点是 AI 工具通道。写索引配置、分析 explain 输出、生成 Model 扩展骨架时如果每个工具都单独配 Key管理起来很乱。TaoToken 提供统一 Key 和 API 通道把模型对话、编码辅助、API Key 管理收敛到一个入口。你可以先到官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 了解整体能力再进控制台 https://taotoken.net/console 创建 Key。拿到 Key 后在需要调用模型的地方统一走 https://taotoken.net/api 这个 API 地址即可。注意API 地址是 https://taotoken.net/api不要带多余的路径后缀Key 通过请求头传递具体格式以接入文档为准。如果你主要用 Claude Code 这类编码工具做长期开发可以看 Coding Plan https://taotoken.net/coding-plan如果只是想快速验证一段 Schema 或 explain 结果用模型对话 https://taotoken.net/model-chat 更轻量。Key 的创建和管理都在 https://taotoken.net/api-keys接入细节看文档 https://taotoken.net/doc。3. 可复制配置Mongoose Schema 索引与 Model 扩展这一节是核心直接给可复制的代码。先看 Schema 索引配置。3.1 Schema 里配置单字段、复合、唯一索引在 Eggjs 的 app/model 目录下建 user.js索引配置写在字段定义里// app/model/user.js module.exports app { const mongoose app.mongoose; const Schema mongoose.Schema; const UserSchema new Schema({ // 唯一索引userid 不允许重复 userid: { type: Number, unique: true, }, // 普通索引username 查询频繁 username: { type: String, index: true, }, age: { type: Number, }, // 复合索引username 升序 age 降序 // 注意复合索引要写在 schema.index() 里更清晰 status: { type: Number, default: 1, }, }, { timestamps: true, }); // 复合索引username 升序age 降序 UserSchema.index({ username: 1, age: -1 }); // 复合唯一索引userid age 组合唯一 UserSchema.index({ userid: 1, age: 1 }, { unique: true }); // 后台创建索引避免阻塞其他操作 UserSchema.index({ status: 1 }, { background: true }); return mongoose.model(User, UserSchema, user); };几个关键点。数字 1 表示升序-1 表示降序。复合索引 { username: 1, age: -1 } 创建后基于 username 的查询、以及基于 username age 的查询都能命中但只基于 age 的查询用不上这个索引——必须包含复合索引的前 N 个字段。MongoDB 会智能调整查询条件顺序所以 find({ age: 30, username: stephen }) 也能命中不用手动对齐顺序。唯一索引在插入重复值时会报 E11000 duplicate key error。如果插入的文档不包含该字段字段值为 null多次插入 null 同样会触发唯一冲突这点容易踩坑。3.2 扩展静态方法与实例方法静态方法挂在 Model 上不需要实例化就能调用实例方法需要 new 出实例后调用。在 Schema 定义后、model 导出前扩展// 静态方法按 userid 查询 UserSchema.statics.findByUid function(uid, cb) { this.find({ _id: uid }, function(err, docs) { cb(err, docs); }); }; // 静态方法按 username 模糊查询返回 Promise UserSchema.statics.findByName function(name) { return this.find({ username: new RegExp(name, i) }); }; // 实例方法打印当前文档信息 UserSchema.methods.print function() { console.log(当前用户, this.username, 年龄, this.age); }; // 实例方法判断是否成年 UserSchema.methods.isAdult function() { return this.age 18; };在 controller 里调用// app/controller/user.js const Controller require(egg).Controller; class UserController extends Controller { async index() { const { ctx } this; const UserModel ctx.model.User; // 调用静态方法 const users await UserModel.findByName(zhang); ctx.body users; } async detail() { const { ctx } this; const UserModel ctx.model.User; // 实例化后调用实例方法 const user new UserModel({ username: 赵六, age: 29 }); user.print(); ctx.body { adult: user.isAdult() }; } } module.exports UserController;静态方法和实例方法的区别用 ES5 类比最直观Person.run2 是静态方法直接 Person.run2() 调用p.run1 是实例方法必须 new Person() 后 p.run1()。TypeScript 里 static 修饰的就是静态方法普通方法就是实例方法。3.3 Mongoose 内置 CURD 方法速查Mongoose 的 Model 和 Query 提供了完整的内置 CURD常用的整理成表方法作用返回Model.find()查多条数组Model.findOne()查一条文档或 nullModel.findById()按 _id 查文档或 nullModel.create()插入文档Model.updateOne()更新一条更新结果Model.updateMany()更新多条更新结果Model.findOneAndUpdate()查并更新更新前/后文档Model.findByIdAndUpdate()按 _id 查并更新文档Model.deleteOne()删一条删除结果Model.deleteMany()删多条删除结果Model.findOneAndDelete()查并删被删文档Model.findByIdAndDelete()按 _id 查并删被删文档Model.replaceOne()整体替换替换结果完整 API 在 Mongoose 官方文档的 Model 和 Queries 章节。实际项目里findOneAndUpdate 配合 { new: true } 返回更新后的文档比先查再改再存更原子。4. 验证请求用 explain 确认索引真的生效配置写完不代表索引生效必须用 explain 验证。在 mongo shell 里执行// 查看集合当前所有索引 db.user.getIndexes() // 查看查询执行计划 db.user.find({ username: stephen }).explain(executionStats)重点看 executionStats 里的几个数值{ executionStats: { nReturned: 2, executionTimeMillis: 0, totalKeysExamined: 2, totalDocsExamined: 2, executionStages: { stage: IXSCAN } } }stage 是 IXSCAN 表示走了索引扫描COLLSCAN 表示全表扫描。totalKeysExamined 是扫描的索引键数totalDocsExamined 是扫描的文档数。理想情况下这两个值接近 nReturned说明索引过滤效率高。如果 totalDocsExamined 远大于 nReturned说明索引选择性差需要重新设计。对比一下没加索引时的输出{ executionStats: { nReturned: 2, totalKeysExamined: 0, totalDocsExamined: 200000, executionStages: { stage: COLLSCAN } } }totalDocsExamined 是 20 万全表扫描这就是慢查询的根源。加上索引后 totalDocsExamined 降到 2差距一目了然。删除索引用 db.user.dropIndex({ username: 1 })或者按索引名 db.user.dropIndex(username_1)。创建索引时如果集合已有大量数据用 { background: true } 后台创建避免阻塞其他操作但后台创建效率比阻塞方式低。5. 本篇常见错排查5.1 E11000 duplicate key error插入时遇到这个错误说明唯一索引冲突。检查是否重复插入了相同 userid或者多个文档都缺少该字段导致 null 冲突。如果创建唯一索引时集合已有重复数据需要先清理重复项再建索引否则建索引会失败。5.2 复合索引查询不走索引只查 age 不查 username 时{ username: 1, age: -1 } 这个复合索引用不上。复合索引遵循最左前缀原则查询条件必须包含索引的第一个字段。如果 age 单独查询频繁需要单独给 age 建索引。5.3 explain 显示 COLLSCAN 但明明建了索引可能原因有几个索引还没建完后台创建需要时间查询条件字段类型和索引字段类型不一致比如索引是 Number 但查询传了 String或者查询用了 $or、$ne 等操作符导致索引失效。用 db.user.getIndexes() 确认索引存在再检查查询条件类型。5.4 静态方法里 this 指向错误用 function 定义静态方法时this 指向 Model用箭头函数会导致 this 指向外层拿不到 Model。所以静态方法和实例方法都用 function 定义不要用箭头函数。5.5 索引拖慢写入每个索引都会在插入时更新索引越多写入越慢。写多读少的字段不要加索引。用 explain 确认查询确实需要索引再加不要凭感觉堆索引。6. 把索引配置和 Model 扩展接入统一通道上面这些 Schema 索引配置、explain 分析、Model 扩展骨架如果每次手写容易出错可以让 AI 工具帮你生成和校验。统一走 TaoToken 的 Key 和 API 通道不用每个工具单独配。具体做法在控制台 https://taotoken.net/console 创建 Key然后在你的编码工具或对话工具里配置 API 地址 https://taotoken.net/api 和这个 Key。需要生成 Schema 索引配置时把字段和查询场景描述清楚让模型输出 Mongoose 代码拿到 explain 输出后也可以贴给模型帮你判断 stage 和扫描数是否合理。排障和接入问题看 API Keys https://taotoken.net/api-keys 和接入文档 https://taotoken.net/doc快速验证一段 Schema 或 explain 结果用模型对话 https://taotoken.net/model-chat长期在 Eggjs 项目里做编码和 Agent 辅助用 Coding Plan https://taotoken.net/coding-plan索引设计这件事最终还是要靠 explain 的数据说话。AI 能帮你快速生成配置骨架但 totalDocsExamined 和 nReturned 的比值得你自己在真实数据上跑一遍才算数。