MongoDB 批量写操作、重试写和mongodb到sql的映射

本章基于v4.2版本。

一、批量写操作

本节介绍以下几点:

  • 概述
  • 有序操作与无序操作
  • bulkWrite()方法
  • 对切分集合进行批量插入的策略

1.概述

MongoDB为客户端提供了批量执行写操作的能力。批量写操作影响单个集合。MongoDB允许应用程序确定批量写操作所需的可接受的确认级别。

新版本 v3.2开始启动。

 db.collection.bulkWrite()方法提供了执行批量插入、更新和删除操作的能力。MongoDB还通过 db.collection.insertMany()支持批量插入。

2.有序操作VS无序操作

批量写操作可以是有序的,也可以是无序的。

使用一个有序的操作列表,MongoDB按顺序执行这些操作。如果在处理其中一个写操作时发生错误,MongoDB将返回,而不处理列表中任何剩余的写操作。见ordered Bulk Write

使用一个无序的操作列表,MongoDB可以并行执行这些操作,但是不能保证这种行为。如果在处理其中一个写操作时发生错误,MongoDB将继续处理列表中剩下的写操作。参见无序批量写入。

在sharded集合上执行一个有序的操作列表通常比执行一个无序列表慢,因为在有序列表中,每个操作必须等待前一个操作完成。

默认情况下,bulkWrite()执行有序的操作。要指定无序的写操作,请在options文档中设置ordered: false。

3.bulkWrite()方法

bulkWrite()支持以下写操作:

每个写操作都作为数组中的文档传递给bulkWrite()。

例如,下面执行多个写操作:

字符集包含以下文件:

{ "_id" : 1, "char" : "Brisbane", "class" : "monk", "lvl" : 4 },
{ "_id" : 2, "char" : "Eldon", "class" : "alchemist", "lvl" : 3 },
{ "_id" : 3, "char" : "Meldane", "class" : "ranger", "lvl" : 3 }

下面的bulkWrite()对集合执行多个操作:

try {
   db.characters.bulkWrite(
      [
         { insertOne :
            {
               "document" :
               {
                  "_id" : 4, "char" : "Dithras", "class" : "barbarian", "lvl" : 4
               }
            }
         },
         { insertOne :
            {
               "document" :
               {
                  "_id" : 5, "char" : "Taeln", "class" : "fighter", "lvl" : 3
               }
            }
         },
         { updateOne :
            {
               "filter" : { "char" : "Eldon" },
               "update" : { $set : { "status" : "Critical Injury" } }
            }
         },
         { deleteOne :
            { "filter" : { "char" : "Brisbane"} }
         },
         { replaceOne :
            {
               "filter" : { "char" : "Meldane" },
               "replacement" : { "char" : "Tanys", "class" : "oracle", "lvl" : 4 }
            }
         }
      ]
   );
}
catch (e) {
   print(e);
}

操作返回以下内容:

{
   "acknowledged" : true,
   "deletedCount" : 1,
   "insertedCount" : 2,
   "matchedCount" : 2,
   "upsertedCount" : 0,
   "insertedIds" : {
      "0" : 4,
      "1" : 5
   },
   "upsertedIds" : {

   }
}

有关更多示例,请参见 bulkWrite() Examples

4.对切分集合进行批量插入的策略

大量批量插入操作(包括初始数据插入或例程数据导入)会影响切分集群的性能。对于批量插入,请考虑以下策略:

4.1 Pre-Split集合

如果切分的集合为空,则该集合只有一个初始块,它驻留在单个切分上。MongoDB必须花时间来接收数据,创建分块,并将分块分配到可用的分片。为了避免这种性能开销,您可以预先分割集合,正如切分集群中分割块中所描述的那样。

4.2 无序写入到mongos

要提高对分片集群的写性能,可以使用bulkWrite(),并将可选参数ordered设置为false。mongos可以尝试同时向多个碎片发送写操作。对于空集合,首先按照切分集群中分割块的描述对集合进行预分割。

4.3 避免单调节流

如果您的分片键在一次插入期间单调增加,那么所有插入的数据都将进入集合中的最后一个块,最后总是在一个分片上结束。因此,集群的插入容量永远不会超过单个碎片的插入容量。

如果您的插入容量大于单个碎片所能处理的容量,并且无法避免单调递增的碎片键,那么请考虑对您的应用程序进行以下修改:

  • 反转碎片密钥的二进制位。这保留了信息,并避免了插入顺序与增加的值序列之间的关联。
  • 交换第一个和最后一个16位单词来“shuffle”插入。

例子
下面的例子,在c++中,交换了生成的BSON对象的前导和后置16位字,因此它们不再是单调递增的。

using namespace mongo;
OID make_an_id() {
  OID x = OID::gen();
  const unsigned char *p = x.getData();
  swap( (unsigned short&) p[0], (unsigned short&) p[10] );
  return x;
}

void foo() {
  // create an object
  BSONObj o = BSON( "_id" << make_an_id() << "x" << 3 << "name" << "jane" );
  // now we may insert o into a sharded collection
}

SEE ALSO

Shard Keys for information on choosing a sharded key. Also see Shard Key Internals (in particular, Choosing a Shard Key).

二、重试写

本节将介绍以下几点:

  • 前提条件
  • 重试写和多文档事务
  • 怎么开启重试写
  • 重试写操作
  • 行为

新版本3.6。
Retryable写允许MongoDB驱动程序在遇到网络错误时,或者在复制集或分片集群中找不到一个健康的主节点时,可以一次自动重试某些写操作。[1]

1.前提条件

Retryable写有以下要求:

支持部署拓扑
Retryable写需要一个复制集或分片集群,并且不支持独立实例。
支持存储引擎
Retryable写需要存储引擎支持文档级锁定,比如WiredTiger或内存存储引擎。
3.6 + MongoDB司机
客户端需要MongoDB驱动更新为MongoDB 3.6或更高:

Java 3.6+

Python 3.6+

C 1.9+

C# 2.5+

Node 3.0+

Ruby 2.5+

Perl 2.0+

PHPC 1.4+

Scala 2.2+

MongoDB版本
集群中每个节点的MongoDB版本必须是3.6或更高,集群中每个节点的特性兼容性版本必须是3.6或更高。有关featureCompatibilityVersion标志的更多信息,请参见setFeatureCompatibilityVersion

写确认
具有0的写关注的写操作不能重试。

2.可重试的写和多文档事务

新版本4.0。
事务提交和中止操作是可重试的写操作。如果提交操作或中止操作遇到错误,MongoDB驱动程序将一次性重试操作,而不管retrywrite是否设置为false。

不管retrywrite的值是多少,事务中的写操作都是不可单独重试的。

3.怎么开始重试写

MongoDB的驱动
官方的MongoDB 3.6和4.0兼容的驱动程序需要在连接字符串中包含retrywrite =true选项,以便为该连接启用retryable write。

官方的MongoDB 4.2兼容驱动程序默认支持可重试写。如果应用程序升级到4.2兼容的驱动程序,需要进行可重试写操作,则可能会忽略retrywrite =true选项。升级到4.2兼容驱动程序的应用程序需要禁用retryable写,必须在连接字符串中包含retrywrite =false。

mongo shell
要在mongo shell中启用retryable写操作,请使用--retrywriting命令行选项:

mongo --retryWrites

4.重试写操作

下列写操作在发出已确认的写问题时可重试;例如,写Concern不能是{w: 0}。

提示:事务中的写操作不能单独重试。

MethodsDescriptions

db.collection.insertOne()

db.collection.insert()

db.collection.insertMany()

插入操作。

db.collection.updateOne()

db.collection.replaceOne()

db.collection.save()

db.collection.update() where multi is false

单个文档更新操作。

db.collection.deleteOne()

db.collection.remove() where justOne is true

单个文档删除操作。

db.collection.findAndModify()

db.collection.findOneAndDelete()

db.collection.findOneAndReplace()

db.collection.findOneAndUpdate()

findAndModify 操作. 所有的 findAndModify 操作都是单个文档操作.

db.collection.bulkWrite() with the following write operations:

只包含单文档写操作的批量写操作。可重试的批量操作可以包含指定写操作的任何组合,但不能包含任何多文档写操作,如updateMany。

Bulk operations for:

只包含单文档写操作的批量写操作。可重试的批量操作可以包括指定的写操作的任何组合,但不能包括任何多文档的写操作,如为multi选项指定true的update。

更新碎片键值
从MongoDB 4.2开始,您可以通过发布单个文档的update/findAndModify操作来更新文档的碎片键值(除非碎片键字段是不可变的_id字段),这些操作可以作为可重新尝试的写操作,也可以在事务中执行。有关详细信息,请参见更改文档的碎片键值。

提示:MongoDB 4.2将重试某些遇到重复键异常的单文档upsert (upsert: true和multi: false更新)。有关条件,请参阅Upsert上的重复键错误。
在MongoDB 4.2之前,MongoDB不会重试遇到重复密钥错误的upsert操作。

5.行为

持续的网络错误
MongoDB retryable写只做一次重试尝试。这有助于解决暂时的网络错误和副本集选举,但不是持久的网络错误。

故障恢复时间
如果驱动程序无法在目标副本集或分片集群碎片中找到一个健康的主节点,则驱动程序将在重试之前等待serverSelectionTimeoutMS毫秒来确定新主节点。Retryable写操作不处理故障转移周期超过serverSelectionTimeoutMS的实例。

警告:

如果客户机应用程序在发出写操作之后的响应时间超过了localLogicalSessionTimeoutMinutes,那么当客户机应用程序开始响应时(没有重新启动),可能会重试并再次应用写操作。

重复的关键字错误在Upsert
MongoDB 4.2将重试单文档upsert操作(upsert: true和multi: false)只有在操作满足以下所有条件时,才会由于重复的键错误而失败:

  • 目标集合有一个惟一的索引,该索引导致了重复键错误。
  • 更新匹配条件为:

        单个相等谓词

         {"fieldA": "valueA"},

        或

        一个逻辑的、相等的谓词

        {"fieldA": "valueA", "fieldB": "valueB"}

  • 惟一索引键模式中的字段集与更新查询谓词中的字段集匹配。
  • 更新操作不修改查询谓词中的任何字段。

下表包含upsert操作的例子,服务器可以或不能重试一个重复的键错误:

Unique Index Key PatternUpdate OperationRetryable
{ _id : 1 }
db.collName.updateOne(
  { _id : ObjectId("1aa1c1efb123f14aaa167aaa") },
  { $set : { fieldA : 25 } },
  { upsert : true }
)
Yes
{ fieldA : 1 }
db.collName.updateOne(
  { fieldA : { $in : [ 25 ] } },
  { $set : { fieldB : "someValue" } },
  { upsert : true }
)
Yes
{
  fieldA : 1,
  fieldB : 1
}
db.collName.updateOne(
  { fieldA : 25, fieldB : "someValue" },
  { $set : { fieldC : false } },
  { upsert : true }
)
Yes
{ fieldA : 1 }
db.collName.updateOne(
  { fieldA : { $lte : 25 } },
  { $set : { fieldC : true } },
  { upsert : true }
)

No

fieldA上的查询谓词不是等式

{ fieldA : 1 }
db.collName.updateOne(
  { fieldA : { $in : [ 25 ] } },
  { $set : { fieldA : 20 } },
  { upsert : true }
)

No
更新操作修改查询谓词中指定的字段。

{ _id : 1 }
db.collName.updateOne(
  { fieldA : { $in : [ 25 ] } },
  { $set : { fieldA : 20 } },
  { upsert : true }
)

No
查询谓词字段集(fieldA)与索引键字段集(_id)不匹配。

{ fieldA : 1 }
db.collName.updateOne(
  { fieldA : 25, fieldC : true },
  { $set : { fieldD : false } },
  { upsert : true }
)
No
查询谓词字段集(fieldA、fieldC)与索引键字段集(fieldA)不匹配。

在MongoDB 4.2之前,MongoDB retryable写不支持重试upserts,因为重复的键错误而失败。

诊断
新版本3.6.3。
serverStatus命令及其mongo shell助手db.serverStatus()在事务部分包含关于可重试写的统计信息。

对本地数据库进行可重试的写操作
官方的MongoDB 4.2系列驱动程序默认支持可重试写。向本地数据库写入的应用程序在升级到4.2系列驱动程序时将遇到写入错误,除非显式禁用了可重试的写入。
要禁用retryable写,请在MongoDB集群的连接字符串中指定retrywrite =false。

三、SQL到MongoDB的映射

  • 术语和概念
  • 可执行文件
  • 例子

除了下面的图表之外,您可能还需要考虑关于MongoDB的常见问题的常见问题部分。

1.术语和概念

下表给出了各种SQL术语和概念,以及相应的MongoDB术语和概念。

SQL Terms/ConceptsMongoDB Terms/Concepts
databasedatabase
tablecollection
rowdocument or BSON document
columnfield
indexindex
table joins$lookup, embedded documents

primary key

指定任何唯一的列或列组合作为主键。

primary key

In MongoDB, the primary key is automatically set to the _id field.

aggregation (e.g. group by)

aggregation pipeline

See the SQL to Aggregation Mapping Chart.

SELECT INTO NEW_TABLE

$out

See the SQL to Aggregation Mapping Chart.

MERGE INTO TABLE

$merge (Available starting in MongoDB 4.2)

See the SQL to Aggregation Mapping Chart.

transactions

transactions

TIP

对于许多场景,非规范化的数据模型(嵌入式文档和数组)将继续是数据和用例的最佳选择,而不是多文档事务。也就是说,对于许多场景,适当地对数据建模将最小化对多文档事务的需求。

2.可执行文件
下表给出了一些数据库可执行文件和相应的MongoDB可执行文件。这张表并不是详尽无遗的。

 

 MongoDBMySQLOracleInfomixDB2
Database ServermongodmysqldoracleIDSDB2 Server
Database ClientmongomysqlsqlplusDB-AccessDB2 Client

3.例子
下表给出了各种SQL语句和相应的MongoDB语句。表中的例子假设了以下条件:
SQL示例假设有一个名为people的表。
MongoDB示例假设一个名为people的集合,其中包含以下原型的文档:

{
  _id: ObjectId("509a8fb2f3f4948bd2f983a0"),
  user_id: "abc123",
  age: 55,
  status: 'A'
}

创建和修改
下表给出了与表级操作相关的各种SQL语句和相应的MongoDB语句。

SQL Schema StatementsMongoDB Schema Statements
CREATE TABLE people (
    id MEDIUMINT NOT NULL
        AUTO_INCREMENT,
    user_id Varchar(30),
    age Number,
    status char(1),
    PRIMARY KEY (id)
)

在第一个insertOne()或insertMany()操作上隐式创建。如果未指定_id字段,则自动添加主键_id。

db.people.insertOne( {
    user_id: "abc123",
    age: 55,
    status: "A"
 } )

但是,您也可以显式地创建一个集合:

db.createCollection("people")
ALTER TABLE people
ADD join_date DATETIME

集合不描述或强制执行其文档的结构;即在收集层面并无结构上的改变。
但是,在文档级别,updateMany()操作可以使用$set操作符向现有文档添加字段。

db.people.updateMany(
    { },
    { $set: { join_date: new Date() } }
)
ALTER TABLE people
DROP COLUMN join_date

集合不描述或强制执行其文档的结构;即在收集层面并无结构上的改变。
但是,在文档级别,updateMany()操作可以使用$unset操作符从文档中删除字段。

db.people.updateMany(
    { },
    { $unset: { "join_date": "" } }
)
CREATE INDEX idx_user_id_asc
ON people(user_id)
db.people.createIndex( { user_id: 1 } )
CREATE INDEX
       idx_user_id_asc_age_desc
ON people(user_id, age DESC)
db.people.createIndex( { user_id: 1, age: -1 } )
DROP TABLE people
db.people.drop()

有关使用的方法和操作符的更多信息,请参见:

插入

下表给出了与将记录插入表相关的各种SQL语句和相应的MongoDB语句。

SQL INSERT StatementsMongoDB insertOne() Statements
INSERT INTO people(user_id,
                  age,
                  status)
VALUES ("bcd001",
        45,
        "A")
db.people.insertOne(
   { user_id: "bcd001", age: 45, status: "A" }
)

查询

下表给出了与从表中读取记录相关的各种SQL语句和相应的MongoDB语句。

提示:find()方法总是在返回的文档中包含_id字段,除非通过投影特别排除。下面的一些SQL查询可能包含一个_id字段来反映这一点,即使相应的find()查询中没有包含该字段。

SQL SELECT StatementsMongoDB find() Statements
SELECT *
FROM people
db.people.find()
SELECT id,
       user_id,
       status
FROM people
db.people.find(
    { },
    { user_id: 1, status: 1 }
)
SELECT user_id, status
FROM people
db.people.find(
    { },
    { user_id: 1, status: 1, _id: 0 }
)
SELECT *
FROM people
WHERE status = "A"
db.people.find(
    { status: "A" }
)
SELECT user_id, status
FROM people
WHERE status = "A"
db.people.find(
    { status: "A" },
    { user_id: 1, status: 1, _id: 0 }
)
SELECT *
FROM people
WHERE status != "A"
db.people.find(
    { status: { $ne: "A" } }
)
SELECT *
FROM people
WHERE status = "A"
AND age = 50
db.people.find(
    { status: "A",
      age: 50 }
)
SELECT *
FROM people
WHERE status = "A"
OR age = 50
db.people.find(
    { $or: [ { status: "A" } , { age: 50 } ] }
)
SELECT *
FROM people
WHERE age > 25
db.people.find(
    { age: { $gt: 25 } }
)
SELECT *
FROM people
WHERE age < 25
db.people.find(
   { age: { $lt: 25 } }
)
SELECT *
FROM people
WHERE age > 25
AND   age <= 50
db.people.find(
   { age: { $gt: 25, $lte: 50 } }
)
SELECT *
FROM people
WHERE user_id like "%bc%"
db.people.find( { user_id: /bc/ } )

-or-

db.people.find( { user_id: { $regex: /bc/ } } )
SELECT *
FROM people
WHERE user_id like "bc%"
db.people.find( { user_id: /^bc/ } )

-or-

db.people.find( { user_id: { $regex: /^bc/ } } )
SELECT *
FROM people
WHERE status = "A"
ORDER BY user_id ASC
db.people.find( { status: "A" } ).sort( { user_id: 1 } )
SELECT *
FROM people
WHERE status = "A"
ORDER BY user_id DESC
db.people.find( { status: "A" } ).sort( { user_id: -1 } )
SELECT COUNT(*)
FROM people
db.people.count()

or

db.people.find().count()
SELECT COUNT(user_id)
FROM people
db.people.count( { user_id: { $exists: true } } )

or

db.people.find( { user_id: { $exists: true } } ).count()
SELECT COUNT(*)
FROM people
WHERE age > 30
db.people.count( { age: { $gt: 30 } } )

or

db.people.find( { age: { $gt: 30 } } ).count()
SELECT DISTINCT(status)
FROM people
db.people.aggregate( [ { $group : { _id : "$status" } } ] )

or, for distinct value sets that do not exceed the BSON size limit

db.people.distinct( "status" )
SELECT *
FROM people
LIMIT 1
db.people.findOne()

or

db.people.find().limit(1)
SELECT *
FROM people
LIMIT 5
SKIP 10
db.people.find().limit(5).skip(10)
EXPLAIN SELECT *
FROM people
WHERE status = "A"
db.people.find( { status: "A" } ).explain()

有关使用的方法和操作符的更多信息,请参见

更新记录

下表给出了与更新表中现有记录相关的各种SQL语句和相应的MongoDB语句。

SQL Update StatementsMongoDB updateMany() Statements
UPDATE people
SET status = "C"
WHERE age > 25
db.people.updateMany(
   { age: { $gt: 25 } },
   { $set: { status: "C" } }
)
UPDATE people
SET age = age + 3
WHERE status = "A"
db.people.updateMany(
   { status: "A" } ,
   { $inc: { age: 3 } }
)

删除记录

下表给出了与从表中删除记录相关的各种SQL语句和相应的MongoDB语句。

SQL Delete StatementsMongoDB deleteMany() Statements
DELETE FROM people
WHERE status = "D"
db.people.deleteMany( { status: "D" } )
DELETE FROM people
db.people.deleteMany({})

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值