MongoDB4.0新增了对事务的支持,本文首先介绍一些MongoDB的基础概念,后文会对4.0新增的事务功能进行解读
MongoDB中,DB是保存一系列集合(Collections)列表
MongoDB无需显示创建DB,当你往指定的DB中插入第一条数据的时候,系统会自动帮你创建一个。因此,你可以在MongoDB中使用use <db_name>
切入到一个不存在的DB空间中
use myNewDB
db.myNewCollection1.insertOne( { x: 1 } )
如果DB "myNewDB"不存在,以上insertOne()操作,会同时创建DB "myNewDB"和集合 "myNewCollection1"
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拥有更多的数据类型。
对于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)
}
上述键值对,包含了以下的数据类型:
当在不同类型的BSON格式数据进行比较或排序时,MongoDB遵循以下的优先级:
MongoDB写操作对于文档来说,是原子性的(即MongoDB提供了文档级别的原子操作),即时一个操作同时更新了文档中的多个字段
当一个独立的写操作(比如db.collection.updateMany())同时更新了多个文档,对于每个文档来说,写操作是原子性的,但是各个文档之间的写操作并不能保证原子性
因此,MongoDB4.0以后,提供了多文档事务接口(后文会专门来讲)
MongoDB4.0以后,提供了事务处理能力</br>
MongoDB对于单文档的操作,天然是原子性的,因为对于单文档来说,多个字段的写操作可以通过一次性的修改然后统一回写;但是对于一个操作,如果涉及到多文档的更新,则无法保证整个操作是原子性的,因为每个文档需要独立更新,而在各个文档的更新过程中,很可能由于并发性,被插入了其他操作
4.0以后的版本,支持跨文档、跨集合、跨DB级别的事务操作
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来修改该参数,以满足具体的业务需求
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。