Node.js 中 Mongoose 更新数据库的最佳实践?
Best practice for Mongoose updates to database in Node.js?
假设我有以下架构:
const mySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
date: Number,
data: {
field1 : Number,
field2 : Number
}
});
并且我想为具有 "myAwesomeDate" 的文档更新 field2 为 "myAwesomeValue"。我当前的代码在 async/await 函数中是:
// V1
var myAwesomeDocument = await myModel.findOneAndUpdate(
{date: myAwesomeDate}, //selector
{$set: { //update
'data.field2': myAwesomeValue
}},
{ //options
upsert: false,
new: true
}
).exec();
此代码允许我使用更新后的文档。
如果我没记错的话,下面的代码具有相同的行为,但应该避免,因为它首先将文档加载到客户端(因此效率较低)(Mongoose difference between .save() and using update()):
// V2
var myAwesomeDocument = await myModel.findOne({date: myAwesomeDate}).exec();
myAwesomeDocument.data.field2 = myAwesomeValue;
myAwesomeDocument = await myAwesomeDocument.save().exec();
现在,我想使用 .doSomething() 时尚让我的代码更具可读性:
// V3 (any code mistake here ?)
var myAwesomeDocument = await myModel.findOne({date: myAwesomeDate})
.set(
{'data.field2': myAwesomeValue},
{upsert: false, new: true}
)
.exec();
我的问题首先是效率,其次是代码可读性:
- 有没有比V1更高效的代码?
- V3 是否执行相同的更新?它和 V1 一样高效吗?
- 有没有更高效、更易读的V3写法?
感谢任何类型的回答!
从您提供的示例来看,最有效的是 v1,
- V1 在后台它只触发一个查询,mongo's
findAndModify.
- V2 需要 2 个查询才能完成预期的更新。然后找到一个
updateOne.
- V3 没有做预期的事情,它只是做了 findOne,没有
对找到的文档发出任何更新操作。
V3 的正确版本如下:
instance = await model
.findOneAndUpdate({date})
.set({'data.f2': f1},)
.setOptions({new: true})
.exec();
说明:
Mongoose findOneAndUpdate returns a Query (检查示例)。然后我们使用查询的方法来设置更新操作和选项。
总而言之,您可以选择 V1 或我提供的更新后的 V3,因为它们在后台使用相同的数据库调用。
您始终可以使用 mongoose.set('debug': true) 来分析哪些查询实际发送到数据库。
为了支持我上面所说的话,这里是我用来 运行 测试的代码片段。您可以像这样调用它:
const uri = 'mongodb://localhost:27017/test-sav';
const mongoose = require('mongoose');
const bombardCount = process.argv[2] ? parseInt(process.argv[2]) : 1;
const debug = process.argv[3] === 'true';
const date = 1234567;
const f1 = 1;
const f2 = 2;
let model;
(async function () {
await mongoose.connect(uri, {useNewUrlParser: true, useUnifiedTopology: true});
const schema = new mongoose.Schema({
date: Number,
data: {
f1: Number,
f2: Number,
}
});
model = mongoose.model('model', schema);
console.log('### START ###')
const doc1 = await bombard(v1, bombardCount);
console.log(doc1);
const doc2 = await bombard(v2, bombardCount);
console.log(doc2);
const doc3 = await bombard(v3, bombardCount);
console.log(doc3);
const doc4 = await bombard(v4, bombardCount);
console.log(doc4);
console.log('### END ###');
})().catch(error => console.error(error)).then(() => process.exit(1));
async function v1() {
console.log('### V1 ###\n');
await beforeEach();
console.time('### V1 ###');
let instance = await model.findOneAndUpdate(
{date},
{
$set: {
'data.f2': f1,
},
},
{
upsert: false,
new: true
}
).exec();
console.timeEnd('### V1 ###');
await afterEach();
return instance;
}
async function v2() {
console.log('### V2 ###\n');
await beforeEach();
console.time('### V2 ###');
let instance = await model.findOne({date}).exec();
instance.data.f2 = f1;
instance = await instance.save();
console.timeEnd('### V2 ###');
await afterEach();
return instance;
}
async function v3() {
console.log('### V3 ###\n');
console.time('### V3 ###');
await beforeEach();
let instance = await model
.findOne({date})
.set(
{'data.f2': f1},
{upsert: false, new: true}
).exec();
console.timeEnd('### V3 ###');
await afterEach();
return instance
}
async function v4() {
console.log('### V4 ###\n');
console.time('### V4 ###');
await beforeEach();
let instance = await model
.findOneAndUpdate({date})
.set({'data.f2': f1})
.setOptions({new: true})
.exec();
console.timeEnd('### V4 ###');
await afterEach();
return instance;
}
async function beforeEach() {
await new model({
date,
data: {
f1,
f2,
},
}).save();
mongoose.set('debug', debug);
}
async function afterEach() {
mongoose.set('debug', debug);
await model.deleteMany({});
}
async function bombard(f, times) {
let x;
for (let i = 0; i < times; i++) {
x = await f();
}
return x;
}
node index.js [repeats?=number] [debug?=true/false]
假设我有以下架构:
const mySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
date: Number,
data: {
field1 : Number,
field2 : Number
}
});
并且我想为具有 "myAwesomeDate" 的文档更新 field2 为 "myAwesomeValue"。我当前的代码在 async/await 函数中是:
// V1
var myAwesomeDocument = await myModel.findOneAndUpdate(
{date: myAwesomeDate}, //selector
{$set: { //update
'data.field2': myAwesomeValue
}},
{ //options
upsert: false,
new: true
}
).exec();
此代码允许我使用更新后的文档。
如果我没记错的话,下面的代码具有相同的行为,但应该避免,因为它首先将文档加载到客户端(因此效率较低)(Mongoose difference between .save() and using update()):
// V2
var myAwesomeDocument = await myModel.findOne({date: myAwesomeDate}).exec();
myAwesomeDocument.data.field2 = myAwesomeValue;
myAwesomeDocument = await myAwesomeDocument.save().exec();
现在,我想使用 .doSomething() 时尚让我的代码更具可读性:
// V3 (any code mistake here ?)
var myAwesomeDocument = await myModel.findOne({date: myAwesomeDate})
.set(
{'data.field2': myAwesomeValue},
{upsert: false, new: true}
)
.exec();
我的问题首先是效率,其次是代码可读性:
- 有没有比V1更高效的代码?
- V3 是否执行相同的更新?它和 V1 一样高效吗?
- 有没有更高效、更易读的V3写法?
感谢任何类型的回答!
从您提供的示例来看,最有效的是 v1,
- V1 在后台它只触发一个查询,mongo's findAndModify.
- V2 需要 2 个查询才能完成预期的更新。然后找到一个 updateOne.
- V3 没有做预期的事情,它只是做了 findOne,没有 对找到的文档发出任何更新操作。
V3 的正确版本如下:
instance = await model
.findOneAndUpdate({date})
.set({'data.f2': f1},)
.setOptions({new: true})
.exec();
说明: Mongoose findOneAndUpdate returns a Query (检查示例)。然后我们使用查询的方法来设置更新操作和选项。
总而言之,您可以选择 V1 或我提供的更新后的 V3,因为它们在后台使用相同的数据库调用。
您始终可以使用 mongoose.set('debug': true) 来分析哪些查询实际发送到数据库。
为了支持我上面所说的话,这里是我用来 运行 测试的代码片段。您可以像这样调用它:
const uri = 'mongodb://localhost:27017/test-sav';
const mongoose = require('mongoose');
const bombardCount = process.argv[2] ? parseInt(process.argv[2]) : 1;
const debug = process.argv[3] === 'true';
const date = 1234567;
const f1 = 1;
const f2 = 2;
let model;
(async function () {
await mongoose.connect(uri, {useNewUrlParser: true, useUnifiedTopology: true});
const schema = new mongoose.Schema({
date: Number,
data: {
f1: Number,
f2: Number,
}
});
model = mongoose.model('model', schema);
console.log('### START ###')
const doc1 = await bombard(v1, bombardCount);
console.log(doc1);
const doc2 = await bombard(v2, bombardCount);
console.log(doc2);
const doc3 = await bombard(v3, bombardCount);
console.log(doc3);
const doc4 = await bombard(v4, bombardCount);
console.log(doc4);
console.log('### END ###');
})().catch(error => console.error(error)).then(() => process.exit(1));
async function v1() {
console.log('### V1 ###\n');
await beforeEach();
console.time('### V1 ###');
let instance = await model.findOneAndUpdate(
{date},
{
$set: {
'data.f2': f1,
},
},
{
upsert: false,
new: true
}
).exec();
console.timeEnd('### V1 ###');
await afterEach();
return instance;
}
async function v2() {
console.log('### V2 ###\n');
await beforeEach();
console.time('### V2 ###');
let instance = await model.findOne({date}).exec();
instance.data.f2 = f1;
instance = await instance.save();
console.timeEnd('### V2 ###');
await afterEach();
return instance;
}
async function v3() {
console.log('### V3 ###\n');
console.time('### V3 ###');
await beforeEach();
let instance = await model
.findOne({date})
.set(
{'data.f2': f1},
{upsert: false, new: true}
).exec();
console.timeEnd('### V3 ###');
await afterEach();
return instance
}
async function v4() {
console.log('### V4 ###\n');
console.time('### V4 ###');
await beforeEach();
let instance = await model
.findOneAndUpdate({date})
.set({'data.f2': f1})
.setOptions({new: true})
.exec();
console.timeEnd('### V4 ###');
await afterEach();
return instance;
}
async function beforeEach() {
await new model({
date,
data: {
f1,
f2,
},
}).save();
mongoose.set('debug', debug);
}
async function afterEach() {
mongoose.set('debug', debug);
await model.deleteMany({});
}
async function bombard(f, times) {
let x;
for (let i = 0; i < times; i++) {
x = await f();
}
return x;
}
node index.js [repeats?=number] [debug?=true/false]