文章目录

  • 前言
  • 1. 隔离性
  • 2. Rocksdb实现的隔离级别
    • 2.1 常见的四种隔离级别
    • 2.2 Rocksdb 支持的隔离级别及基本实现
      • 2.2.1 ReadComitted 隔离级别的测试
      • 2.2.2 ReadCommitted的实现
      • 2.2.3 RepeatableRead的实现
      • 2.2.4 事务并发处理
  • 3. 一些总结

前言

Rocksdb 作为单机存储引擎,已经非常成熟得应用在了许多分布式存储(CEPH, TiKV),以及十分通用的数据库之上(mysql, mongodb, Drango等),所以Rocksdb本身需要能够实现ACID属性,尤其是其中的不同的隔离级别才能够作为一个公共的存储组件。本节,结合rocksdb6.4.6代码以及官网wiki来梳理一下rocksdb的事务管理以及隔离性的实现。


1. 隔离性

ACID中的隔离性意味着 同时执行的事务之间是互不影响的。这个时候,在一些同时执行事务的场景下,就需要有针对事务的隔离级别,来满足客户端针对存储系统的要求。

图1.1 两个客户之间的竞争状态同时递增计数器

如上图1.1,user1和user2对数据库的访问

  • user1先从数据库中get,得到了42。完成get事务之后拿着get的结果+1,将43set到数据库中
  • user1下发set的同时user2从数据库中get,同样得到了42,也进行42+1 的操作
  • 两者的事务都是各自隔离的,且是串行执行互不影响(user2的get并无法同时访问user1 set的结果),保证了结果对用户的正确性


图1.2 违反了隔离性:一个事务读取了另一个事务执行的结果

如上图中,user2将user1的insert过程中的 hello 作为了自己的输入,即一个事务能够读取另一个事务未被执行状态。这个过程被称作脏读

2. Rocksdb实现的隔离级别

2.1 常见的四种隔离级别

  • ReadUncommited 读取未提交内容,所有事务都可以看到其他未提交事务的执行结果,存在脏读
  • ReadCommited 读取已提交内容,事务只能看到其他已提交事务的更新内容,多次读的时候可能读到其他事务更新的内容
  • RepeatableRead 可重复读,确保事务读取数据时,多次操作会看到同样的数据行(innodb引擎使用快照隔离来实现)。
  • Serializability 可串行化,强制事务之间的执行是有序的,不会互相冲突。

2.2 Rocksdb 支持的隔离级别及基本实现

2.2.1 ReadComitted 隔离级别的测试

Rocksdb支持ReadCommited的隔离级别,它能够提供两个保障

  • 从数据库读时,只能看到已提交的数据(没有脏读(dirty reads):不同事务之间能够读到对方未提交的内容
  • 写入数据库时,只会覆盖已经写入的数据(没有脏写(dirty writes):不同事务之间的写在提交之前能够相互覆盖

先看一下简单的测试代码:

  //支持事务的方式打开rocksdbStatus s = TransactionDB::Open(options, txn_db_options, kDBPath, &txn_db);// 开启事务操作,定义当前事务为t1Transaction* txn = txn_db->BeginTransaction(write_options);assert(txn);// 先下发一个t1的读操作s = txn->Get(read_options, "abc", &value);assert(s.IsNotFound());// 再下发一个t1的写操作(注意此时是在同一个事务t1内部,现在只是不同的操作)s = txn->Put("abc", "def");assert(s.ok());// 在当前事务外部下发一个t2读操作,确认是否存在脏读(txn_db->Get是一个不同于当前事务的独立事务,t2)s = txn_db->Get(read_options, "abc", &value);std::cout << "t2 Get result " << s.ToString() << std::endl;// 在当前事务外部下发一个t3写操作,这里更新的是不同的key,如果更新相同的key。则t1事务commit的时候会报错//s = txn_db->Put(write_options, "xyz", "zzz");s = txn_db->Put(write_options, "abc", "zzz");std::cout << "t3 Put result " << s.ToString() << std::endl;// 提交t1事务s = txn->Commit();assert(s.ok());//提交之后再get一次s = txn_db->Get(read_options, "abc", &value);std::cout << "t4 Get result after commit: " << value << std::endl;delete txn;

输出如下:

# 两个事务Get时不可见对方未提交内容,不存在脏读
t2 Get result NotFound:
# 在提交之后能够发现Set的结果也并未生效,不存在脏写,切Put相同的key发现加锁超时
t3 Put result Operation timed out: Timeout waiting to lock key
# t4在t1提交之后get t1的结果的时候能够看到t1的结果生效
t4 Get result after commit def

通过这个简单的测试代码以及对应的输出结果,我们能够看出当前Rocksdb已经能够支持ReadCommited的隔离级别,不存在脏读,同时脏写实现看起来像是通过加锁来避免的。

2.2.2 ReadCommitted的实现

简单描述一下该隔离特性,Rocksdb的一个事务操作是通过Rocksdb内部WriteBatch实现的,针对不同事务Rocksdb会为其分配对应的WriteBatch,由WriteBatch来处理具体的写入。同时针对同一个事务的读操作,会优先从当前事务的WriteBatch中读,来保证能够读到当前写操作之前未提交的更新。提交的时候则依次写入WAL和memtable之中,保证ACID的原子性和一致性。

大体的流程如下2.1图

图2.1 通过WriteBatch实现 ReadCommitted

以上过程结合我们的测试代码,可以有两种方式来进行

  • 显式得通过事务的方式写入,提交

    Transaction* txn = txn_db->BeginTransaction(write_options);
    txn->Get(read_option,"abc",&value);
    txn->Put("abc","value1");
    txn->commit();
    
  • 直接通过TransactionDB生成一个auto transaction,transactionDB会将这个单独的操作封装成事务,并自动commit。
    txn_db->Get(read_options, "abc", &value);
    txn_db->Put(write_options, "abc", "zzz");
    

一种transactionDB这里没有锁的冲突检查,而我们使用transaction的方式进行Put,实验代码中也能看到有锁的超时检查.

2.2.3 RepeatableRead的实现

可重复读是指Rocksdb重复多次读取数据的时候,能够访问到预期的数值,而不会被其他事务的更新操作影响。
这里的可重复读其实在SQL指定标准之前是用快照隔离来描述的,通用的关系型数据库都使用MVCC机制来进行多版本管理,多版本的访问也就是通过快照来进行的。

Rocksdb这里的实现是通过为每一个写入的key-value请求添加一个LSN(Log Sequence Number),最初是0,每次写入+1,达到全局递增的目的。同时当实现快照隔离时,通过Snapshot设置其与一个lsn绑定,则该snapshot能够访问到小于等于当前lsn的k-v数据,而大于该lsn的key-value是不可见的。

相关代码在snapshot_impl.h之中

class SnapshotImpl : public Snapshot {public://lsn numberSequenceNumber number_;  ......SnapshotImpl* prev_;SnapshotImpl* next_;SnapshotList* list_;                 // 链表头指针int64_t unix_time_; //时间戳// 用于写冲突的检查bool is_write_conflict_boundary_;
};

snapshot可以有多个,它的创建和删除是通过操作一个全局的双向链表来进行,天然得根据创建的时间来进行排序SetSnapShot()函数创建一个快照。
快照隔离的测试代码如下:

  // 通过设置set_snapshot=true,来在BeginTransaction的时候就设置一个快照value = "def";txn_options.set_snapshot = true;txn = txn_db->BeginTransaction(write_options, txn_options);//读取一个快照const Snapshot* snapshot = txn->GetSnapshot();// 重新生成一个写入事务db->Put(write_options, "abc", "xyz");// 通过读取的snapshot,来访问指定的keyread_options.snapshot = snapshot;// 通过GetForUpdate来进行读操作,这个函数锁定多个事务操作,即也会让之前的Put加入到WriteBatch中。s = txn->GetForUpdate(read_options, "abc", &value);assert(value == "def");// 提交事务s = txn->Commit();// 新生成的事务可能与读操作冲突,不过这里用了GetForUpdate就不会产生冲突了assert(s.IsBusy());delete txn;// 释放snapshotread_options.snapshot = nullptr;snapshot = nullptr;

其中用到了GetForUpdate函数,区别于Get接口,GetForUpdate对读记录加独占写锁,保证后续对该记录的写操作是排他的。保证了多个事务的操作都能够被GetForUpdate锁定,而不是一个GetForUpdate成功,其他的失败。

2.2.4 事务并发处理

通过对以上事务的隔离性的分析,能够总结出以下几种事务并发时Rocksdb的处理方式。

  1. 如果事务都是读操作,不论操作之间是否有交集,都不会触发锁定
  2. 如果事务冲包含读、写操作
    • 所有的读事务都不会触发锁定,读的结果与snapshot请求相关
    • 写事务之间不存在交集,则不会锁定
    • 写事务之间存在交集,如果此时设置了snapshot,则会串行提交;如果没有设置snapshot,则只执行第一个写操作,其他的操作都会失败。

3. 一些总结

本文通过探索Rocksdb的事务机制 以及描述了事务的基本实现,读提交以及可重复读的特性基本能够让其作为单机存储引擎底座,来适配分布式存储中的ACID特性。
同时还有一些更加细粒度的实现需要探索:

  • 像针对写事务的交集如何进行冲突检测以及如何通过锁机制解决冲突。
  • 默认使用的悲观锁以及可以显式调用的乐观锁 在隔离性的几个级别中是如何生效的。
  • 还有2PC(Two-Pharse-Commit)的实现机制,以及2PC上层的应用场景

不得不说一个公共的存储底座实现是真的不容易,后续将尝试手写一些隔离级别,来加深对分布式锁的理解。

Rocksdb 事务(一): 隔离性的实现相关推荐

  1. 同一事务中未提交的写能读到吗_03、MySQL事务的隔离性分析

    事务可以用来保证数据库的完整性:要么都做,要么不做.在 MySQL 中,事务支持是在引擎层实现的.你现在知道,MySQL 是一个支持多引擎的系统,但并不是所有的引擎都支持事务.比如 MySQL 原生的 ...

  2. Mysql 事务的隔离性(隔离级别)

    Mysql 中的事务分为手动提交和自动提交,默认是自动提交,所以我们在Mysql每输入一条语句,其实就会被封装成一个事务提交给Mysql服务端. 手动提交需要先输入begin,表示要开始处理事务,然后 ...

  3. 面试官:MySQL事务的隔离性是如何实现的?

    并发场景 最近做了一些分布式事务的项目,对事务的隔离性有了更深的认识,后续写文章聊分布式事务.今天就复盘一下单机事务的隔离性是如何实现的? 隔离的本质就是控制并发,如果SQL语句就是串行执行的.那么数 ...

  4. 你这 Saga 事务保“隔离性”吗?

    文章目录 1. 什么是 Saga 事务模式 2. Saga 的状态图 3. Saga 模式案例 3.1 准备工作 3.2 测试运行 3.3 案例分析 3.3.1 JSON 状态描述分析 3.3.2 代 ...

  5. 什么是事务?事务的基本操作、事务的隔离性问题、事务的ACID特性

    文章目录 1.什么是事务? 2.事务的基本操作 2.事务操作的注意事项 3.事务的隔离级别 3.1 无隔离性的问题 3.1.1 脏读 3.1.2 不可重复读 3.1.3 幻读 3.2 事务的隔离级别 ...

  6. 【MySQL】事务及其隔离性/隔离级别

    目录 一.事务的概念 1.事务的四种特性 2.事务的作用 3.存储引擎对事务的支持 4.事务的提交方式 二.事务的启动.回滚与提交 1.准备工作:调整MySQL的默认隔离级别为最低/创建测试表 2.事 ...

  7. spring 事务隔离级别和传播行为_Spring事务传播性与隔离性实战

    一.事务传播性 1.1 什么是事务的传播性 事务的传播性一般在事务嵌套时候使用,比如在事务A里面调用了另外一个使用事务的方法,那么这俩个事务是各自作为独立的事务执行提交,还是内层的事务合并到外层的事务 ...

  8. 跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现

    来源 | 阿丸笔记 提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一 ...

  9. MySQL数据库事务隔离性的实现

    摘要:事实上在数据库引擎的实现中并不能实现完全的事务隔离,比如串行化. 本文分享自华为云社区<[数据库事务与锁机制]- 事务隔离的实现>,原文作者:技术火炬手 . 事实上在数据库引擎的实现 ...

最新文章

  1. mysql 表引擎无法更新_Mysql安装archive引擎更新表引擎
  2. 干货 | 《利用Python进行数据分析》资料开源下载
  3. ACdream1032(树形DP)
  4. Loj #2036. 「SHOI2015」自动刷题机
  5. UOJ #131 BZOJ 4199 luogu P2178【NOI2015】品酒大会 (后缀自动机、树形DP)
  6. Android开发之播放量点赞量打赏量收藏量单位格式化工具类
  7. html 指定对象为块元素,html内联(行内)元素、块级(块状)元素和行内块元素分类...
  8. c语言RePutDate用法,住宿结帐管理系统--C语言课程设计.doc
  9. 从spring容器中获取对象工具类
  10. Ubuntu中zabbix4.2配置shell脚本邮件报警
  11. 根据第xx天推算日期
  12. html地址栏传值问题
  13. html鼠标各种坐标,各种MOUSE鼠标形状的表示方法
  14. Mosets Tree开发笔记
  15. 一些与一对一视频聊天软件开发有关的事,也许你该了解的
  16. java 打印对象大小_如何获取一个Java对象所占内存大小
  17. 整数划分问题 java
  18. 青青子佩(朋友写给我的)
  19. 计算机基础知识八股文(网络篇)
  20. 跑步耳机哪款好用,排行前五的运动耳机推荐

热门文章

  1. 用java实现给图片增加图片水印或者文字水印(也支持视频图像帧添加水印)
  2. uva 401.Palindromes
  3. android帧动画实现方法之一
  4. 一些可能没用过的调试窗口
  5. usaco frame up(所有拓扑排序的输出)
  6. pacman 查询_pacman包管理常用命令
  7. html的子页面获取自己url,如何从html页面获取url参数并将其显示在textarea中?
  8. docker 安装oracle_阿里云使用Docker搭建Hadoop集群
  9. 武汉大学计算机学院放假时间,计算机学院关于2019年学生放暑假的通知
  10. java 详解 搭建 框架_maven 基本框架搭建详解