MongoDB
 sql >> база данни >  >> NoSQL >> MongoDB

insertMany Обработка на дублиращи се грешки

Всъщност MongoDB по "подразбиране" няма да създава дублиращи се данни, където има "уникален ключ", от който _id ( псевдоним от mongoose като id , но се игнорира от insertMany() така че трябва да внимавате ), но това има много по-голяма история, с която наистина трябва да сте наясно .

Основният проблем тук е, че както "мангустата" реализация на insertMany() както и основният драйвер в момента са меко казано малко "разбити". Тъй като има малко несъответствие в начина, по който драйверът предава отговора за грешка в „групови“ операции и това всъщност се усложнява от това, че „мангустът“ всъщност не „търси на правилното място“ за действителната информация за грешката.

"Бързата" част, която ви липсва, е добавянето на { ordered: false } към операцията "Bulk", от която .insertMany() просто обвива обаждане до. Задаването на това гарантира, че „партидата“ от заявки действително се изпраща „напълно“ и не спира изпълнението, когато възникне грешка.

Но тъй като „mongoose“ не се справя много добре с това (нито драйверът „последователно“), ние всъщност трябва да търсим възможни „грешки“ в „отговора“, а не резултата „грешка“ от основното обратно извикване.

Като демонстрация:

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const songSchema = new Schema({
  _id: Number,
  name: String
});

const Song = mongoose.model('Song', songSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

let docs = [
  { _id: 1, name: "something" },
  { _id: 2, name: "something else" },
  { _id: 2, name: "something else entirely" },
  { _id: 3, name: "another thing" }
];

mongoose.connect(uri,options)
  .then( () => Song.remove() )
  .then( () =>
    new Promise((resolve,reject) =>
      Song.collection.insertMany(docs,{ ordered: false },function(err,result) {
        if (result.hasWriteErrors()) {
          // Log something just for the sake of it
          console.log('Has Write Errors:');
          log(result.getWriteErrors());

          // Check to see if something else other than a duplicate key, and throw
          if (result.getWriteErrors().some( error => error.code != 11000 ))
            reject(err);
        }
        resolve(result);    // Otherwise resolve
      })
    )
  )
  .then( results => { log(results); return true; } )
  .then( () => Song.find() )
  .then( songs => { log(songs); mongoose.disconnect() })
  .catch( err => { console.error(err); mongoose.disconnect(); } );

Или може би малко по-хубав, тъй като текущият LTS node.js има async/await :

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const songSchema = new Schema({
  _id: Number,
  name: String
});

const Song = mongoose.model('Song', songSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

let docs = [
  { _id: 1, name: "something" },
  { _id: 2, name: "something else" },
  { _id: 2, name: "something else entirely" },
  { _id: 3, name: "another thing" }
];

(async function() {

  try {
    const conn = await mongoose.connect(uri,options);

    await Song.remove();

    let results = await new Promise((resolve,reject) => {
      Song.collection.insertMany(docs,{ ordered: false },function(err,result) {
        if (result.hasWriteErrors()) {
          // Log something just for the sake of it
          console.log('Has Write Errors:');
          log(result.getWriteErrors());

          // Check to see if something else other than a duplicate key, then throw
          if (result.getWriteErrors().some( error => error.code != 11000 ))
            reject(err);
        }
        resolve(result);    // Otherwise resolve

      });
    });

    log(results);

    let songs = await Song.find();
    log(songs);

  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }


})()

Във всеки случай получавате същия резултат, показващ, че записите продължават и че ние с уважение „игнорираме“ грешки, които са свързани с „дублиран ключ“ или по друг начин известни като код на грешка 11000 . „Безопасното боравене“ е, че очакваме такива грешки и ги изхвърляме, докато търсим наличието на „други грешки“, на които може просто да искаме да обърнем внимание. Също така виждаме, че останалата част от кода продължава и изброява всички документи, действително вмъкнати чрез изпълнение на последващ .find() обадете се:

Mongoose: songs.remove({}, {})
Mongoose: songs.insertMany([ { _id: 1, name: 'something' }, { _id: 2, name: 'something else' }, { _id: 2, name: 'something else entirely' }, { _id: 3, name: 'another thing' } ], { ordered: false })
Has Write Errors:
[
  {
    "code": 11000,
    "index": 2,
    "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
    "op": {
      "_id": 2,
      "name": "something else entirely"
    }
  }
]
{
  "ok": 1,
  "writeErrors": [
    {
      "code": 11000,
      "index": 2,
      "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
      "op": {
        "_id": 2,
        "name": "something else entirely"
      }
    }
  ],
  "writeConcernErrors": [],
  "insertedIds": [
    {
      "index": 0,
      "_id": 1
    },
    {
      "index": 1,
      "_id": 2
    },
    {
      "index": 2,
      "_id": 2
    },
    {
      "index": 3,
      "_id": 3
    }
  ],
  "nInserted": 3,
  "nUpserted": 0,
  "nMatched": 0,
  "nModified": 0,
  "nRemoved": 0,
  "upserted": [],
  "lastOp": {
    "ts": "6485492726828630028",
    "t": 23
  }
}
Mongoose: songs.find({}, { fields: {} })
[
  {
    "_id": 1,
    "name": "something"
  },
  {
    "_id": 2,
    "name": "something else"
  },
  {
    "_id": 3,
    "name": "another thing"
  }
]

И така, защо този процес? Причината е, че основното извикване всъщност връща и двете err и result както е показано в реализацията на обратното извикване, но има несъответствие в това, което се връща. Основната причина да направите това е, че действително виждате „резултата“, който съдържа не само резултата от успешната операция, но и съобщението за грешка.

Заедно с информацията за грешката е nInserted: 3 показва колко от "партидата" всъщност са били написани. Можете до голяма степен да игнорирате insertedIds тук, тъй като този конкретен тест включваше действително предоставяне на _id стойности. В случай, че различно свойство има "уникалното" ограничение, което е причинило грешката, тогава единствените стойности тук ще бъдат тези от действителни успешни записи. Малко подвеждащо, но лесно за тестване и убедете се сами.

Както беше посочено, уловката е "несъответствието", което може да се демонстрира с друг пример ( async/await само за краткост на списъка):

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const songSchema = new Schema({
  _id: Number,
  name: String
});

const Song = mongoose.model('Song', songSchema);

function log(data) {
  console.log(JSON.stringify(data, undefined, 2))
}

let docs = [
  { _id: 1, name: "something" },
  { _id: 2, name: "something else" },
  { _id: 2, name: "something else entirely" },
  { _id: 3, name: "another thing" },
  { _id: 4, name: "different thing" },
  //{ _id: 4, name: "different thing again" }
];

(async function() {

  try {
    const conn = await mongoose.connect(uri,options);

    await Song.remove();

    try {
      let results = await Song.insertMany(docs,{ ordered: false });
      console.log('what? no result!');
      log(results);   // not going to get here
    } catch(e) {
      // Log something for the sake of it
      console.log('Has write Errors:');

      // Check to see if something else other than a duplicate key, then throw
      // Branching because MongoError is not consistent
      if (e.hasOwnProperty('writeErrors')) {
        log(e.writeErrors);
        if(e.writeErrors.some( error => error.code !== 11000 ))
          throw e;
      } else if (e.code !== 11000) {
        throw e;
      } else {
        log(e);
      }

    }

    let songs = await Song.find();
    log(songs);

  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }


})()

Всичко е същото, но обърнете внимание на това как грешката се регистрира тук:

Has write Errors:
{
  "code": 11000,
  "index": 2,
  "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
  "op": {
    "__v": 0,
    "_id": 2,
    "name": "something else entirely"
  }
}

Имайте предвид, че няма информация за "успех", въпреки че получаваме същото продължение на списъка, като правим последващият .find() и получаване на изхода. Това е така, защото реализацията действа само на "изхвърлената грешка" при отхвърляне и никога не преминава през действителния result част. Така че, въпреки че поискахме ordered: false , ние не получаваме информация за това, което е завършено, освен ако не увием обратното извикване и сами не приложим логиката, както е показано в първоначалните списъци.

Другата важна "непоследователност" се случва, когато има "повече от една грешка". Така декоментиране на допълнителната стойност за _id: 4 ни дава:

Has write Errors:
[
  {
    "code": 11000,
    "index": 2,
    "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 2 }",
    "op": {
      "__v": 0,
      "_id": 2,
      "name": "something else entirely"
    }
  },
  {
    "code": 11000,
    "index": 5,
    "errmsg": "E11000 duplicate key error collection: test.songs index: _id_ dup key: { : 4 }",
    "op": {
      "__v": 0,
      "_id": 4,
      "name": "different thing again"
    }
  }
]

Тук можете да видите "разклонения" код за наличието на e.writeErrors , което не съществува, когато имаединен грешка. За разлика от по-ранния response обектът има и двата hasWriteErrors() и getWriteErrors() методи, независимо от наличието на някаква грешка. Така че това е по-последователният интерфейс и причината, поради която трябва да го използвате, вместо да проверявате err отговор само.

Поправки на драйвери на MongoDB 3.x

Това поведение всъщност е фиксирано в предстоящата версия 3.x на драйвера, която трябва да съвпада с версията на сървъра MongoDB 3.6. Поведението се променя в това err отговорът е по-подобен на стандартния result , но разбира се класифициран като BulkWriteError отговор вместо MongoError което е в момента.

Докато това не бъде пуснато (и разбира се, докато тази зависимост и промени не бъдат разпространени в реализацията на "mongoose"), тогава препоръчителният начин на действие е да сте наясно, че полезната информация е в result ине err . Всъщност вашият код вероятно трябва да търси hasErrors() в result и след това обратно, за да проверите err също така, за да се погрижи промяната да бъде въведена в драйвера.

Забележка на авторите: Голяма част от това съдържание и свързаното четене всъщност вече е отговорено тук във Функция insertMany() unorreded:правилен начин да получите както грешките, така и резултата? и роден драйвер на MongoDB Node.js безшумно поглъща bulkWrite изключение. Но повтаряйки и усъвършенствайки тук, докато най-накрая не стане ясно на хората, че това е начинът, по който се справяте с изключенията в текущата реализация на драйвера. И наистина работи, когато погледнете на правилното място и напишете кода си, за да се справите съответно с него.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB изтеглящ елемент от масив от колекция

  2. MongoDB, премахнете обекта от масива

  3. Изтриване на каскаден стил в Mongoose

  4. Автоматично генерирано поле за MongoDB с помощта на Spring Boot

  5. Най-добри практики за .NET за MongoDB връзки?