前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MongoDB基础概念与事务支持

MongoDB基础概念与事务支持

原创
作者头像
范锦
修改2018-06-29 14:07:47
3.4K2
修改2018-06-29 14:07:47
举报
文章被收录于专栏:MongoDBMongoDB

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

MongoDB

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

数据库(Databases、DB)

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

创建DB

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

代码语言:txt
复制
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的文档,以键-值对形式进行存储

代码语言:txt
复制
{
   field1: value1,
   field2: value2,
   field3: value3,
   ...
   fieldN: valueN
}

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

代码语言:txt
复制
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存储引擎有效
事务接口
代码语言:txt
复制
Session.startTransaction()
Session.commitTransaction()
Session.abortTransaction()
事务重试
代码语言:txt
复制
// 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;
            }
        }
    }
}
事务提交重试
代码语言:txt
复制
// 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;
            }
       }
    }
}
事务与提交重试

完整代码:

代码语言:txt
复制
// 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时,则需要在每次具体操作中,指定对应的等待时长

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MongoDB
    • 数据库(Databases)与集合(Collections)
      • 数据库(Databases、DB)
      • 集合(Collections)
      • 视图
    • 文档
      • 附:关于BSON格式
      • 文档结构
      • 命名规则
      • 排序/比较
    • MongoDB CRUD基本原则
      • 原子性与事务操作
    • 事务
      • 事务接口
      • 事务重试
      • 事务提交重试
      • 事务与提交重试
      • 原子性
    • 事务与锁
    相关产品与服务
    云数据库 SQL Server
    腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    http://www.vxiaotou.com