我正在尝试更新一个功能以提高性能。 对于同一调用,旧函数需要约 32 秒,新函数需要约 350 毫秒,但现在它们没有返回相同的结果,我不确定为什么。
它使用以下猫鼬模式:
const sentenceFeatureSchema = new mongoose.Schema({
text_id: { type: String, required: true },
md5_hash: { type: String, required: true },
offset: { type: Number, required: true },
length: { type: Number, required: true },
num_words: { type: Number, required: true },
});
给定一个哈希数组,查询 mongodb 中与给定数组中任何 md5 哈希匹配的所有文档。如果提供了 exceptedTextId,则不包含具有相同 textId 的文档。哈希值和 textid 不是排他性的,这意味着任何文档都可以具有 textid 和哈希值的任意组合(甚至可以有多个文档具有相同的 id 和哈希值,但其他值会有所不同)。
注意:应该只查询需要的数据,因为系统中有很多文档,所以必须避免尝试查询超出我们需要的数据,然后只使用我们需要的数据。
我的新实现没有返回与原始结果相同的结果。我相信新的实施是正确的,而旧的实施则不是,但我可能是错的。不管怎样,我想确切地了解他们返回不同结果的内容和原因。
const getMatchingHashSentences = async (md5Hashes, excludedTextId = null) => {
try {
const entries = [];
for (const md5Hash of md5Hashes) {
const queryConditions = { md5_hash: md5Hash };
if (excludedTextId) {
queryConditions.text_id = { $ne: excludedTextId };
}
const result = await SentenceFeature.find(queryConditions);
entries.push(...result);
}
logger.debug(`entries length: ${entries.length}`);
return entries;
} catch (error) {
throw error;
}
};
我尝试将其变成单个数据库请求。
const getMatchingHashSentences = async (md5Hashes, excludedTextId = null) => {
try {
const query = SentenceFeature.find({ md5_hash: { $in: md5Hashes } });
if (excludedTextId) {
query.where("text_id").ne(excludedTextId);
}
const results = await query.exec();
return results;
} catch (error) {
throw error;
}
};
按照要求,这里是演示代码,它将创建 10 个元素并使用这两个函数来查询它们:
演示目录:
mkdir demo && cd demo
准系统 npm 项目:
npm init -y && npm i mongoose mongodb
创建 demo.js:
const mongoose = require("mongoose");
// Define schema
const sentenceFeatureSchema = new mongoose.Schema({
text_id: { type: String, required: true },
md5_hash: { type: String, required: true },
offset: { type: Number, required: true },
length: { type: Number, required: true },
num_words: { type: Number, required: true },
});
// Define model
const SentenceFeature = mongoose.model("features", sentenceFeatureSchema);
const getMatchingHashSentences = async (md5Hashes, excludedTextId = null) => {
try {
const entries = [];
for (const md5Hash of md5Hashes) {
const queryConditions = { md5_hash: md5Hash };
if (excludedTextId) {
queryConditions.text_id = { $ne: excludedTextId };
}
const result = await SentenceFeature.find(queryConditions);
entries.push(...result);
}
return entries;
} catch (error) {
throw error;
}
};
const getMatchingHashSentences2 = async (md5Hashes, excludedTextId = null) => {
try {
const query = SentenceFeature.find({ md5_hash: { $in: md5Hashes } });
if (excludedTextId) {
query.where("text_id").ne(excludedTextId);
}
const results = await query.exec();
return results;
} catch (error) {
throw error;
}
};
// Connect to MongoDB
mongoose
.connect("mongodb://localhost:27017/mydatabase", { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => {
console.log("Connected to MongoDB");
const feature = new Feature({
text_id: "1",
md5_hash: "h1",
offset: 0,
length: 0,
num_words: 0,
});
feature
.save()
.then(() => {
console.log("Entry saved successfully");
})
.catch((err) => {
console.error("Error saving entry:", err);
});
})
.catch((err) => {
console.error("Error connecting to MongoDB:", err);
});
async function main() {
try {
// Connect to MongoDB
await mongoose.connect("mongodb://localhost:27017/mydatabase", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log("Connected to MongoDB");
// Save 10 features
for (let i = 1; i <= 10; i++) {
const feature = new SentenceFeature({
text_id: `${i}`,
md5_hash: `h${i}`,
offset: 0,
length: 0,
num_words: 0,
});
await feature.save();
console.log(`Saved Feature ${i}`);
}
// Query for saved features
const oldres = await getMatchingHashSentences(["h1", "h3", "h6"], "3");
console.log("old:", oldres);
const newres = await getMatchingHashSentences2(["h1", "h3", "h6"], "3");
console.log("new:", newres);
} catch (error) {
console.error("Error:", error);
} finally {
// Close connection
await mongoose.disconnect();
console.log("Disconnected from MongoDB");
}
}
main();
// OUTPUT:
// Saved Feature 1
// Saved Feature 2
// Saved Feature 3
// Saved Feature 4
// Saved Feature 5
// Saved Feature 6
// Saved Feature 7
// Saved Feature 8
// Saved Feature 9
// Saved Feature 10
// old: [
// {
// _id: new ObjectId('6605aa98790f41e69f3f966b'),
// text_id: '1',
// md5_hash: 'h1',
// offset: 0,
// length: 0,
// num_words: 0,
// __v: 0
// },
// {
// _id: new ObjectId('6605aa98790f41e69f3f9676'),
// text_id: '6',
// md5_hash: 'h6',
// offset: 0,
// length: 0,
// num_words: 0,
// __v: 0
// }
// ]
// new: [
// {
// _id: new ObjectId('6605aa98790f41e69f3f966b'),
// text_id: '1',
// md5_hash: 'h1',
// offset: 0,
// length: 0,
// num_words: 0,
// __v: 0
// },
// {
// _id: new ObjectId('6605aa98790f41e69f3f9676'),
// text_id: '6',
// md5_hash: 'h6',
// offset: 0,
// length: 0,
// num_words: 0,
// __v: 0
// }
// ]
// Disconnected from MongoDB
为 mogodb 启动一次性 docker 容器:
docker run --rm -d -p 27017:27017 --name my-mongodb mongo
最后,运行演示:
node demo.js
您会注意到,对于这么小的示例,两个函数的输出是相同的,这是正常的。在对大量数据运行查询时,一定存在一些奇怪的边缘情况导致输出差异。
来自 Ruby on Rails 背景,我最初的想法是
if (excludedTextId) { query.where("text_id").ne(excludedTextId); }
不会改变
query
,而是返回包含新条件的新查询。这种非变异行为通常可以防止错误,因为您可以重复使用 query
而无需更改标准。
在您的场景中,这本质上意味着上面的代码是无操作,因为新返回的查询没有保存在变量中,也没有执行。如果这确实是问题,那么简单的解决方法就是用新值替换当前的
query
变量。
let query = ...
// ^ let instead of const
if (excludedTextId) {
query = query.where("text_id").ne(excludedTextId);
}
所以最终的代码(没有重大改变)应该是这样的:
const getMatchingHashSentences = async (md5Hashes, excludedTextId = null) => {
try {
let query = SentenceFeature.find({ md5_hash: { $in: md5Hashes } });
if (excludedTextId) {
query = query.where("text_id").ne(excludedTextId);
}
const results = await query.exec();
return results;
} catch (error) {
throw error;
}
};
ps。只是为了给大家一些启发,我个人会写成:
function getMatchingHashSentences(md5Hashes, excludedTextId = null) {
let query = SentenceFeature.where("md5_hash").in(md5Hashes);
if (excludedTextId) {
query = query.where("text_id").ne(excludedTextId);
}
return query.exec();
}
如果您要使用
.where("text_id").ne(excludedTextId)
,那么使用 .where("md5_hash").in(md5Hashes)
可能会更干净,否则您将混合 2 种不同的构造。
try/catch
似乎没有必要,因为您唯一要做的就是重新抛出完全相同的异常。如果您删除 try/catch
,错误/异常会自行进一步向上冒泡,这具有完全相同的结果。
你只需返回
.exec()
返回的promise即可。没有理由等待返回值。
const results = await query.exec();
return results;
// is the same as
return await query.exec();
// is the same as*
return query.exec()
// * assuming you don't handle errors (re-throwing is the same as not handling)
或者不使用 Promise(不使用
async/await
):
返回query.exec().then(结果=>结果) // 是相同的 返回query.exec()