MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

insertBanyak Menangani Kesalahan Duplikat

Sebenarnya, MongoDB secara "default" tidak akan membuat data duplikat di mana ada "kunci unik" yang terlibat, di antaranya _id ( alias oleh luwak sebagai id , tetapi diabaikan oleh insertMany() jadi Anda harus berhati-hati ), tetapi ada cerita yang jauh lebih besar tentang hal ini yang benar-benar perlu Anda ketahui .

Masalah mendasar di sini adalah implementasi "luwak" dari insertMany() serta driver yang mendasarinya saat ini agak "borked" secara halus. Bahwa ada sedikit ketidakkonsistenan dalam cara pengemudi melewati respons kesalahan dalam operasi "Massal" dan ini sebenarnya diperparah oleh "luwak" yang tidak benar-benar "mencari di tempat yang tepat" untuk informasi kesalahan yang sebenarnya.

Bagian "cepat" yang Anda lewatkan adalah penambahan { ordered: false } ke operasi "Bulk" yang .insertMany() hanya membungkus panggilan ke. Menyetel ini memastikan bahwa "kumpulan" permintaan benar-benar dikirimkan "sepenuhnya" dan tidak menghentikan eksekusi saat terjadi kesalahan.

Tetapi karena "luwak" tidak menangani ini dengan baik ( begitu pula driver "secara konsisten" ), kita sebenarnya perlu mencari kemungkinan "kesalahan" dalam "respons" daripada "kesalahan" hasil dari panggilan balik yang mendasarinya.

Sebagai demonstrasi:

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

Atau mungkin sedikit lebih baik karena LTS node.js saat ini memiliki 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();
  }


})()

Bagaimanapun, Anda mendapatkan hasil yang sama yang menunjukkan bahwa penulisan dilanjutkan dan bahwa kami dengan hormat "mengabaikan" kesalahan yang terkait dengan "kunci duplikat" atau dikenal sebagai kode kesalahan 11000 . "Penanganan aman" adalah bahwa kami mengharapkan kesalahan tersebut dan membuangnya sambil mencari keberadaan "kesalahan lain" yang mungkin hanya ingin kami perhatikan. Kami juga melihat sisa kode berlanjut dan mencantumkan semua dokumen yang benar-benar dimasukkan dengan mengeksekusi .find() berikutnya hubungi:

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

Jadi mengapa proses ini? Alasannya adalah bahwa panggilan yang mendasarinya benar-benar mengembalikan err dan result seperti yang ditunjukkan dalam implementasi panggilan balik tetapi ada inkonsistensi dalam apa yang dikembalikan. Alasan utama untuk melakukan ini adalah agar Anda benar-benar melihat "hasil", yang tidak hanya memiliki hasil operasi yang berhasil, tetapi juga pesan kesalahan.

Bersamaan dengan informasi kesalahan adalah nInserted: 3 menunjukkan berapa banyak dari "batch" yang benar-benar ditulis. Anda dapat mengabaikan insertedIds di sini karena tes khusus ini benar-benar melibatkan penyediaan _id nilai-nilai. Jika properti yang berbeda memiliki batasan "unik" yang menyebabkan kesalahan, maka satu-satunya nilai di sini adalah nilai dari penulisan yang benar-benar berhasil. Agak menyesatkan, tetapi mudah untuk diuji dan dilihat sendiri.

Seperti yang dinyatakan, tangkapannya adalah "inkosistensi" yang dapat ditunjukkan dengan contoh lain ( async/await hanya untuk singkatnya daftar):

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


})()

Semuanya hampir sama, tetapi perhatikan bagaimana kesalahan dicatat di sini:

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

Perhatikan bahwa tidak ada informasi "berhasil", meskipun kita mendapatkan kelanjutan daftar yang sama dengan melakukan .find() berikutnya dan mendapatkan outputnya. Ini karena implementasinya hanya bekerja pada "kesalahan yang dilemparkan" dalam penolakan dan tidak pernah melewati result yang sebenarnya bagian. Jadi meskipun kami meminta ordered: false , kami tidak mendapatkan informasi tentang apa yang telah selesai kecuali kami membungkus panggilan balik dan menerapkan logika itu sendiri, seperti yang ditunjukkan dalam daftar awal.

"Inkonsistensi" penting lainnya terjadi ketika ada "lebih dari satu kesalahan". Jadi batalkan komentar nilai tambahan untuk _id: 4 memberi kita:

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

Di sini Anda dapat melihat kode "bercabang" dengan adanya e.writeErrors , yang tidak ada jika ada satu kesalahan. Sebaliknya response sebelumnya objek memiliki keduanya hasWriteErrors() dan getWriteErrors() metode, terlepas dari kesalahan yang ada sama sekali. Jadi itulah antarmuka yang lebih konsisten dan alasan mengapa Anda harus menggunakannya daripada memeriksa err tanggapan saja.

Perbaikan Driver MongoDB 3.x

Perilaku ini sebenarnya diperbaiki dalam rilis driver 3.x mendatang yang dimaksudkan untuk bertepatan dengan rilis server MongoDB 3.6. Perilaku berubah di mana err respons lebih mirip dengan result standar , tapi tentu saja digolongkan sebagai BulkWriteError respons alih-alih MongoError yang saat ini.

Sampai itu dirilis (dan tentu saja sampai ketergantungan dan perubahan itu disebarkan ke implementasi "luwak"), maka tindakan yang disarankan adalah menyadari bahwa informasi yang berguna ada di result dan tidak err . Sebenarnya kode Anda mungkin harus mencari hasErrors() di result dan kemudian mundur untuk memeriksa err juga, untuk memenuhi perubahan yang akan diterapkan pada driver.

Catatan Penulis: Sebagian besar konten ini dan bacaan terkait sebenarnya sudah dijawab di sini di Function insertMany() unordered:cara yang tepat untuk mendapatkan kesalahan dan hasilnya? dan driver asli MongoDB Node.js diam-diam menelan bulkWrite pengecualian. Tetapi mengulangi dan menguraikan di sini sampai akhirnya meresap ke orang-orang bahwa ini adalah cara Anda menangani pengecualian dalam implementasi driver saat ini. Dan itu benar-benar berfungsi, ketika Anda melihat di tempat yang benar dan menulis kode Anda untuk menanganinya dengan tepat.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Ikhtisar Cadangan Percona untuk MongoDB

  2. Meteor dan DBRefs

  3. Cara Membuat Indeks dengan Nama Tertentu di MongoDB

  4. Cara Memantau Server Database Anda Menggunakan ClusterControl CLI

  5. Bagaimana cara menentukan Order atau Sort menggunakan driver C# untuk MongoDB?