MongoDB基础概念与事务支持

MongoDB4.0新增了对事务的支持,本文首先介绍一些MongoDB的基础概念,后文会对4.0新增的事务功能进行解读

MongoDB

数据库(Databases)与集合(Collections)

数据库(Databases、DB)

MongoDB中,DB是保存一系列集合(Collections)列表

创建DB

MongoDB无需显示创建DB,当你往指定的DB中插入第一条数据的时候,系统会自动帮你创建一个。因此,你可以在MongoDB中使用use <db_name> 切入到一个不存在的DB空间中

use myNewDB
db.myNewCollection1.insertOne( { x: 1 } )

如果DB "myNewDB"不存在,以上insertOne()操作,会同时创建DB "myNewDB"和集合 "myNewCollection1"

集合(Collections)

MongoDB中,文档保存在集合当中,集合类似关系数据库中的表(Tables)

创建集合

与db类似,MongoDB无需显式创建集合,当你往指定的集合中插入第一条数据时,如果集合不存在,系统会自动帮你创建对应的集合。因此,类似的,以下语句会自动创建一个名为"myNewCollection2"的集合(假设该集合目前不存在):

db.myNewCollection2.insertOne( { x: 1 } )

insertOne()和 createIndex()操作都会默认自动创建对应的集合

显示创建

使用db.createCollection()方法,可以显式创建一个不存在的集合

显示创建的好处在于,可以在创建的时候,自定义创建参数,比如:固定集合容量(capped+size)、指定自增长id(autoIndexId,类似Mysql的autoincrement primary key)、存储引擎的类型(storageEngine)等等

格式限定

MongoDB3.2以后,可以指定MongoDB中文档的模式,当插入的数据不满足指定的模式时,会插入失败

改变文档结构

MongoDB允许动态改变指定集合中文档的结构,比如新增字段、移除字段等,类似Mysql中的alter table add/drop column

视图

MongoDB3.4以后,提供了视图(Views)的功能,与关系数据库中的视图类似

文档

MongoDB以BSON数据格式存储文档数据。BSON是JSON格式的二进制表示形式,但是会比JSON拥有更多的数据类型。

附:关于BSON格式

对于json格式,如果json的结构过大,会导致遍历的时候性能非常差:在json中要跳过一个文档进行数据读取,必须对此文档进行扫描(因为需要完成括号匹配)

而bson格式,相对json来说,会将json的每一个元素的长度存在元素头部(类似KLV结构),因此遍历的时候,可以通过长度字段,快速跳seek到指定的锚点上进行操作。

另一方面,json的数据存储是无类型的(或者都是以string形式存储),如果要修改一个数值,比如将1改成100,由于存储长度发生了变化,所以会导致后面所有的内容都需要往后移动;而bson可以指定数据格式,比如数值类型,则将1变为100时,实际长度并不会发生变化,因此也就无需整体后移,但是带来的副作用就是,可能需要占用比字符串更多的存储空间。

数据格式

存储方式

空间占用

操作速度

修改结构

JSON

字符串

大动大移

BSON

结构化

无需移动或较小移动

文档结构

MongoDB的文档,以键-值对形式进行存储

{
   field1: value1,
   field2: value2,
   field3: value3,
   ...
   fieldN: valueN
}

一个键对应的值,可以是任意一种BSON数据类型data types,甚至是文档、其他文档、数组、或者文档数据

var mydoc = {
               _id: ObjectId("5099803df3f4948bd2f98391"),
               name: { first: "Alan", last: "Turing" },
               birth: new Date('Jun 23, 1912'),
               death: new Date('Jun 07, 1954'),
               contribs: [ "Turing machine", "Turing test", "Turingery" ],
               views : NumberLong(1250000)
            }

上述键值对,包含了以下的数据类型:

  • _id 标识了一个对象ID
  • name 指向一个内嵌文档,这个文档包含了“first”和“last”两个子键
  • birth 和 death 采用的是日期(Date)类型
  • contribs 指向一个字符串数组
  • views 对应的数据类型是长整型(NumberLong)

命名规则

  • _id :保留字段,相当于mysql中的Primary Key
  • 字段名不可以以"$"开头
  • 字段名不可以包含"."
  • 字段名不可以包含"null"取值限制对于使用了索引的文档,索引列的最大长度不能超过指定的最大索引长度

排序/比较

当在不同类型的BSON格式数据进行比较或排序时,MongoDB遵循以下的优先级:

  1. MinKey (internal type)
  2. Null
  3. Numbers (ints, longs, doubles, decimals)
  4. Symbol, String
  5. Object
  6. Array
  7. BinData
  8. ObjectId
  9. Boolean
  10. Date
  11. Timestamp
  12. Regular Expression
  13. MaxKey (internal type)

MongoDB CRUD基本原则

原子性与事务操作

原子性

MongoDB写操作对于文档来说,是原子性的(即MongoDB提供了文档级别的原子操作),即时一个操作同时更新了文档中的多个字段

多文档事务

当一个独立的写操作(比如db.collection.updateMany())同时更新了多个文档,对于每个文档来说,写操作是原子性的,但是各个文档之间的写操作并不能保证原子性

因此,MongoDB4.0以后,提供了多文档事务接口(后文会专门来讲)

事务

MongoDB4.0以后,提供了事务处理能力</br>

MongoDB对于单文档的操作,天然是原子性的,因为对于单文档来说,多个字段的写操作可以通过一次性的修改然后统一回写;但是对于一个操作,如果涉及到多文档的更新,则无法保证整个操作是原子性的,因为每个文档需要独立更新,而在各个文档的更新过程中,很可能由于并发性,被插入了其他操作

4.0以后的版本,支持跨文档、跨集合、跨DB级别的事务操作

  • 事务性保证了要不一个写操作是成功了,所有的更改都被执行了,要不就全部执行失败,所有的操作均无效
  • 一个事务在提交生效前,对所有的外部请求是黑盒不可见的
  • 当前发布的事务版本,只对Replica Set架构有效
  • 当前发布的事务版本,只对WiredTiger存储引擎有效

事务接口

Session.startTransaction()
Session.commitTransaction()
Session.abortTransaction()

事务重试

// Runs the txnFunc and retries if TransientTransactionError encountered
function runTransactionWithRetry(txnFunc, session) {
    while (true) {
        try {
            txnFunc(session);  // 执行事务操作
            break;
        } catch (error) {
            print("Transaction aborted. Caught exception during transaction.");
            // 失败重试
            if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes( "TransientTransactionError")  ) {
                print("TransientTransactionError, retrying transaction ...");
                continue;
            } else {
                throw error;
            }
        }
    }
}

事务提交重试

// Retries commit if UnknownTransactionCommitResult encountered
function commitWithRetry(session) {
    while (true) {
        try {
            session.commitTransaction(); // 提交
            print("Transaction committed.");
            break;
        } catch (error) {
            // 失败重试
            if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes( "UnknownTransactionCommitResult") ) {
                print("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                print("Error during commit ...");
                throw error;
            }
       }
    }
}

事务与提交重试

完整代码:

// Runs the txnFunc and retries if TransientTransactionError encountered
function runTransactionWithRetry(txnFunc, session) {
    while (true) {
        try {
            txnFunc(session);  // 执行事务操作
            break;
        } catch (error) {
            // 失败重试
            if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError")  ) {
                print("TransientTransactionError, retrying transaction ...");
                continue;
            } else {
                throw error;
            }
        }
    }
}
// Retries commit if UnknownTransactionCommitResult encountered
function commitWithRetry(session) {
    while (true) {
        try {
            session.commitTransaction(); // 提交
            print("Transaction committed.");
            break;
        } catch (error) {
            // 失败重试
            if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) {
                print("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                print("Error during commit ...");
                throw error;
            }
       }
    }
}
// Updates two collections in a transactions
function updateEmployeeInfo(session) {
    employeesCollection = session.getDatabase("hr").employees;
    eventsCollection = session.getDatabase("reporting").events;
    session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );
    try{
        employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } );
        eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } );
    } catch (error) {
        print("Caught exception during transaction, aborting.");
        session.abortTransaction();
        throw error;
    }
    commitWithRetry(session);
}
// 开始事务操作
session = db.getMongo().startSession( { mode: "primary" } );
try{
   runTransactionWithRetry(updateEmployeeInfo, session);
} catch (error) {
   // Do something with error
} finally {
   session.endSession();
}

原子性

跨文档事务具有原子性

  • 事务性保证了要不一个写操作是成功了,所有的更改都被执行了,要不就全部执行失败,所有的操作均无效
  • 一个事务在提交生效前,对所有的外部请求是黑盒不可见的

事务与锁

事务操作情况下,默认会通过获取一个超时时间为5ms的锁,如果5ms内锁失败,事务则会终止

5ms为默认参数,可以通过maxTransactionLockRequestTimeoutMillis来修改该参数,以满足具体的业务需求

  • 当该值被设置为0时,则表示一旦获取锁失败,则事务终止
  • 当该值被设置为一个大于0的值时,则表示等待锁的时长,单位ms
  • 当该值被置为-1时,则需要在每次具体操作中,指定对应的等待时长

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

MongoDB

1 篇文章3 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码神联盟

面试题 | 《Java面试题集》-- 第三套

varchar2分别在oracle的sql和pl/sql中都有使用,oracle 在sql参考手册和pl/sql参考手册中指出:oracle sql varch...

1302
来自专栏IMWeb前端团队

nodejs中错误捕获的一些最佳实践

本文作者:IMWeb yisbug 原文出处:IMWeb社区 未经同意,禁止转载 本文内容大部分来自 https://www.joyent.com/...

2066
来自专栏性能与架构

【教程】快速掌握ES6 javascript常用新特性

主要内容 介绍7个 ES6 中常用的新特性 Let 和 const 箭头函数 => 默认参数 解构 ... object 便捷写法 模板字符 `` 学习时间半小...

3245
来自专栏运维咖啡吧

Django model update的各种用法介绍

方法一适合更新一批数据,类似于mysql语句update user set username='nick' where id = 1

1122
来自专栏蓝天

从程序员角度看ELF

原文:http://xcd.blog.techweb.com.cn/archives/222.html

934
来自专栏腾讯IVWEB团队的专栏

nodejs 中错误捕获的一些最佳实践

本文为翻译文章,原文比较长,感觉也有点啰嗦,所以根据个人理解猜测梳理出本文。

5330
来自专栏吴生的专栏

谁说深入浅出虚拟机难?现在我让他通俗易懂(JVM)

1:什么是JVM 大家可以想想,JVM 是什么?JVM是用来干什么的?在这里我列出了三个概念,第一个是JVM,第二个是JDK,第三个是JRE。相信大家对这三个不...

3806
来自专栏逸鹏说道

C# 温故而知新:Stream篇(四)上

FileStream 目录: 如何去理解FileStream? FileStream的重要性 FileStream常用构造函数(重要) 非托管参数SafeFil...

2945
来自专栏Pythonista

Django基础教程

URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于...

3112
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第4章 4.1 规则文件

一个标准的规则文件的格式为已“.drl”结尾的文本文件,因此可以通过记事本工具进行编辑。规则放置于规则文件当中,一个规则文件可以放置多条规则。在规则文件当中也可...

2485

扫码关注云+社区