MongoDB 查询偶发返回空数据问题
MongoDB 查询偶发返回空数据问题
问题描述
/api/orderUser 接口在正常查询时,偶发性返回空数据列表(list: []),但接口返回 200 OK 状态码,响应格式正常。
现象
-
同样的查询条件,有时有数据,有时没数据
-
接口不报错,返回格式正常
-
数据库中确实存在匹配的数据
示例请求
GET /api/orderUser?pageSize=20&isOrder=&page=1&timeOut=1&sword=惠州市路普电子商务有限公&goods=66ba2fc26b857249c8c87c03&swordType=1
正常响应
{
"list": [...],
"count": 100,
"hasCheck": true
}
异常响应
{
"list": [],
"count": 0,
"hasCheck": true
}
根因分析
1. MongoDB 驱动行为
MongoDB Node.js 驱动在以下情况下,toArray() 可能返回空数组而非抛出错误:
| 场景 | 行为 |
|-----|------|
| 连接超时 | 返回 [] |
| 查询超时 | 返回 [] |
| 连接池耗尽 | 返回 [] 或异常 |
| 网络抖动 | 返回 [] |
关键代码位置:
// src/app/api/orderUser/route.ts:251
list = await collection("order").find(match).sort({ "add_time": -1 }).skip(skipNum).limit(pageSize).toArray()
2. 为什么 Next.js 不报错?
// 查询返回空数组,代码继续执行
count = await collection("order").countDocuments(match);
// 构造正常响应
return succ({
list: [], // 空数组
count: 0,
hasCheck,
...count_data
})
MongoDB 驱动没有抛出异常,Next.js 认为查询成功,正常返回 200。
3. 触发条件
-
数据库并发查询过多
-
MongoDB 服务器负载高
-
网络不稳定
-
连接池配置不足
解决方案
方案 1:添加查询超时限制(推荐)
在查询时设置最大执行时间,超时时抛出错误而非返回空数组:
// src/app/api/orderUser/route.ts
// 修改前
list = await collection("order").find(match)
.sort({ "add_time": -1 })
.skip(skipNum)
.limit(pageSize)
.toArray()
// 修改后
list = await collection("order").find(match)
.sort({ "add_time": -1 })
.skip(skipNum)
.limit(pageSize)
.maxTimeMS(10000) // 10秒超时
.toArray()
方案 2:添加重试机制
当检测到异常空数据时,自动重试查询:
// src/app/api/orderUser/route.ts
const findWithRetry = async (match: any, skipNum: number, pageSize: number, retryCount = 3) => {
for (let i = 0; i < retryCount; i++) {
const list = await collection("order")
.find(match)
.sort({ "add_time": -1 })
.skip(skipNum)
.limit(pageSize)
.maxTimeMS(10000)
.toArray();
// 如果查到数据,或者是最后一次重试,直接返回
if (list.length > 0 || i === retryCount - 1) {
return list;
}
// 等待后重试(指数退避)
await new Promise(r => setTimeout(r, 100 * Math.pow(2, i)));
}
return [];
};
// 使用
list = await findWithRetry(match, skipNum, pageSize);
方案 3:添加异常检测日志
在查询后检测异常情况,便于排查:
// src/app/api/orderUser/route.ts
const startTime = Date.now();
list = await collection("order").find(match)
.sort({ "add_time": -1 })
.skip(skipNum)
.limit(pageSize)
.maxTimeMS(10000)
.toArray();
const queryTime = Date.now() - startTime;
// 检测异常:count > 0 但 list 为空
if (list.length === 0 && count > 0) {
console.error("[MongoDB异常] 查询返回空数据:", {
queryTime,
match: JSON.stringify(match),
page,
pageSize,
skipNum,
count,
timestamp: new Date().toISOString()
});
}
方案 4:优化 MongoDB 连接配置
在数据库连接层增加超时和重连配置:
// src/models/db.ts(假设的数据库连接文件)
const client = new MongoClient(uri, {
maxPoolSize: 50, // 连接池大小
minPoolSize: 10,
maxIdleTimeMS: 30000, // 连接最大空闲时间
waitQueueTimeoutMS: 5000, // 等待连接超时
serverSelectionTimeoutMS: 10000, // 服务器选择超时
socketTimeoutMS: 30000, // Socket 超时
});
完整修改示例
修改文件:src/app/api/orderUser/route.ts
// 在 GET 函数中,替换原有的查询逻辑
export const GET = async (request: NextRequest) => {
try {
// ... 前面的代码 ...
const skipNum = (page - 1) * pageSize;
// 添加重试的查询函数
const findWithRetry = async (retryCount = 3) => {
for (let i = 0; i < retryCount; i++) {
const result = await collection("order")
.find(match)
.sort({ "add_time": -1 })
.skip(skipNum)
.limit(pageSize)
.maxTimeMS(10000) // 10秒超时
.toArray();
if (result.length > 0 || i === retryCount - 1) {
return result;
}
// 指数退避重试
await new Promise(r => setTimeout(r, 100 * Math.pow(2, i)));
}
return [];
};
const startTime = Date.now();
let list: O_DB[] = [];
let count = 0;
if (!isEmailMarketing && !isWechatMarketing && !isPhoneMarketing && !isSmsMarketing && !isRenewal) {
// 普通查询,使用重试机制
list = await findWithRetry();
count = await collection("order").countDocuments(match);
// 异常检测
if (list.length === 0 && count > 0) {
console.error("[MongoDB异常] 查询返回空数据:", {
queryTime: Date.now() - startTime,
match: JSON.stringify(match),
page,
pageSize,
skipNum,
count
});
}
}
// ... 后续代码 ...
} catch (err) {
return error((err as any).message)
}
}
验证方法
1. 查看日志
部署修改后,查看 Next.js 日志:
# 查看错误日志
tail -f logs/error.log | grep "MongoDB异常"
2. 手动测试
快速连续请求接口 10 次,观察是否还有空数据:
for i in {1..10}; do
curl -s "http://localhost:3090/api/orderUser?pageSize=20&page=1&sword=test" | jq '.list | length'
done
3. 监控指标
-
查询响应时间
-
MongoDB 连接池使用率
-
错误日志频率
相关代码位置
| 文件 | 行号 | 说明 |
|-----|------|------|
| src/app/api/orderUser/route.ts | 251 | 主查询逻辑 |
| src/app/api/orderUser/getUrlMatch.ts | 96 | 查询条件生成 |
| src/models/db.ts | - | 数据库连接配置 |
参考
本文由萧兮的博客原创发布,欢迎转载,转载务必保留原文链接。
萧兮的博客:https://www.20010515.xyz · 原文:https://www.20010515.xyz/posts/019e4ece-9076-7da3-a6d9-4a87197783d0