sql >> Database teknologi >  >> NoSQL >> MongoDB

indsætMange Håndter Duplikatfejl

Faktisk vil MongoDB som "standard" ikke skabe duplikerede data, hvor der er en "unik nøgle" involveret, hvoraf _id ( kaldet af mongoose som id , men ignoreret af insertMany() så du skal være forsigtig ), men der er en meget større historie i dette, som du virkelig skal være opmærksom på .

Det grundlæggende problem her er, at både "mongoose"-implementeringen af ​​insertMany() såvel som den underliggende driver er i øjeblikket lidt "borket" for at sige det mildt. At der er lidt inkonsistens i, hvordan chaufføren videregiver fejlreaktionen i "bulk"-operationer, og dette forstærkes faktisk af, at "mongoose" ikke rigtig "søger det rigtige sted" efter den faktiske fejlinformation.

Den "hurtige" del, du mangler, er tilføjelsen af ​​{ ordered: false } til "Bulk"-operationen, hvoraf .insertMany() afslutter blot et opkald til. Indstilling af dette sikrer, at "batchen" af anmodninger faktisk sendes "helt" og stopper ikke udførelsen, når der opstår en fejl.

Men da "mongoose" ikke håndterer dette særlig godt (og driveren heller ikke "konsekvent"), er vi faktisk nødt til at lede efter mulige "fejl" i "svaret" snarere end "fejl"-resultatet af det underliggende tilbagekald.

Som en demonstration:

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

Eller måske en smule pænere, da nuværende LTS node.js har 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();
  }


})()

I hvert fald får du det samme resultat, der viser, at skrivninger både fortsættes, og at vi respektfuldt "ignorerer" fejl, der er relateret til en "duplicate key" eller på anden måde kendt som fejlkode 11000 . Den "sikre håndtering" er, at vi forventer sådanne fejl og kasserer dem, mens vi leder efter tilstedeværelsen af ​​"andre fejl", som vi måske bare vil være opmærksomme på. Vi ser også, at resten af ​​koden fortsætter og viser alle dokumenter, der faktisk er indsat ved at udføre en efterfølgende .find() ring:

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

Så hvorfor denne proces? Årsagen er, at det underliggende kald faktisk returnerer både err og result som vist i callback-implementeringen, men der er en inkonsistens i, hvad der returneres. Hovedårsagen til at gøre dette er, så du rent faktisk ser "resultatet", som ikke kun har resultatet af den vellykkede operation, men også fejlmeddelelsen.

Sammen med fejlinformationen er nInserted: 3 angiver, hvor mange ud af "batchen" der rent faktisk blev skrevet. Du kan stort set ignorere insertedIds her, da denne særlige test faktisk involverede levering af _id værdier. I det tilfælde, hvor en anden egenskab havde den "unikke" begrænsning, der forårsagede fejlen, ville de eneste værdier her være dem fra faktiske vellykkede skrivninger. Lidt misvisende, men let at teste og se selv.

Som nævnt er fangsten "inkosistens", som kan demonstreres med et andet eksempel ( async/await kun for kortheds skyld):

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


})()

Alt sammen stort set det samme, men vær opmærksom på, hvordan fejlen logges her:

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

Bemærk, at der ikke er nogen "succes"-oplysninger, selvom vi får den samme fortsættelse af fortegnelsen ved at udføre den efterfølgende .find() og få output. Dette skyldes, at implementeringen kun handler på den "smidte fejl" i afvisningen og aldrig passerer gennem det faktiske result en del. Så selvom vi bad om ordered: false , får vi ikke oplysningerne om, hvad der blev gennemført, medmindre vi afslutter tilbagekaldet og implementerer logikken selv, som det er vist i de indledende lister.

Den anden vigtige "inkonsistens" sker, når der er "mere end én fejl". Så fjern den yderligere værdi for _id: 4 giver os:

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

Her kan du se koden "forgrenet" på tilstedeværelsen af ​​e.writeErrors , som ikke eksisterer, når der er en fejl. I modsætning hertil det tidligere response objektet har både hasWriteErrors() og getWriteErrors() metoder, uanset om der overhovedet er nogen fejl. Så det er den mere konsistente grænseflade og grunden til, at du skal bruge den i stedet for at inspicere err svar alene.

MongoDB 3.x-driverrettelser

Denne adfærd er faktisk rettet i den kommende 3.x-udgivelse af driveren, som er beregnet til at falde sammen med MongoDB 3.6-serverudgivelsen. Adfærden ændres ved, at err svar er mere beslægtet med standard result , men selvfølgelig klassificeret som en BulkWriteError svar i stedet for MongoError som det er i øjeblikket.

Indtil det er frigivet (og selvfølgelig indtil denne afhængighed og ændringer er udbredt til "mongoose"-implementeringen), så er den anbefalede fremgangsmåde at være opmærksom på, at den nyttige information er i result og ikke err . Faktisk burde din kode sandsynligvis lede efter hasErrors() i result og derefter gå tilbage for at kontrollere err også for at tage højde for ændringen, der skal implementeres i driveren.

Forfattere Bemærk: Meget af dette indhold og relateret læsning er faktisk allerede besvaret her på Function insertMany() unordered:korrekt måde at få både fejlene og resultatet på? og MongoDB Node.js native driver sluger lydløst bulkWrite undtagelse. Men gentager og uddyber her, indtil det endelig synker ind i folk, at det er den måde, man håndterer undtagelser på i den nuværende driverimplementering. Og det virker faktisk, når du kigger på det rigtige sted og skriver din kode for at håndtere den i overensstemmelse hermed.




  1. Importer data til dine nyoprettede MongoDB-instanser

  2. Redis hente alle værdier af listen uden iteration og uden popping

  3. Hvordan kan jeg gennemse eller forespørge om live MongoDB-data?

  4. Hvordan opdaterer jeg et Mongo-dokument efter at have indsat det?