MongoDB并不像MySQL一样天然支持多文档事务,其演变过程如下:
但是MongoDB4.0的事务仍有限制:
事务的默认最大运行时间是 60s。
1)通过在 mongod 实例级别上修改transactionLifetimeLimitSeconds 的限制来增加。对于分片集群,必须在所有分片副本集成员上设置该参数。超过此时间后,事务将被视为已过期,并由定期运行的清理进程中止。清理进程每 60 秒或每 transactionLifetimeLimitSeconds/2 运行一次,以较小的值为准。
2)要显式设置事务的时间限制,建议在提交事务时指定 maxTimeMS 参数。如果 maxTimeMS 没有设置,那么将使用 transactionLifetimeLimitSeconds;如果设置了 maxTimeMS,但这个值超过了 transactionLifetimeLimitSeconds,那么还是会使用 transactionLifetimeLimitSeconds。
3)事务等待获取其操作所需锁的默认最大时间是 5 毫秒。可以通过修改由maxTransactionLockRequestTimeoutMillis 参数控制的限制来增加。如果事务在此期间无法获得锁,则该事务会被中止。maxTransactionLockRequestTimeoutMillis 可以设置为 0、-1 或大于 0 的数字。将其设置为 0 意味着,如果事务无法立即获得所需的所有锁,则该事务会被中止。设置为 -1 将使用由 maxTimeMS 参数所指定的特定于操作的超时时间。任何大于 0 的数字都将等待时间配置为该时间(以秒为单位)以作为事务尝试获取所需锁的指定时间段。
MongoDB 将当前事务中的所有写操作日志放入单文档中,而oplog只是一个特殊的collection,其单个文档也象正常集合一样受16MB的大小限制。这个限制在MongoDB4.2做了很大的优化,即调整成为当前事务中的每一个写操作日志都创建一个单文档。
综上两点限制,不管如何还是要避免长事务与大事务。
MongoDB的oplog相当于MySQL的binlog,用于主备节点数据复制
MongoDB的journal log相当于MySQL的redo log,用于实现事务持久性,尽量保证数据不丢失以及崩溃恢复
事务的四大特性这里就不多说了,这里主要聊一下MongoDB的事务持久性与隔离性。
PS:针对MongoDB的WiredTiger存储引擎
持久性是指一个事务提交后,其所做的写操作会被永久保存到数据库中,即使此时数据库or操作系统崩溃,修改的数据也不会丢失。
下面我们来看看MongoDB事务持久性是否是这样的,先看一下下图:
我们已经知道journal log记录的是最新的写操作内容,即redo,所以只要写的内容到了journal log,MongoDB就可以在崩溃重启后恢复。
journal log主要受两个配置参数控制。
storage:
journal:
enabled: > #是否开启journal log
commitIntervalMs: > #journal log刷盘的间隔,默认100ms,范围是1-500ms,值越小,丢失数据越少,性能越低;
除了配置文件,也可以通过如下命令调整journalCommitInterval的值:
db.adminCommand({"setParameter":1,"journalCommitInterval":10}); #设置为10ms
可知最多有journalCommitInterval
ms的数据丢失。
另外在db.collection.insert({x:1}, {writeConcern: {j: true}})
写命令中可以通过设置 j 的值为true来确保该语句的journal log刷盘。当然这并不意味着每一个写操作就等于一个IO。MongoDB并不会对每一个操作都立即刷盘,而是会等最多30ms,把30ms内的写操作集中到一起,采用顺序追加的方式写入到盘里。在这30ms内客户端线程会处于等待状态。这样对于单个操作的总体响应时间将有所延长,但对于高并发的场景,综合下来平均吞吐能力和响应时间不会有太大的影响。特别是你能给journal部署一个对顺序写有优化的IO带宽足够的专门的存储系统的话,这个对性能的影响可以降到最低。
还有就是缓冲区buffer中的journal log大小达到100MB(因为journal log文件的大小限制是100MB)也会触发刷盘。
总共三种,可以看到在写操作语句中指定j: true
可确保journal log刷盘,保证数据不丢失。
MongoDB的数据刷盘也有和MySQL一样的checkpoint机制,触发条件如下:
从数据刷盘机制可知,MongoDB的持久性只要靠journal log保证。
db.collection.insert({x:1}, {writeConcern: {w: 1}}) #默认w值为1
所以说,可以通过w和j的值合理安排自己所需要的数据安全级别和性能要求。
隔离性其实很好理解,即一个事务所做的写操作在它提交之前,对于其他事务是不可见的。那A事务修改了id=5的数据后B事务如何还能读到未修改数据呢,当然是记录下历史数据了,但是记录历史数据这个实现有两大流派,James Gray老爷子 的undo log流派和Michael Stonebraker老爷子的多版本派,二者在增删改上各有千秋。
所以说,MongoDB是没有undo log的,事务回滚是靠多版本,不像MySQL,SQLServer等会有undo log 段。
待续…