MongoDB
 sql >> Cơ Sở Dữ Liệu >  >> NoSQL >> MongoDB

insertMany Xử lý lỗi trùng lặp

Trên thực tế, MongoDB theo "mặc định" sẽ không tạo dữ liệu trùng lặp khi có liên quan đến "khóa duy nhất", trong đó _id (được mongoose đặt bí danh là id , nhưng bị insertMany() bỏ qua vì vậy bạn cần phải cẩn thận), nhưng có một câu chuyện lớn hơn nhiều về vấn đề này mà bạn thực sự cần lưu ý .

Vấn đề cơ bản ở đây là cả việc triển khai "mongoose" của insertMany() cũng như trình điều khiển cơ bản hiện đang được một chút "borked" để đặt nó một cách nhẹ nhàng. Đó là có một chút mâu thuẫn trong cách trình điều khiển vượt qua phản hồi lỗi trong các hoạt động "Hàng loạt" và điều này thực sự được kết hợp bởi "mongoose" không thực sự "tìm kiếm đúng chỗ" cho thông tin lỗi thực tế.

Phần "nhanh" mà bạn đang thiếu là thêm { ordered: false } vào hoạt động "Bulk" trong đó .insertMany() chỉ cần kết thúc một cuộc gọi đến. Cài đặt này đảm bảo rằng "lô" yêu cầu thực sự được gửi "hoàn toàn" và không ngừng thực thi khi xảy ra lỗi.

Nhưng vì "mongoose" không xử lý điều này rất tốt (cũng như trình điều khiển không "nhất quán") nên chúng tôi thực sự cần tìm kiếm "lỗi" có thể xảy ra trong "phản hồi" thay vì "lỗi" của lệnh gọi lại bên dưới.

Như một minh chứng:

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

Hoặc có lẽ đẹp hơn một chút vì LTS node.js hiện tại có 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();
  }


})()

Ở bất kỳ mức độ nào, bạn sẽ nhận được cùng một kết quả cho thấy rằng quá trình ghi đều được tiếp tục và chúng tôi trân trọng "bỏ qua" các lỗi liên quan đến "khóa trùng lặp" hay còn được gọi là mã lỗi 11000 . "Xử lý an toàn" là chúng tôi mong đợi các lỗi như vậy và loại bỏ chúng trong khi tìm kiếm sự hiện diện của "các lỗi khác" mà chúng tôi có thể chỉ muốn chú ý đến. Chúng tôi cũng thấy phần còn lại của mã tiếp tục và liệt kê tất cả các tài liệu thực sự được chèn bằng cách thực thi .find() tiếp theo gọi:

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

Vì vậy, tại sao quá trình này? Lý do là lệnh gọi cơ bản thực sự trả về cả errresult như được hiển thị trong triển khai gọi lại nhưng có sự không nhất quán trong những gì được trả về. Lý do chính để làm điều này là để bạn thực sự nhìn thấy "kết quả", không chỉ có kết quả của hoạt động thành công mà còn có thông báo lỗi.

Cùng với thông tin lỗi là nInserted: 3 cho biết có bao nhiêu trong số "lô" thực sự đã được viết. Bạn có thể bỏ qua insertedIds ở đây vì thử nghiệm cụ thể này liên quan đến việc thực sự cung cấp _id các giá trị. Trong trường hợp một thuộc tính khác có ràng buộc "duy nhất" gây ra lỗi, thì các giá trị duy nhất ở đây sẽ là các giá trị từ các lần ghi thành công thực tế. Một chút sai lệch, nhưng dễ dàng để kiểm tra và xem cho chính mình.

Như đã nêu, bắt là "tính không nhất quán" có thể được chứng minh bằng một ví dụ khác (async/await chỉ cho sự ngắn gọn của danh sách):

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


})()

Tất cả đều giống nhau, nhưng hãy chú ý đến cách ghi lỗi ở đây:

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

Lưu ý rằng không có thông tin "thành công", mặc dù chúng tôi nhận được sự tiếp nối tương tự của danh sách bằng cách thực hiện .find() tiếp theo và nhận được đầu ra. Điều này là do việc triển khai chỉ hoạt động trên "lỗi được đưa ra" trong từ chối và không bao giờ chuyển qua result thực tế phần. Vì vậy, mặc dù chúng tôi đã yêu cầu ordered: false , chúng tôi không nhận được thông tin về những gì đã được hoàn thành trừ khi chúng tôi kết thúc lệnh gọi lại và tự triển khai logic, như được hiển thị trong danh sách ban đầu.

"Sự không nhất quán" quan trọng khác xảy ra khi có "nhiều hơn một lỗi". Vì vậy, bỏ ghi chú giá trị bổ sung cho _id: 4 cung cấp cho chúng tôi:

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

Tại đây, bạn có thể thấy mã "rẽ nhánh" khi có e.writeErrors , không tồn tại khi có một lỗi. Ngược lại, response trước đó đối tượng có cả hasWriteErrors()getWriteErrors() phương pháp, bất kể lỗi nào đang hiện hữu. Vì vậy, đó là giao diện nhất quán hơn và lý do tại sao bạn nên sử dụng nó thay vì kiểm tra err phản hồi một mình.

Bản sửa lỗi trình điều khiển MongoDB 3.x

Hành vi này thực sự đã được khắc phục trong bản phát hành trình điều khiển 3.x sắp tới, có nghĩa là trùng với bản phát hành máy chủ MongoDB 3.6. Hành vi thay đổi trong đó err phản hồi gần giống với result tiêu chuẩn , nhưng tất nhiên được phân loại là BulkWriteError phản hồi thay vì MongoError mà nó hiện đang là.

Cho đến khi điều đó được phát hành (và tất nhiên cho đến khi sự phụ thuộc và những thay đổi đó được truyền sang triển khai "mongoose"), thì cách hành động được đề xuất là lưu ý rằng thông tin hữu ích nằm trong result không err . Trên thực tế, mã của bạn có lẽ nên tìm kiếm hasErrors() trong result và sau đó dự phòng để kiểm tra err cũng như để phục vụ cho việc thay đổi được triển khai trong trình điều khiển.

Ghi chú của tác giả: Phần lớn nội dung này và bài đọc liên quan thực sự đã được trả lời ở đây trên Hàm insertMany () không có thứ tự:cách thích hợp để nhận cả lỗi và kết quả? và trình điều khiển gốc MongoDB Node.js âm thầm nuốt bulkWrite ngoại lệ. Nhưng lặp lại và giải thích ở đây cho đến khi mọi người hiểu rằng đây là cách bạn xử lý các trường hợp ngoại lệ trong việc triển khai trình điều khiển hiện tại. Và nó thực sự hoạt động, khi bạn tìm đúng chỗ và viết mã của mình để xử lý nó cho phù hợp.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Mongodb:không kết nối được với máy chủ trong lần kết nối đầu tiên

  2. Kết nối từ xa với giao diện MongoDB http trên máy chủ EC2

  3. Tại sao mongoose sử dụng lược đồ khi lợi ích của mongodb được cho là nó không có lược đồ?

  4. tiếp tục trong cursor.forEach ()

  5. Mongodb tìm kết quả được tạo theo ngày hôm nay