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 篇文章2 人订阅

我来说两句

2 条评论
登录 后参与评论

相关文章

来自专栏Python

wtforms

 简介 WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。 安装: ? 1 pip3 install wtf...

1947
来自专栏大内老A

.NET Core采用的全新配置系统[2]: 配置模型设计详解

在《.NET Core采用的全新配置系统[1]: 读取配置数据》中,我们通过实例的方式演示了几种典型的配置读取方式,其主要目的在于使读者朋友们从编程的角度对.N...

1709
来自专栏醒者呆

Debug EOS:nodeos + mongo_db_plugin

nodeos开始运行前,要先使用项目的总CmakeList.txt配置,这里我配置了boost库的位置,如果你配置了boost的环境变量可以跳过这里。

1351
来自专栏salesforce零基础学习

salesforce 零基础学习(三十七) DML及Database方法简单描述

在apex中通过soql查询可以使用两种方式,使用DML语句或者使用Database的方法。 使用DML语句和使用Database类的方法对于我们来说用的都很多...

1927
来自专栏芋道源码1024

源码级别解读 mybatis 插件

简介: ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以...

3408
来自专栏钟绍威的专栏

操作系统启动顺序bios在哪里寻址机制bootloader结构建立段机制使能保护模式

概述 在这里以x86的处理器为例 机器在启动的时候会执行第一条指令。这条指令会去执行bios,将控制权交给bios。 bios完成硬件的质检,然后将boot...

17810
来自专栏PingCAP的专栏

TiDB 源码阅读系列文章(四)Insert 语句概览

本文为 TiDB 源码阅读系列文章的第四篇。上一篇文章简单介绍了整体流程,无论什么语句,大体上是在这个框架下运行,DDL 语句也不例外。

3805
来自专栏黄Java的地盘

旧项目TypeScript改造问题与解决方案记

由于本次改造的项目为一个通过NPM进行发布的基础服务包,因此本次采用TypeScript进行改造的目标是移除Babel全家桶,减小包体积,同时增加强类型约束从而...

1451
来自专栏信安之路

ROP Emporium 挑战 WP

ROP Emporium 挑战是用来学习 ROP 技术的一系列挑战,本文就对于其中涉及的所有挑战记录一下 wirte up。

780
来自专栏技术博文

唯一索引与主键索引的比较

唯一索引 唯一索引不允许两行具有相同的索引值。 如果现有数据中存在重复的键值,则大多数数据库都不允许将新创建的唯一索引与表一起保存。当新数据将使表中的键值重复时...

28411

扫码关注云+社区