本文接下来介绍h2database如何插入一条行记录。
下文h2database简称为h2。
h2使用页存储数据,默认每页大小为4k,数据结构为B

本文目录

  • 1、加表锁
  • 2、搜索插入位置
  • 3、构建undo log
  • 4、插入B树
  • 5、提交事务

1、加表锁

当向表里面插入一条数据时,首先查到该表对应的root页,如果该表之前打开过,那么root页便存在内存中,如果没有打开过,需要首先打开该表,将root页加载到内存中。在插入数据前,h2还会对表进行加共享锁,以防止在操作数据期间,有其他事务更新表结构。
加锁的代码如下:

table.lock(session, Table.WRITE_LOCK);//增加共享锁
try {table.addRow(session, newRow);//向表中增加一条记录
}

2、搜索插入位置

接下来根据要插入的数据,搜索B+树,代码如下:

 //第一次调用时,page就是root页,key可以认为是要插入记录行的主键static <K,V> CursorPos<K,V> traverseDown(Page<K,V> page, K key) {CursorPos<K,V> cursorPos = null;while (!page.isLeaf()) {//如果page不是叶子节点就继续搜索int index = page.binarySearch(key) + 1;//在key上使用二分查找搜索if (index < 0) {index = -index;//如果index是负值,表示要插入的位置}cursorPos = new CursorPos<>(page, index, cursorPos);//下面方法用于找index对应的孩子节点,如果孩子节点不再内存中,便从磁盘加载page = page.getChildPage(index);}//走完上面的循环后,page表示的便是叶子节点,在叶子节点上执行一次二分查找,并可以//定位到要插入的位置//CursorPos构造方法的最后一个参数cursorPos表示当前叶子节点的父节点,如果root节点同时也是叶子节点,那么cursorPos为nullreturn new CursorPos<>(page, page.binarySearch(key), cursorPos);}

根据traverseDown()的返回值便可知插入行的叶子节点、插入节点中的位置,以及父节点。

3、构建undo log

接下来要构造undo log了。
在h2中,undo log也是按照B+tree存储的,其key有两部分组成:

long undoKey=transactionId << LOG_ID_BITS) | logId;

transactionId是事务id,事务id唯一定位一个事务,logId表示undo log的id,每生成一个undo log,logId便加一,每个事务的初始logId从0开始。LOG_ID_BITS是一个常量,值为40,从undoKey的组成上可以看出,h2支持同时运行的事务数是有上限要求的,logId的二进制位也不能超过40位,如果一个事务的undo log数过多,h2会报错:The transaction contains too many changes.

在h2中,多个不同时运行的事务的undo log可能会存储在同一个B树上,比如,在时刻M,同时运行了三个事务T1,T2,T3,那么T1,T2,T3的undo log分别存储在不同的undo log树上,而且它们的undo logId都是从0开始的 ,时刻M+1上述三个事务都执行完成,在时刻M+2运行了事务T4,那么事务T4的undo log和事务T1的undo log可能会存储在同一个B树上。

下面的代码是undo log的B树上的value值:

     new Record<>(mapId, key, valueToLog)

undo log的value是Record对象,可以看到构造方法有三个入参,mapId可以唯一确认表,也就是根据mapId可以唯一定位一个表,key是插入数据的主键,valueToLog表示被插入的记录替换的数据,如果插入的位置原来没有数据,那么valueToLog为null。
key和value生成好之后,执行下面的逻辑将undo log设置到keysBuffer和valuesBuffer中。

 keysBuffer[appendCounter] = key;if (valuesBuffer != null) {valuesBuffer[appendCounter] = value;}++appendCounter;

当然代码到这里,undo log仅仅是写入了内存中,h2有后台线程会以固定频率将keysBuffer和valuesBuffer写入磁盘中。
另外一点需要注意在写入undo log时,h2会将undo log的B树加锁,当数据写入到keysBuffer和valuesBuffer后,便会解锁。

4、插入B树

undo log处理完毕之后,接下来便是将行记录插入到表对应的树的叶子节点中。代码如下:

        //将key和value插入到页面中@Overridepublic void insertLeaf(int index, K key, V value) {int keyCount = getKeyCount();//keyCount表示当前页存储的key的个数insertKey(index, key);//将key插入叶子节点中,insertKey的执行逻辑和下面if分支里面的代码基本一致if(values != null) {V[] newValues = createValueStorage(keyCount + 1);//创建空的value数组DataUtils.copyWithGap(values, newValues, keyCount, index);//将旧value数组的值复制到newValues中,并将index位置空出来values = newValues;setValueInternal(index, value);//将value插入到指定下标位置if (isPersistent()) {addMemory(MEMORY_POINTER + map.evaluateMemoryForValue(value));}}}

到这里,新插入的行记录数据便完全更新到了内存中,此时数据都在内存的数组中保存着。
如果该表还有辅助索引,或者说二级索引,那么接下来还会将索引值和表的主键值插入到索引页中,同时更新索引也会记录undo log。
上面的内容是将行记录插入到表里面的操作,接下来介绍事务提交的时候,h2都做了哪些动作。

5、提交事务

首先说一下,当事务提交的时候,h2如何判断事务是否更改过数据。判断方式就是依赖上面提到的logId,如果logId不为0,表示记录过undo log,也就意味着该事务更改过数据。如果事务没有更改过数据,那么事务便没有提交动作了,下面介绍的内容便不会执行。
如果logId不为0,首先在undo log里面增加一条记录,表示当前事务进行了提交。该记录的undoKey也是两部分组成,规则同上面,不过logId不同,这里的logId是一个常量值:TransactionStore.LOG_ID_MASK,也就是undoKey的后40个bit位全部都是1,该记录的value也是一个常量值:

    static final Record<?,?> COMMIT_MARKER = new Record<>(-1, null, null);

可以看到提交的时候,undo log的部分数据都是固定值,这也很好理解,因为事务提交,undo log只需要记录哪个事务id提交了即可,其他信息都是不必须的。
记录完undo log后,接下来重新访问要插入记录的表的B树,找到当前记录插入到表中的位置,接下来h2将原来位置的对象由VersionedValueUncommitted更换为DefaultRow,在表里面一般行记录都是使用DefaultRow表示,从对象更换上可以看出来,h2将一个临时对象做了替换。如果该表上有二级索引,那么在二级索引上也会进行同样的操作将临时对象更换下来。
到这里为止,执行insert和commit语句的操作已经完成了。从上面的流程可以看出,这两个操作都是在内存中进行的,数据并没有写入磁盘,因此一旦发生系统掉电,那么数据是无法保存的,为了防止出现问题,h2还提供了一个后台线程,该线程按照固定的频率将更改的页面刷新到磁盘。这部分内容在以后的文章中介绍。

h2database源码解析-如何插入一条行记录相关推荐

  1. h2database源码解析-表和索引

    目录 表 索引 MVPrimaryIndex MVDelegateIndex MVSecondaryIndex 索引更新 表 h2使用类MVTable表示数据库表,h2的表数据是基于主键排列的,这种表 ...

  2. HashMap-红黑树插入平衡、左旋、右旋源码解析

    目录 一.树的演变 二.红黑树 1.红黑树的特点 2.树左旋右旋的过程 3.红黑树插入节点情景分析: 三.HashMap插入平衡.左旋.右旋源码解析 1.添加值 2.插入平衡 3.左旋.右旋 一.树的 ...

  3. Java集合---LinkedList源码解析

    一.源码解析 1. LinkedList类定义 2.LinkedList数据结构原理 3.私有属性 4.构造方法 5.元素添加add()及原理 6.删除数据remove() 7.数据获取get() 8 ...

  4. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  5. HashMap源码解析(JDK1.8)

    HashMap源码解析(JDK1.8) 目录 定义 构造函数 数据结构 存储实现源码分析 删除操作源码分析 hashMap遍历和异常解析 1. 定义 HashMap实现了Map接口,继承Abstrac ...

  6. Java集合之TreeMap源码解析上篇

    上期回顾 上期我从树型结构谈到了红黑树的概念以及自平衡的各种变化(指路上期←戳),本期我将会对TreeMap结合红黑树理论进行解读. 首先,我们先来回忆一下红黑树的5条基本规则. 1.结点是红色或者黑 ...

  7. java容器三:HashMap源码解析

    前言:Map接口 map是一个存储键值对的集合,实现了Map接口的主要类有以下几种 TreeMap:用红黑树实现 HashMap:数组和链表实现 HashTable:与HashMap类似,但是线程安全 ...

  8. hashmap删除指定key_Java集合之HashMap源码解析(JDK8)

    哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景非常丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...

  9. 2022-10-24 ClickHouse 源码解析-查询引擎经典理论

    ClickHouse 源码解析: 综述 ClickHouse 源码解析: MergeTree Write-Path ClickHouse 源码解析: MergeTree Read-Path Click ...

最新文章

  1. mysql学习二:sql语句分类
  2. 扫地机器人的特点描写_描写扫地机器人五年级作文500字
  3. var let const 区别
  4. mysql数据库as表恢复_【翻译】如何从ibdata和.frm文件恢复MySQL表数据
  5. iis php 开启gzip_IIS6.0 开启Gzip方法及PHP Gzip函数分享
  6. asp access物流基础信息查询平台毕业设计成品
  7. failed to load ldlinux.c32
  8. 使用Animation编辑器编辑动画
  9. 【专利提交】个人通过CPC客户端网上提交专利文稿的完整流程
  10. React学习之进阶WEB组件(二十)
  11. word参考文献格式设置(国标下载)
  12. Go 依赖管理工具 Dep 的安装及配置
  13. 软考——系统架构设计师工作日志
  14. 六、利用ESP32搭建网络服务器(一)
  15. kafka topic acl授权
  16. 关系型数据库设计——银行业务管理系统
  17. python mac 启动台 图标 跳跃_详解macOS的Mac电脑上使用“启动台”(Launchpad)
  18. IDEA 神级插件!效率提升 50 倍!
  19. fiddler--HTTP协议调试工具
  20. 确定项目的目的和目标

热门文章

  1. DX9绘图-------VB6编程学习DX9游戏编程DirectX9编程2D小游戏源码冷风引擎CoolWind2D游戏引擎(8)
  2. UBNT ER-4 配置IPsec实现不同网络互访
  3. stata生成脉冲响应图怎么导出_GIF 动态图:我用 Stata 来制作!
  4. 根据生日判断是否大于18岁
  5. 2010年消费电子技术20个最大失败:Buzz居首
  6. ubuntu20下安装nginx插件geoip2查询ip信息
  7. 音频变速变调原理及 soundtouch 代码分析
  8. 树莓派系统、硬件更新
  9. DELMIA软件 初始界面的设定
  10. php 可以编辑treegrid,TreeGrid(树形表格)