Express.js如何高效从MongoStore获取数据 避免每次请求查询数据库
解决方案
先修复现有代码的明显错误
你当前的代码存在两个优先级最高的逻辑问题,先修复保证功能正常:
- 中间件顺序错误:session 中间件必须放在用户查询中间件的前面,否则
req.session还没初始化,根本拿不到存储的userid - 变量引用错误:查询语句里的
userid没有绑定到req.session,应该写成req.session.userid - Schema字段定义错误:
Shopname的字段键写成了大写Type,需改为小写type;IsSeller的默认值配置拼错为defualt,需改为default
修复后的基础中间件顺序示例:
// 先初始化session app.use(session({ secret: 'secret', saveUninitialized: false, resave: false, cookie: { maxAge: 1000 * 60 * 60 * 24 * 365 * 100, secure: false }, store: store })) // 再处理用户查询 app.use(async (req, res, next) => { try { if(req.session.userid) { req.user = await user.findById(req.session.userid) } next() } catch(err) { res.redirect('/' + req.oldUrl + '?err=UserNotFound') } })
效率优化方案
按照实现成本从低到高、收益从高到低排序:
1. 取消全局挂载中间件,按需加载
不需要给所有请求都加用户查询逻辑,只给需要登录态、需要用到用户数据的路由挂载该中间件,比如购物车、个人中心、订单、卖家后台相关路由,静态资源、公开商品页、首页等不需要用户信息的路由完全不用走查询逻辑,能直接减少90%以上的无效数据库查询。
2. 利用Mongoose hydrate方法完全避免重复查询
这个方案可以完美解决你“需要Mongoose Model实例但不想重复查库”的核心痛点:
- 用户登录成功时,直接把查出来的用户文档序列化后存入session:
req.session.user = user.toObject(),同时保留userid字段即可 - 中间件中不需要再查数据库,直接用Mongoose的
hydrate方法把session里的普通对象转成Mongoose文档实例:
app.use(async (req, res, next) => { try { if(req.session.user) { // 直接生成Mongoose实例,无数据库查询 req.user = user.hydrate(req.session.user) } next() } catch(err) { res.redirect('/' + req.oldUrl + '?err=UserNotFound') } })
- 仅需要在用户信息更新(比如修改资料、加减购物车、新增商品)时,同步更新
req.session.user为最新的数据即可,全程不需要额外查询数据库。
3. 增加短时效内存缓存
如果不想把用户全量数据存在session里,可以用LRU缓存做内存级缓存,设置过期时间3~5分钟,缓存key为用户id,value为Mongoose用户实例:
- 同一个用户短时间内的多次请求只会查一次数据库
- 用户信息更新时直接删除对应用户id的缓存,下次请求自动拉取最新数据
- 可以用现成的
lru-cache库实现,缓存上限可根据自身用户量设置,内存占用极低。
内容的提问来源于stack exchange,提问作者Someone
相关产品推荐
相关产品推荐

