sql >> Database >  >> NoSQL >> MongoDB

insertMany behandelt dubbele fouten

Welnu, in feite zal MongoDB "standaard" geen dubbele gegevens creëren waar een "unieke sleutel" bij betrokken is, waarvan _id ( alias door mangoest als id , maar genegeerd door insertMany() dus je moet voorzichtig zijn), maar er is een veel groter verhaal waar je echt van op de hoogte moet zijn .

Het basisprobleem hier is dat zowel de "mongoose"-implementatie van insertMany() evenals de onderliggende driver zijn momenteel een beetje "borked" om het zacht uit te drukken. Dat er een beetje inconsistentie is in de manier waarop de bestuurder de foutreactie doorgeeft bij "Bulk" -bewerkingen, wordt nog verergerd door "mangoest" die niet echt "op de juiste plaats zoekt" naar de daadwerkelijke foutinformatie.

Het "snelle" deel dat je mist is de toevoeging van { ordered: false } naar de "Bulk" bewerking waarvan .insertMany() belt eenvoudigweg naar. Als u dit instelt, zorgt u ervoor dat de "batch" van verzoeken daadwerkelijk "volledig" wordt ingediend en stopt de uitvoering niet wanneer er een fout optreedt.

Maar aangezien "mangoest" dit niet zo goed aankan (en de bestuurder ook niet "consequent"), moeten we eigenlijk zoeken naar mogelijke "fouten" in de "reactie" in plaats van naar het "fout"-resultaat van de onderliggende callback.

Als demonstratie:

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(); } );

Of misschien een beetje leuker omdat de huidige LTS node.js async/await heeft :

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();
  }


})()

Je krijgt in ieder geval hetzelfde resultaat dat aantoont dat het schrijven beide wordt voortgezet en dat we respectvol fouten "negeren" die verband houden met een "duplicaatsleutel" of anders bekend als foutcode 11000 . De "veilige behandeling" is dat we dergelijke fouten verwachten en ze weggooien terwijl we zoeken naar de aanwezigheid van "andere fouten" waar we misschien aandacht aan willen besteden. We zien ook dat de rest van de code doorgaat en een lijst geeft van alle documenten die daadwerkelijk zijn ingevoegd door een volgende .find() uit te voeren. bel:

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"
  }
]

Dus waarom dit proces? De reden hiervoor is dat de onderliggende aanroep feitelijk zowel de err . retourneert en result zoals weergegeven in de callback-implementatie, maar er is een inconsistentie in wat wordt geretourneerd. De belangrijkste reden om dit te doen, is dat u daadwerkelijk het "resultaat" ziet, dat niet alleen het resultaat van de succesvolle bewerking heeft, maar ook de foutmelding.

Samen met de foutinformatie is de nInserted: 3 wat aangeeft hoeveel van de "batch" daadwerkelijk zijn geschreven. Je kunt de insertedIds vrijwel negeren hier omdat deze specifieke test het daadwerkelijk leveren van _id . betrof waarden. In het geval dat een andere eigenschap de "unieke" beperking had die de fout veroorzaakte, dan zijn de enige waarden hier die van daadwerkelijke succesvolle schrijfacties. Een beetje misleidend, maar gemakkelijk te testen en zelf te zien.

Zoals gezegd, is de vangst de "incosistentie" die kan worden aangetoond met een ander voorbeeld ( async/await alleen voor de beknoptheid van de lijst):

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();
  }


})()

Allemaal hetzelfde, maar let op hoe de fouten hier worden geregistreerd:

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"
  }
}

Merk op dat er geen "succes"-informatie is, ook al krijgen we dezelfde voortzetting van de lijst door de volgende .find() te doen en het krijgen van de output. Dit komt omdat de implementatie alleen handelt op de "gegooide fout" bij afwijzing en nooit door het daadwerkelijke result gaat. deel. Dus ook al hebben we gevraagd om ordered: false , krijgen we geen informatie over wat er is voltooid, tenzij we de callback afronden en de logica zelf implementeren, zoals wordt getoond in de eerste lijsten.

De andere belangrijke "inconsistentie" treedt op wanneer er "meer dan één fout" is. Dus het commentaar verwijderen van de toegevoegde waarde voor _id: 4 geeft ons:

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"
    }
  }
]

Hier kunt u de code "vertakt" zien op de aanwezigheid van e.writeErrors , die niet bestaat als er één . is fout. In tegenstelling tot het eerdere response object heeft zowel de hasWriteErrors() en getWriteErrors() methoden, ongeacht of er een fout aanwezig is. Dus dat is de meer consistente interface en de reden waarom u deze zou moeten gebruiken in plaats van de err te inspecteren alleen reactie.

MongoDB 3.x-stuurprogrammareparaties

Dit gedrag is verholpen in de komende 3.x-release van het stuurprogramma, die moet samenvallen met de MongoDB 3.6-serverrelease. Het gedrag verandert doordat de err reactie lijkt meer op het standaard result , maar natuurlijk geclassificeerd als een BulkWriteError reactie in plaats van MongoError wat het nu is.

Totdat dat wordt vrijgegeven (en natuurlijk totdat die afhankelijkheid en wijzigingen zijn doorgevoerd in de "mongoose" -implementatie), is de aanbevolen handelwijze om je ervan bewust te zijn dat de nuttige informatie in het result staat en niet de err . In feite zou uw code waarschijnlijk moeten zoeken naar hasErrors() in het result en dan terugvallen om err . te controleren ook om tegemoet te komen aan de wijziging die in de driver moet worden doorgevoerd.

Opmerking auteurs: Veel van deze inhoud en gerelateerde informatie wordt hier al beantwoord op Functie insertMany() ongeordend:juiste manier om zowel de fouten als het resultaat te krijgen? en MongoDB Node.js native driver slikt stilletjes bulkWrite uitzondering. Maar hier herhalen en uitwerken totdat het eindelijk doordringt tot mensen dat dit de manier is waarop je met uitzonderingen omgaat in de huidige driverimplementatie. En het werkt echt, als je op de juiste plaats kijkt en je code schrijft om het dienovereenkomstig af te handelen.




  1. ga verder in cursor.forEach()

  2. MongoDB Regex, Index &Prestaties

  3. Is het veilig om het journaalbestand van mongodb te verwijderen?

  4. Meteor, definieer collecties dynamisch