函数 insertMany() 无序:获取错误和结果的正确方法?

2024-03-13

看来MongoDBinsertMany()将有序选项设置为 false 的函数可以比将有序选项设置为 true 更有效地插入文档。并且即使多个文档插入失败也可以继续插入文档。

但我发现没有干净的方法来获取每个失败文档的错误和总体命令结果。

(顺便说一句,我使用的是 Node.js 驱动程序 API 2.2。从现在开始我将参考驱动程序的源代码:http://mongodb.github.io/node-mongodb-native/2.2/api/lib_collection.js.html http://mongodb.github.io/node-mongodb-native/2.2/api/lib_collection.js.html)

首先,如果使用 Promise,则无法同时获取错误和结果。在源代码第 540 行,insertMany()返回错误或结果 - 不是两者都返回,而bulkWrite()回调在源代码第 703 行返回两者。

其次,如果使用回调,事情会变得更糟。什么时候bulkWrite()调用带有错误和结果的回调,insertMany()调用带有错误和结果的回调,但结果是 BulkWrite 的结果,而不是正确转换为 InsertManyResults 的结果。请查看源代码第535行。我认为这是一个错误。

甚至对于bulkWrite(),当错误数为 1 时,它不会将结果正确转换为其格式。请看源代码第669行。我认为这也是一个bug。

现在我认为 Node.js 驱动程序根本没有准备好处理这种情况。

目前,似乎没有办法同时正确获取错误和结果。

我对吗?

UPDATE

我运行了一个基于 Neil Lunn 的答案的测试代码。自从我的 Node.js(4.4.5) 不理解 async/await,我不得不用显式 Promises 重写测试代码。

测试代码如下:

function doTest()
{
    var MongoClient = require('mongodb').MongoClient;
    var testData = [ 1,2,2,3,3,4,5,6,7,8,9 ];
    var db;

    return MongoClient.connect('mongodb://127.0.0.1/test')
    .then(function (_db)
    {
        db = _db;
        return db.createCollection('test');
    })
    .then(function ()
    {
        return db.collection('test').deleteMany({})
        .then(function ()
        {
            return db.collection('test').insertMany(
                testData.map(function (_id)
                {
                    return { _id: _id };
                }),
                { ordered: false })
            .then(function (result)
            {
                console.log('Promise: result', result);
            }, function (err)
            {
                console.log('Promise: error', err);
            });
        })
        .then(function ()
        {
            return db.collection('test').deleteMany({});
        })
        .then(function ()
        {
            return new Promise(function (resolve, reject)
            {
                return db.collection('test').insertMany(
                    testData.map(function (_id)
                    {
                        return { _id: _id };
                    }),
                    { ordered: false },
                    function (err, result)
                {
                    console.log('callback: error', err);
                    console.log('callback: result', result);
                    console.log('callback: result.hasWriteErrors', result.hasWriteErrors());
                    console.log('callback: result.getWriteErrors',
                        JSON.stringify(result.getWriteErrors(), null, 2));
                    resolve();
                });
            });
        });
    })
    .catch(function (err)
    {
        console.log('catch', err);
    })
    .then(function ()
    {
        db.close();
    });
}
doTest();

这是结果:

Promise: error { [MongoError: write operation failed]
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors: 
   [ { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
callback: error { [MongoError: write operation failed]
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors: 
   [ { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     { code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
callback: result { ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function],
  insertedCount: 9,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds: 
   { '0': 1,
     '1': 2,
     '2': 2,
     '3': 3,
     '4': 3,
     '5': 4,
     '6': 5,
     '7': 6,
     '8': 7,
     '9': 8,
     '10': 9 },
  n: 9 }
callback: result.hasWriteErrors true
callback: result.getWriteErrors [
  {
    "code": 11000,
    "index": 2,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 2 }",
    "op": {
      "_id": 2
    }
  },
  {
    "code": 11000,
    "index": 4,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  }
]

现在,我再次运行代码,并将 testData 变量修改如下:

var testData = [ 1,2,3,3,4,5,6,7,8,9 ];

在这种情况下,错误数将为 1,而不是 2,因为重复的“2”已被删除。

这是结果:

Promise: error { [MongoError: E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }]
  name: 'MongoError',
  message: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  driver: true,
  code: 11000,
  index: 3,
  errmsg: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  getOperation: [Function],
  toJSON: [Function],
  toString: [Function] }
callback: error { [MongoError: E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }]
  name: 'MongoError',
  message: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  driver: true,
  code: 11000,
  index: 3,
  errmsg: 'E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }',
  getOperation: [Function],
  toJSON: [Function],
  toString: [Function] }
callback: result { ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function] }
callback: result.hasWriteErrors true
callback: result.getWriteErrors [
  {
    "code": 11000,
    "index": 3,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  }
]

错误和结果的格式与第一次运行有很大不同。

  • 该错误没有 writeErrors 字段。
  • 结果没有“转换”字段。 (insertedCount、matchedCount 等)正如我上面所说,这是驱动程序源代码第 669 行上的一个“错误”。

在两次测试运行中,结果参数的类型都不是Collection~insertWriteOpResult。第一个是Collection~bulkWriteOpCallback,第二个是更内部的。因此,本例中的 API 文档是错误的。正如我上面所说,这是由第 535 行和 669 行上的“错误”引起的。

所以,即使结果可以使用(事实上,结果已经hasWriteErrors() and getWriteErrors()正如 Neil Lunn 所说),由于这种行为没有记录在案,我怀疑它可能会在以后的版本中更改,恕不另行通知,并且我的代码将会损坏。


问题实际上只是如何解决“Promise”以及如何传递错误信息,但当然真正的核心问题是is事实上,当任何“批量”操作设置为时,都会返回错误和结果信息{ ordered: false }。 3.x 版本的驱动程序已解决此问题,如上所述NODE-1158 https://jira.mongodb.org/browse/NODE-1158其中还包含指向解决问题的未来分支中的提交的链接。

对此的“解决方法”是要注意“结果”和错误信息都存在于BulkWriteResult对象作为任何此类方法的“回调”调用的结果返回(注意insertMany()乃至bulkWrite()实际上包裹了一个底层批量API实施 https://docs.mongodb.com/manual/reference/method/Bulk/ ).

用列表来演示:

const MongoClient = require('mongodb').MongoClient;

const uri = 'mongodb://localhost/test';
const testData = [1,2,3,3,4,5,6,6,7,8,9];

(async function() {

  let db;

  try {

    db = await MongoClient.connect(uri);

    await db.collection('test').remove();

    // Expect an error here - but it's just the errors
    try {
      let result = await db.collection('test').insertMany(
        testData.map( _id => ({ _id }) ),
        { "ordered": false }
      );
      console.log(result);   // never gets here
    } catch(e) {
      console.dir(e);
      console.log(JSON.stringify(e.writeErrors,undefined,2));
    }

    await db.collection('test').remove();
    // Wrapped callback so we see what happens

    try {
      let result = await new Promise((resolve,reject) => 
        db.collection('test').insertMany(
          testData.map( _id => ({ _id }) ),
          { "ordered": false },
          (err,result) => {
            if (err) reject(result);    // Because the errors are here as well
            resolve(result);
          }
        )
      );
      console.log(result);  // Never gets here
    } catch(e) {
      console.dir(e);
      console.log(e.hasWriteErrors());
      console.log(JSON.stringify(e.getWriteErrors(),undefined,2));
    }

  } catch(e) {
    console.error(e);
  } finally {
    db.close();
  }

})();

所以有两个代码块试图使用insertMany()包含一个值列表,这些值将产生某些值的重复键错误。

在第一次尝试中,我们使用默认值Promise返回值应该由驱动程序的实现代码指示,只是简单地传递err回调结果在它包装的方法中reject()陈述。这意味着我们要去catch在这里阻塞并产生错误信息作为输出:

{ MongoError: [object Object]
    at Function.MongoError.create (/home/neillunn/projects/bulkerror/node_modules/mongodb-core/lib/error.js:31:11)
    at toError (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:139:22)
    at /home/neillunn/projects/bulkerror/node_modules/mongodb/lib/collection.js:701:23
    at handleCallback (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:120:56)
    at /home/neillunn/projects/bulkerror/node_modules/mongodb/lib/bulk/unordered.js:465:9
    at handleCallback (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/utils.js:120:56)
    at resultHandler (/home/neillunn/projects/bulkerror/node_modules/mongodb/lib/bulk/unordered.js:413:5)
    at /home/neillunn/projects/bulkerror/node_modules/mongodb-core/lib/connection/pool.js:469:18
    at _combinedTickCallback (internal/process/next_tick.js:131:7)
    at process._tickCallback (internal/process/next_tick.js:180:9)
  name: 'MongoError',
  message: 'write operation failed',
  driver: true,
  code: 11000,
  writeErrors:
   [ WriteError {
       code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] },
     WriteError {
       code: [Getter],
       index: [Getter],
       errmsg: [Getter],
       getOperation: [Function],
       toJSON: [Function],
       toString: [Function] } ] }
[
  {
    "code": 11000,
    "index": 3,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  },
  {
    "code": 11000,
    "index": 7,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 6 }",
    "op": {
      "_id": 6
    }
  }
]

请注意,这是一个包装的MongoError尽管我们设定了{ ordered: false }响应中没有“结果”信息。详细查看错误信息我们可以看到列表WriteError确实有关于产生的每个重复键错误的详细信息。

因此,批处理中的所有内容均已成功写入,但未引发错误,但未在任何可获得的内容中报告来自Promise。但底层方法却并非如此,它仍然使用回调来实现。

第二次尝试“手动”包装此回调,因此我们实际上可以通过更改行为并传递result反对rejecterr存在。这告诉我们一个不同的故事:

BulkWriteResult {
  ok: [Getter],
  nInserted: [Getter],
  nUpserted: [Getter],
  nMatched: [Getter],
  nModified: [Getter],
  nRemoved: [Getter],
  getInsertedIds: [Function],
  getUpsertedIds: [Function],
  getUpsertedIdAt: [Function],
  getRawResponse: [Function],
  hasWriteErrors: [Function],
  getWriteErrorCount: [Function],
  getWriteErrorAt: [Function],
  getWriteErrors: [Function],
  getLastOp: [Function],
  getWriteConcernError: [Function],
  toJSON: [Function],
  toString: [Function],
  isOk: [Function],
  insertedCount: 9,
  matchedCount: 0,
  modifiedCount: 0,
  deletedCount: 0,
  upsertedCount: 0,
  upsertedIds: {},
  insertedIds:
   { '0': 1,
     '1': 2,
     '2': 3,
     '3': 3,
     '4': 4,
     '5': 5,
     '6': 6,
     '7': 6,
     '8': 7,
     '9': 8,
     '10': 9 },
  n: 9 }
true
[
  {
    "code": 11000,
    "index": 3,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 3 }",
    "op": {
      "_id": 3
    }
  },
  {
    "code": 11000,
    "index": 7,
    "errmsg": "E11000 duplicate key error collection: test.test index: _id_ dup key: { : 6 }",
    "op": {
      "_id": 6
    }
  }
]

由于我们没有传回err我们现在看到BulkWriteResult在 catch 块中。我们知道我们已经到达那里,因为我们在该块中运行特定代码来检查结果。

常规结果确实有一些东西,例如修改或插入的计数,以及列表insertedIds。通过检查我们还可以看出hasWriteErrors()回报true,我们可以得到列表WriteError为了更好地观看,我们将其连载。

3.x 中已修复

如链接问题所述,实际修复仅出现在支持 MongoDB 3.6 的 3.x 驱动程序版本中。 “修复”本质上是在“较低级别”完成的not返回一个BulkWriteResult根本没有,而是让err返回一个BulkWriteError.

这实际上使事情与其他一些驱动程序已经正确实现的方式更加一致。老实说,这有点像传统“节点样式”回调的“宿醉”,传统“节点样式”回调总是返回“两者”错误和响应。

因此,将其纳入“只是一个错误”会使事情更加一致并且按照您通常期望的方式工作。

作为旁注,相关问题在MongoDB Node.js 本机驱动程序默默地吞掉了bulkWrite 异常。 https://stackoverflow.com/q/46700461/2313887JIRA问题中引用的内容显示了实际的bulkWrite()实现中的方法(这不是“直接”什么insertMany()包裹)有一个稍微不同的问题,因为“没有错误”实际上被抛出,因为代码期望result to be null正如所描述的那样,事实并非如此。

所以相反的情况是正确的,我们永远不会到达catch对于默认实现中的异常,使用Promise。然而,应该应用完全相同的处理方法,通过手动包装回调并发送result通过reject优先于err当这两个都返回时null.

按照描述解决这些问题,并最好在新驱动程序可用后立即迁移到新驱动程序。无论如何,这应该很快。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

函数 insertMany() 无序:获取错误和结果的正确方法? 的相关文章

随机推荐