4.2 版本管理器——Entry与事务隔离级别

2024-09-01 08:12

本文主要是介绍4.2 版本管理器——Entry与事务隔离级别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

VM是基于两段锁协议实现调度序列的可串行化,并实现了MVCC以消除读写阻塞。同时也实现了两种隔离级别,所以我们还需要明确版本的概念; DM 层向上层提供了数据项(Data Item)的概念,VM 通过管理所有的数据项,向上层提供了记录(Entry)的概念。上层模块通过 VM 操作数据的最小单位,就是记录。VM 则在其内部,为每个记录,维护了多个版本(Version)。每当上层模块对某个记录进行修改时,VM 就会为这个记录创建一个新的版本

Entry

EntryDataItem基础上进一步封装的数据,其结构可以再看一下下图:

image.png

对一条记录,Entry维护了其结构。理论上MVCC可以保存多版本的记录,然而这里的实现中,VM并没有向上层提供update的操作,update由表和字段的管理器(TBM)来实现了 从上图可以看出,Entry中含有三个字段,后续会讨论他们的作用

Entry定义

对于一条记录来说,MYDB 使用 Entry 类维护了其结构。虽然理论上,MVCC 实现了多版本,但是在实现中,VM 并没有提供 Update 操作,对于字段的更新操作由后面的表和字段管理(TBM)实现。所以在 VM 的实现中,一条记录只有一个版本。 由于一条记录存储在一条 Data Item 中,所以 Entry 中保存一个 DataItem 的引用即可:

Entry格式数据

[XMIN] [XMAX] [DATA]

  1. XMIN 是创建该条记录(版本)的事务编号

  2. XMAX 则是删除该条记录(版本)的事务编号

  3. DATA 就是这条记录持有的数据

public class Entry {// 定义了XMIN的偏移量为0private static final int OF_XMIN = 0;// 定义了XMAX的偏移量为XMIN偏移量后的8个字节private static final int OF_XMAX = OF_XMIN+8;// 定义了DATA的偏移量为XMAX偏移量后的8个字节private static final int OF_DATA = OF_XMAX+8;// uid字段,可能是用来唯一标识一个Entry的private long uid;// DataItem对象,用来存储数据的private DataItem dataItem;// VersionManager对象,用来管理版本的private VersionManager vm;

创建一个新的Entry对象

    public static Entry newEntry(VersionManager vm, DataItem dataItem, long uid) {if (dataItem == null) {return null;}Entry entry = new Entry();entry.uid = uid;entry.dataItem = dataItem;entry.vm = vm;return entry;}

加载一个Entry

LoadEntry 用来加载一个Entry。它首先从VersionManager中读取数据,然后创建一个新的Entry

/*** 通过uid和vm加载entry* @param vm 用来管理版本的* @param uid 唯一标识* @return* @throws Exception*/public static Entry loadEntry(VersionManager vm, long uid) throws Exception {DataItem di = ((VersionManagerImpl)vm).dm.read(uid);return newEntry(vm, di, uid);}

生成日志格式的Entry数据

WrapEntryRaw 生成日志格式的Entry数据

/*** 生成日志格式数据*/
public static byte[] wrapEntryRaw(long xid, byte[] data) {// 将事务id转为8字节数组byte[] xmin = Parser.long2Byte(xid);// 创建一个空的8字节数组,等待版本修改或删除是才修改byte[] xmax = new byte[8];// 拼接成日志格式return Bytes.concat(xmin, xmax, data);
}

获取记录中持有的数据

Data 以拷贝的形式返回内容;获取记录中持有的数据,也就需要按照上面这个结构来解析:

// 以拷贝的形式返回内容
public byte[] data() {// 加锁,确保数据安全dataItem.rLock();try {// 获取日志数据SubArray sa = dataItem.data();// 创建一个去除前16字节的数组,因为前16字节表示 xmin and xmaxbyte[] data = new byte[sa.end - sa.start - OF_DATA];// 拷贝数据到data数组上System.arraycopy(sa.raw, sa.start+OF_DATA, data, 0, data.length);return data;} finally {//释放锁dataItem.rUnLock();}
}
修改记录中持有的数据
setXmax()

当需要对数据进行修改时,就需要设置 xmax的值;

/*** 设置删除版本的事务编号* @param xid*/
public void setXmax(long xid) {// 在修改或删除之前先拷贝好旧数值dataItem.before();try {// 获取需要删除的日志数据SubArray sa = dataItem.data();// 将事务编号拷贝到 8~15 处字节System.arraycopy(Parser.long2Byte(xid), 0, sa.raw, sa.start+OF_XMAX, 8);} finally {// 生成一个修改日志dataItem.after(xid);}
}

事务隔离级别

读已提交

读已提交级别意味着只能读取到其他事务已经提交的数据可能导致不可重复读问题

MYDB中的实现

MYDB中使用XMINXMAX来实现读已提交

  • XMIN:创建该版本的事务编号,当一个事务创建了一个新的版本后,XMIN会记录下来

  • XMAX:删除该版本的事务编号,当一个版本被删除或者有新版本出现时,XMAX会记录删除该版本的事务的编号

如何利用**XMIN****XMAX**来控制读已提交的逻辑?

要满足读已提交的逻辑,那么就要根据XMINXMAX以及当前的事务编号,三者进行对比,然后决定这个Entry是否对当前事务可见 这里的逻辑如下(满足其一即可见):

  1. 如果版本的XMIN等于当前事务的事务编号,并且XMAX为空(表示尚未被删除),则该版本对当前事务可见(即该版本由当前事务创建并且还没被删除)

  2. 如果版本的XMIN对应的事务已经提交,并且XMAX为空(尚未被删除),或者XMAX不是当前事务的事务编号,并且XMAX对应的事务也已经提交,则该版本对当前事务可见(即由一个已提交的事务创建且尚未被删除;或者由一个已提交的事务创建且只是被未提交的事务删除)

在读提交隔离级别下,事务只能看到已经提交的版本,而不能看到尚未提交的版本或被尚未提交的事务删除的版本。这样可以确保读取的数据是稳定和一致的,同时避免了读取到不一致或未提交的数据的可能性。

(XMIN == Ti and                             // 由Ti创建且
    XMAX == NULL                            // 还未被删除
)
or                                          // 或
(XMIN is commited and                       // 由一个已提交的事务创建且
    (XMAX == NULL or                        // 尚未删除或
    (XMAX != Ti and XMAX is not commited)   // 由一个未提交的事务删除
))

判断函数readCommited

// 用来在读提交的隔离级别下,某个记录是否对事务t可见
private static boolean readCommitted(TransactionManager tm, Transaction t, Entry e) {// 获取事务的IDlong xid = t.xid;// 获取记录的创建版本号long xmin = e.getXmin();// 获取记录的删除版本号long xmax = e.getXmax();// 如果记录的创建版本号等于事务的ID并且记录未被删除,则返回trueif (xmin == xid && xmax == 0) return true;// 如果记录的创建版本已经提交if (tm.isCommitted(xmin)) {// 如果记录未被删除,则返回trueif (xmax == 0) return true;// 如果记录的删除版本号不等于事务的IDif (xmax != xid) {// 如果记录的删除版本未提交,则返回true// 因为没有提交,代表该数据还是上一个版本可见的if (!tm.isCommitted(xmax)) {return true;}}}// 其他情况返回falsereturn false;
}

可重复读

在数据库中,可重复读(Repeatable Read)是一种事务隔离级别,它解决了读提交隔离级别下的不可重复读问题。在可重复读隔离级别下,一个事务执行期间多次读取同一数据项,可以保证读取到的结果是一致的,不会因为其他事务的并发操作而导致数据的不一致性。 不可重复读问题指的是,在读提交隔离级别下,一个事务在执行过程中多次读取同一数据项,但由于其他事务的并发修改操作,导致每次读取到的数据值不同,出现了不一致的情况。可重复读隔离级别通过更严格的规则来解决这个问题。 在可重复读隔离级别下,事务只能读取它开始时已经提交的事务产生的数据版本。这意味着,在事务开始时已经提交的所有事务所产生的数据对当前事务是可见的,而在事务开始后产生的其他事务所产生的数据对当前事务则是不可见的。这样可以确保事务在执行期间读取到的数据是一致的,不会受到其他事务的影响。

MYDB中的实现

还是利用XMINXMAX来判断是否可见 这里的逻辑如下(满足其一即可见):

  1. 如果版本的XMIN等于当前事务的事务编号,并且XMAX为空(表示尚未被删除),则该版本对当前事务可见(即该版本由当前事务创建并且还没被删除)

  2. 如果版本的XMIN对应的事务已经提交,且XMIN小于当前事务的事务编号,并且XMIN不在当前的事务开始前活跃的事务集合中(SP(Ti),Ti为当前事务)且满足下列条件之一:

    • XMAX为空(该版本尚未被删除)

    • XMAX不是当前事务的事务编号,并且XMAX对应的事务尚未提交,并且XMAX大于当前事务的事务编号(由其他事务删除,但是删除他的事务尚未提交或者这个事务在当前事务之后才开始)

    • XMAX在当前事务开始前活跃的事务集合中(SP(Ti)

(XMIN == Ti and                 // 由Ti创建且
 (XMAX == NULL or               // 尚未被删除
))
or                              // 或
(XMIN is commited and           // 由一个已提交的事务创建且
 XMIN < XID and                 // 这个事务小于Ti且
 XMIN is not in SP(Ti) and      // 这个事务在Ti开始前提交且
 (XMAX == NULL or               // 尚未被删除或
  (XMAX != Ti and               // 由其他事务删除但是
   (XMAX is not commited or     // 这个事务尚未提交或
XMAX > Ti or                    // 这个事务在Ti开始之后才开始或
XMAX is in SP(Ti)               // 这个事务在Ti开始前还未提交
))))

事务结构

由于可重复读事务的可见性逻辑,需要提供一个结构,用来抽象事务,以保存快照数据;

// vm对一个事务的抽象
public class Transaction {// 事务的IDpublic long xid;// 事务的隔离级别public int level;// 事务的快照,用于存储活跃事务的IDpublic Map<Long, Boolean> snapshot;// 事务执行过程中的错误public Exception err;// 标志事务是否自动中止public boolean autoAborted;// 创建一个新的事务public static Transaction newTransaction(long xid, int level, Map<Long, Transaction> active) {Transaction t = new Transaction();// 设置事务IDt.xid = xid;// 设置事务隔离级别t.level = level;// 如果隔离级别不为0,创建快照if (level != 0) {t.snapshot = new HashMap<>();// 将活跃事务的ID添加到快照中for (Long x : active.keySet()) {t.snapshot.put(x, true);}}// 返回新创建的事务return t;}// 判断一个事务ID是否在快照中public boolean isInSnapshot(long xid) {// 如果事务ID等于超级事务ID,返回falseif (xid == TransactionManagerImpl.SUPER_XID) {return false;}// 否则,检查事务ID是否在快照中return snapshot.containsKey(xid);}
}
判断函数repeatableRead

private static boolean repeatableRead(TransactionManager tm, Transaction t, Entry e) {// 获取事务的IDlong xid = t.xid;// 获取条目的创建版本号long xmin = e.getXmin();// 获取条目的删除版本号long xmax = e.getXmax();// 如果条目的创建版本号等于事务的ID并且条目未被删除,则返回trueif (xmin == xid && xmax == 0) return true;// 如果条目的创建版本已经提交,并且创建版本号小于事务的ID,并且创建版本号不在事务的快照中if (tm.isCommitted(xmin) && xmin < xid && !t.isInSnapshot(xmin)) {// 如果条目未被删除,则返回trueif (xmax == 0) return true;// 如果条目的删除版本号不等于事务的IDif (xmax != xid) {// 如果条目的删除版本未提交,或者删除版本号大于事务的ID,或者删除版本号在事务的快照中,则返回trueif (!tm.isCommitted(xmax) || xmax > xid || t.isInSnapshot(xmax)) {return true;}}}// 其他情况返回falsereturn false;
}
整体判断
    public static boolean isVisible(TransactionManager tm, Transaction t, Entry e) {if(t.level == 0) {return readCommitted(tm, t, e);} else {return repeatableRead(tm, t, e);}}

这篇关于4.2 版本管理器——Entry与事务隔离级别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1126397

相关文章

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

MySql 事务练习

事务(transaction) -- 事务 transaction-- 事务是一组操作的集合,是一个不可分割的工作单位,事务会将所有的操作作为一个整体一起向系统提交或撤销请求-- 事务的操作要么同时成功,要么同时失败-- MySql的事务默认是自动提交的,当执行一个DML语句,MySql会立即自动隐式提交事务-- 常见案例:银行转账-- 逻辑:A给B转账1000:1.查询

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因

PostgreSQL中的多版本并发控制(MVCC)深入解析

引言 PostgreSQL作为一款强大的开源关系数据库管理系统,以其高性能、高可靠性和丰富的功能特性而广受欢迎。在并发控制方面,PostgreSQL采用了多版本并发控制(MVCC)机制,该机制为数据库提供了高效的数据访问和更新能力,同时保证了数据的一致性和隔离性。本文将深入解析PostgreSQL中的MVCC功能,探讨其工作原理、使用场景,并通过具体SQL示例来展示其在实际应用中的表现。 一、

InnoDB的多版本一致性读的实现

InnoDB是支持MVCC多版本一致性读的,因此和其他实现了MVCC的系统如Oracle,PostgreSQL一样,读不会阻塞写,写也不会阻塞读。虽然同样是MVCC,各家的实现是不太一样的。Oracle通过在block头部的事务列表,和记录中的锁标志位,加上回滚段,个人认为实现上是最优雅的方式。 而PostgreSQL则更是将多个版本的数据都放在表中,而没有单独的回滚段,导致的一个结果是回滚非

【Python知识宝库】上下文管理器与with语句:资源管理的优雅方式

🎬 鸽芷咕:个人主页  🔥 个人专栏: 《C++干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 文章目录 前言一、什么是上下文管理器?二、上下文管理器的实现三、使用内置上下文管理器四、使用`contextlib`模块五、总结 前言 在Python编程中,资源管理是一个重要的主题,尤其是在处理文件、网络连接和数据库

Apache Tiles 布局管理器

陈科肇 =========== 1.简介 一个免费的开源模板框架现代Java应用程序。  基于该复合图案它是建立以简化的用户界面的开发。 对于复杂的网站,它仍然最简单,最优雅的方式来一起工作的任何MVC技术。 Tiles允许作者定义页面片段可被组装成在运行一个完整的网页。  这些片段,或Tiles,可以用于为了降低公共页面元素的重复,简单地包括或嵌入在其它瓦片,制定了一系列可重复使用

JeecgBoot 升级springboot版本到2.6.0

1. 环境描述 Jeecgboot 3.0,他所依赖的springboot版本为2.3.5Release,将springboot版本升级为2.6.0。过程全纪录,从2开始描述。 2. 修改springboot版本号 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-pare

【CSS in Depth 2 精译_024】4.2 弹性子元素的大小

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一章 层叠、优先级与继承(已完结) 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位(已完结) 2.1 相对单位的威力2.2 em 与 rem2.3 告别像素思维2.4 视口的相对单位2.5 无单位的数值与行高2.6 自定义属性2.7 本章小结 第三章 文档流与盒模型(已

Cmake之3.0版本重要特性及用法实例(十三)

简介: CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布:《Android系统多媒体进阶实战》🚀 优质专栏: Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏: 多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 🚀 人生格言: 人生从来没有捷径,只有行动才是治疗恐惧