title: mysql · myrocks · myrocks之事务处理
mysql目前支持的事务引擎有innodb,tokudb. rocksdb加入mysql阵营后,mysql支持的事务引擎增长至3个。
myrocks目前支持的事务隔离级别有read-committed和repeatable-read. 同innodb一样,myrocks也支持mvcc机制。
可以说,myrocks提供了很好的事务支持,能够满足的一般业务的事务需求。
谈到rocksdb事务,就必须提及rocksdb中的sequence number机制。rocksdb中的每一条记录都有一个sequence number, 这个sequence number存储在记录的key中。
对于同样的user key记录,在rocksdb中可能存在多条,但他们的sequence number不同。
sequence number是实现事务处理的关键,同时也是mvcc的基础。
snapshot是rocksdb的快照信息,snapshot实际就是对应一个sequence number.
简单的讲,假设snapshot的sequence number为sa, 那么对于此snapshot来说,只能看到sequence number<=sa的记录,sequence number>sa的记录是不可见的。
snapshot 结构
snapshot 主要包含sequence number和snapshot创建时间,sequence number 取自当前的sequence number.
snapshot 管理
snapshot由全局双向链表管理,根据sequence number排序。snapshot的创建和删除都需要维护双向链表。
snapshot与compact
rocksdb的compact操作与snapshot有紧密联系。以我们熟悉的innodb为例,rocksdb的compact类似于innodb的purge操作, 而snapshot类似于innodb的read view. innodb做purge操作时会根据已有的read view来判断哪些undo log可以purge,而rocksdb的compact操作会根据已有snapshot信息即全局双向链表来判断哪些记录在compace时可以清理。
判断的大体原则是,从全局双向链表取出最小的snapshot sequence number sn. 如果已删除的老记录sequence number <=sn, 那么这些老记录在compact时可以清理掉。
有了snapshot,mvcc实现起来就很顺利了。记录的sequence number天然的提供了记录的多版本信息。
每次查询用户记录时,并不需要加锁。而是根据当前的sequence number sn创建一个snapshot, 查询过程中只取小于或等于sn的最大sequence number的记录。查询结束时释放snapshot.
关键代码段
隔离级别也是通过snapshot来实现的。在innodb中,隔离级别为read-committed时,事务中每的个stmt都会建立一个read view, 隔离级别为repeatable-read时,只在事务开启时建立一次read view. rocksdb同innodb类似,隔离级别为read-committed时,事务中每的个stmt都会建立一个snapshot, 隔离级别为repeatable-read时,只在事务开启时第一个stmt建立一次snapshot.
关键代码片段
隔离级别实现差异
在read committed隔离级别下,如果一个大事务要更新1000w行,当它更新了前900w行时,
同时另一个事务已经更新了后100w行,那么myrocks会重新获取快照,再次尝试更新,这样
而之前的处理方式是直接报死锁错误。
innodb不会出现上述情况,当第一个大事更新是会持有b树的index lock, 第二个事务会一直等待index lock直至第一个事务提交完成。
myrocks目前只支持一种锁类型:排他锁(x锁),并且所有的锁信息都保存在内存中。
锁结构
每个锁实际上存储的哪条记录被哪个事务锁住。
每个锁实际是key和lockinfo的映射. 锁信息都保存在map中
为了减少全局锁信息访问的冲突, rocksdb将锁信息进行按key hash分区,
同时每个column family 存储一个这样的lockmap.
锁相关参数:
max_num_locks:事务锁个数限制
expiration:事务过期时间
通过设置以上两个参数,来控制事务锁占用过多的内存。
死锁检测
rocksdb内部实现了简单的死锁检测机制,每次加锁发生等待时都会向下面的map中插入一条等待信息,表示一个事务id等待另一个事务id.
同时会检查wait_txn_map_是否存在等待环路,存在环路则发生死锁。
死锁检测关键代码片段
死锁检测相关参数
deadlock_detect:是否开启死锁检测
deadlock_detect_depth:死锁检查深度,默认50
gap lock
innodb中是存在gap lock的,主要是为了实现repeatable read和唯一性检查的。
而在rocksdb中,不支持gap lock(rocksdb insert是也会多对唯一键加锁,以防止重复插入,
严格的来讲也算是gap lock).
那么在rocksdb一些需要gap lock的地方,目前是报错和打印日志来处理的。
相关参数
gap_lock_write_log: 只打印日志,不返回错误
gap_lock_raise_error: 打印日志并且返回错误
锁示例
直接看例子

myrocks最近也支持了binlog xa.
在开启binlog的情况下,myrocks提交时,会经历两阶段提交阶段。
prepare阶段,根据server层生成的xid(由mysqlxid+server_id+qurey_id组成),在rockdb内部执行2pc操作,生成prepare(xid),endprepare()记录。
commit阶段,根据事务成还是失败,生成commit(xid)或rollback(xid)记录。
myrocks在事务处理方面还有些不完善的地方,比如锁类型只有单一的x锁,不支持gap lock,纯内存锁占用内存等。 myrocks社区正在持续改进中,一起期待。