sql >> Databáze >  >> NoSQL >> MongoDB

insertMany Handle Duplicate Errors

Ve skutečnosti MongoDB ve „výchozím nastavení“ nevytvoří duplicitní data tam, kde je zapojen „jedinečný klíč“, z nichž _id ( s aliasem mongoose jako id , ale ignorováno pomocí insertMany() takže musíte být opatrní ), ale je zde mnohem větší příběh, který si opravdu musíte uvědomit .

Základním problémem je zde jak „mongoose“ implementace insertMany() stejně jako podkladový ovladač jsou v současné době mírně řečeno trochu „borked“. Tato skutečnost je trochu nekonzistentní v tom, jak ovladač předává chybovou odezvu při „hromadných“ operacích, a to je ve skutečnosti umocněno tím, že „mongoose“ ve skutečnosti „nehledá na správném místě“ skutečné informace o chybě.

"Rychlá" část, kterou postrádáte, je přidání { ordered: false } k operaci "Hromadné", jejíž .insertMany() jednoduše zabalí hovor. Toto nastavení zajišťuje, že „dávka“ požadavků bude skutečně odeslána „zcela“ a nezastaví provádění, když dojde k chybě.

Ale protože to „mongoose“ nezvládá příliš dobře (ani ovladač „konzistentně“), musíme ve skutečnosti hledat možné „chyby“ v „odpovědi“ spíše než „chybový“ výsledek základního zpětného volání.

Jako ukázka:

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

Nebo možná trochu hezčí, protože aktuální LTS node.js má 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();
  }


})()

V každém případě získáte stejný výsledek, který ukazuje, že oba zápisy pokračují a že s úctou „ignorujeme“ chyby související s „duplicitním klíčem“ nebo jinak známé jako kód chyby 11000 . „Bezpečné zacházení“ spočívá v tom, že takové chyby očekáváme a vyřadíme je, zatímco hledáme přítomnost „jiných chyb“, kterým bychom mohli chtít věnovat pozornost. Vidíme také, že zbytek kódu pokračuje a uvádí všechny dokumenty skutečně vložené provedením následujícího .find() zavolejte:

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

Proč tedy tento proces? Důvodem je to, že základní volání ve skutečnosti vrací obě err a result jak je znázorněno v implementaci zpětného volání, ale v tom, co je vráceno, existuje nekonzistence. Hlavním důvodem je to, abyste skutečně viděli „výsledek“, který obsahuje nejen výsledek úspěšné operace, ale také chybovou zprávu.

Spolu s informací o chybě je nInserted: 3 udávající, kolik z „dávky“ bylo skutečně napsáno. insertedIds můžete v podstatě ignorovat zde, protože tento konkrétní test ve skutečnosti zahrnoval poskytnutí _id hodnoty. V případě, že jiná vlastnost měla „jedinečné“ omezení, které způsobilo chybu, pak zde budou jediné hodnoty ty ze skutečných úspěšných zápisů. Trochu zavádějící, ale snadno si to otestujete a uvidíte sami.

Jak již bylo řečeno, háček je v „nesoudržnosti“, kterou lze demonstrovat na dalším příkladu ( async/await pouze pro stručnost výpisu):

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


})()

Všechno je to stejné, ale věnujte pozornost tomu, jak se chyba zaznamenává zde:

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

Všimněte si, že neexistují žádné informace o "úspěchu", i když stejné pokračování výpisu získáme provedením následujícího .find() a získání výstupu. Je to proto, že implementace působí pouze na "vyhozenou chybu" při odmítnutí a nikdy neprojde skutečným result část. Takže i když jsme požádali o ordered: false , nezískáme informace o tom, co bylo dokončeno, pokud sami nezabalíme zpětné volání a neimplementujeme logiku, jak je ukázáno v úvodních výpisech.

Další důležitá "nekonzistence" nastává, když je "více než jedna chyba". Takže odkomentování další hodnoty pro _id: 4 nám dává:

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

Zde můžete vidět kód "rozvětvený" na přítomnost e.writeErrors , který neexistuje, když existuje jeden chyba. Naproti tomu dřívější response objekt má obě hasWriteErrors() a getWriteErrors() metod, bez ohledu na to, zda se vůbec vyskytuje nějaká chyba. To je tedy konzistentnější rozhraní a důvod, proč byste jej měli používat namísto kontroly err samotná odpověď.

Opravy ovladače MongoDB 3.x

Toto chování je ve skutečnosti opraveno v nadcházející verzi 3.x ovladače, která se má shodovat s vydáním serveru MongoDB 3.6. Chování se změní v tom, že err odpověď je více podobná standardnímu result , ale samozřejmě klasifikován jako BulkWriteError odpověď namísto MongoError kterým v současnosti je.

Dokud to nebude uvolněno (a samozřejmě dokud se tato závislost a změny nepřenesou do implementace "mongoose"), pak doporučeným postupem je uvědomit si, že užitečné informace jsou ve result a ne err . Ve skutečnosti by váš kód pravděpodobně měl hledat hasErrors() ve result a poté nouzově zkontrolujte err také, aby bylo možné zajistit implementaci změny v ovladači.

Poznámka pro autory: Velká část tohoto obsahu a souvisejícího čtení je ve skutečnosti již zodpovězena zde ve funkci insertMany() unordered:správný způsob, jak získat chyby i výsledek? a nativní ovladač MongoDB Node.js tiše spolkne bulkWrite výjimka. Ale opakování a rozvíjení zde, dokud si lidé konečně nevšimli, že toto je způsob, jakým zacházíte s výjimkami v aktuální implementaci ovladače. A skutečně to funguje, když se podíváte na správné místo a napíšete svůj kód, abyste s ním podle toho zacházeli.




  1. Sidekiq nezpracovává frontu

  2. Ovlivňuje délka jména výkon v Redis?

  3. Mongoose findByIdAndUpdate nevrací správný model

  4. Odeberte vnořený dokument vnořený do pole v MongoDB