h2database源码解析-如何更新一条行记录

这里的更新包括两种操作:删、改。更新操作涉及的内容在其他文章里面已经做过介绍了,本文主要是介绍更新的代码流程,以了解更新操作都做了哪些事情。如果有未介绍过的知识点会详细介绍。

改(update)

当执行update语句时,h2的执行流程如下:

  1. 根据查询条件制定执行计划,主要就是确认使用哪个索引,如果涉及多个表关联,确定表的关联顺序等;
  2. 对表加共享锁,对表加的共享锁都保存在lockSharedSessions属性中;
  3. 根据查询遍历所有的表记录,每遍历一条记录就加一个行锁,h2加行锁是在MVPrimaryIndex上加的,调用MVPrimaryIndex.lockRow()方法;
  4. 针对每行要修改的记录,重新构建一个新的行记录,新行记录里面的数据根据SQL语句要求为更新后的数据;
  5. 索引遍历从二级索引开始,调用索引的remove()方法将索引中要更新的记录,也就是B+树中的value更改为VersionedValueUncommitted对象,该对象里面保存了原索引的旧值以及更新后的行记录,二级索引更新完后,更新主键索引,更新主键索引的方式与二级索引一样,这一步只是将value做了替换,key没有变化;
  6. 从主键索引开始遍历,向索引中插入新的行记录数据,如果是更新B+树的value,那么将B+树中对应位置的value值替换为新的VersionedValueUncommitted对象,新对象里面包含了新行记录数据和旧行记录数据,如果SQL更新了索引中的字段值,那么向B+树中直接插入一个新的key和VersionedValueUncommitted对象的value。这样做的好处是当事务回滚或者事务未提交而结束事务,那么数据可以恢复到修改前的状态。注意此时索引里面只是插入了新key,旧key还记录在B+树里面;
  7. 事务提交,提交后将表和索引里面的VersionedValueUncommitted对象替换为事务已提交的对象,如果索引里面有旧key,则直接将key和value删除;
  8. 表解锁,也就是解除对表的共享锁。

行锁:h2加行锁其实是向表的B+树里面插入了一个value为VersionedValueUncommitted的对象,VersionedValueUncommitted里面保存了旧值。
VersionedValueUncommitted:从类名字上可以得到该对象与版本有关系,而且它还表示了事务未提交,该对象里面会保存两个数据,一个是事务id,另一个是undo log的id,每有一条记录被更改为VersionedValueUncommitted对象,那么便有一条对应的undo log。当两个事务同时操作同一个记录时,h2使用VersionedValueUncommitted来判断是否有其他事务已经对该记录加锁了。在本文后面详细介绍VersionedValueUncommitted以及如何判断是否加了行锁。

上面是执行update语句时,h2进行的操作。不知道大家注意没有,第5步和第6步修改数据都是直接在B+树中操作的,那么第7步更改B+树,h2是如何快速做到的,难道需要遍历整个B+树吗?h2能够快速完成第7步操作多亏了undo log,其实在第5步和第6步每进行一个操作都会记录一个undo log,事务提交的时候,直接对undo log回放就可以了。

如何判读是否加了行锁

在介绍h2如何判断一条记录是否已经加了行锁之前,先简单介绍下VersionedValueUncommitted类。
一个VersionedValueUncommitted对象表示B+树上的一个value,对于主键索引来说,这个对象里面存储的就是行记录,不过这个对象有点特殊,它存储的数据表示事务还未提交,当事务提交后,这个对象就会被清理掉。VersionedValueUncommitted继承自VersionedValueCommitted,综合VersionedValueUncommitted的父类及祖先类可以看到,VersionedValueUncommitted一共只有三个属性:

    public final T value;//存储更新后的值
    private final long operationId;//该值有两部分组成,后40 bit表示undo log id,其他bit表示的就是事务id,也就是正在更新数据的事务id
    private final T committedValue;//存储更新前的值

当h2想对某条记录加锁时,首先访问索引MVPrimaryIndex,根据key找到value,如果已经有其他事务对该记录加了锁,那么当前事务拿到的value便是VersionedValueUncommitted对象,那么h2便可以从VersionedValueUncommitted对象里面得知,当前是否有其他事务加锁,以及加锁的事务id是多少,下面代码是h2拿到VersionedValueUncommitted对象后进行的判断:

	//existingValue是VersionedValueUncommitted对象的committedValue属性值
	//existingValue.getOperationId()得到的就是事务id
	//isThisTransaction()方法判断当前事务是否与已经对行加锁的事务相同
	if (existingValue == null ||
		// or entry is a committed one
		(id = existingValue.getOperationId()) == 0 ||
		// or it came from the same transaction
		isThisTransaction(blockingId = TransactionStore.getTransactionId(id))) 

如果上面的代码判断为false,表示已经有其他事务加了锁,而且事务还没提交,接下来调用toWaitFor.waitForThisToEnd()方法等待一段时间,如果在等待期间解锁了,那么当前事务重新对行加锁,否则抛出所等待超时的异常,默认等待时间为2s。

删(delete)

delete操作相对update操作来说,其执行流程的前三步是一样的,都需要先指定执行计划、查出数据、加锁,下面直接介绍与update不同的地方。

  1. 遍历每个索引,从二级索引开始遍历最后遍历主键索引,调用每个索引的index.remove()方法,与update操作的第五步不同的是,这里的VersionedValueUncommitted对象的value属性是null;
  2. 事务提交,将各个索引里面的涉及到删除的记录全部删除;
  3. 表解锁,也就是解除对表的共享锁。

delete操作可以看做是update操作的子集。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值