h2database源码解析-如何插入一条行记录
本文接下来介绍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源码解析-如何插入一条行记录相关推荐
- h2database源码解析-表和索引
目录 表 索引 MVPrimaryIndex MVDelegateIndex MVSecondaryIndex 索引更新 表 h2使用类MVTable表示数据库表,h2的表数据是基于主键排列的,这种表 ...
- HashMap-红黑树插入平衡、左旋、右旋源码解析
目录 一.树的演变 二.红黑树 1.红黑树的特点 2.树左旋右旋的过程 3.红黑树插入节点情景分析: 三.HashMap插入平衡.左旋.右旋源码解析 1.添加值 2.插入平衡 3.左旋.右旋 一.树的 ...
- Java集合---LinkedList源码解析
一.源码解析 1. LinkedList类定义 2.LinkedList数据结构原理 3.私有属性 4.构造方法 5.元素添加add()及原理 6.删除数据remove() 7.数据获取get() 8 ...
- 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现
写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...
- HashMap源码解析(JDK1.8)
HashMap源码解析(JDK1.8) 目录 定义 构造函数 数据结构 存储实现源码分析 删除操作源码分析 hashMap遍历和异常解析 1. 定义 HashMap实现了Map接口,继承Abstrac ...
- Java集合之TreeMap源码解析上篇
上期回顾 上期我从树型结构谈到了红黑树的概念以及自平衡的各种变化(指路上期←戳),本期我将会对TreeMap结合红黑树理论进行解读. 首先,我们先来回忆一下红黑树的5条基本规则. 1.结点是红色或者黑 ...
- java容器三:HashMap源码解析
前言:Map接口 map是一个存储键值对的集合,实现了Map接口的主要类有以下几种 TreeMap:用红黑树实现 HashMap:数组和链表实现 HashTable:与HashMap类似,但是线程安全 ...
- hashmap删除指定key_Java集合之HashMap源码解析(JDK8)
哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景非常丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...
- 2022-10-24 ClickHouse 源码解析-查询引擎经典理论
ClickHouse 源码解析: 综述 ClickHouse 源码解析: MergeTree Write-Path ClickHouse 源码解析: MergeTree Read-Path Click ...
最新文章
- mysql学习二:sql语句分类
- 扫地机器人的特点描写_描写扫地机器人五年级作文500字
- var let const 区别
- mysql数据库as表恢复_【翻译】如何从ibdata和.frm文件恢复MySQL表数据
- iis php 开启gzip_IIS6.0 开启Gzip方法及PHP Gzip函数分享
- asp access物流基础信息查询平台毕业设计成品
- failed to load ldlinux.c32
- 使用Animation编辑器编辑动画
- 【专利提交】个人通过CPC客户端网上提交专利文稿的完整流程
- React学习之进阶WEB组件(二十)
- word参考文献格式设置(国标下载)
- Go 依赖管理工具 Dep 的安装及配置
- 软考——系统架构设计师工作日志
- 六、利用ESP32搭建网络服务器(一)
- kafka topic acl授权
- 关系型数据库设计——银行业务管理系统
- python mac 启动台 图标 跳跃_详解macOS的Mac电脑上使用“启动台”(Launchpad)
- IDEA 神级插件!效率提升 50 倍!
- fiddler--HTTP协议调试工具
- 确定项目的目的和目标