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

相关文章

SQL server配置管理器找不到如何打开它

《SQLserver配置管理器找不到如何打开它》最近遇到了SQLserver配置管理器打不开的问题,尝试在开始菜单栏搜SQLServerManager无果,于是将自己找到的方法总结分享给大家,对SQ... 目录方法一:桌面图标进入方法二:运行窗口进入方法三:查找文件路径方法四:检查 SQL Server 安

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Python从零打造高安全密码管理器

《Python从零打造高安全密码管理器》在数字化时代,每人平均需要管理近百个账号密码,本文将带大家深入剖析一个基于Python的高安全性密码管理器实现方案,感兴趣的小伙伴可以参考一下... 目录一、前言:为什么我们需要专属密码管理器二、系统架构设计2.1 安全加密体系2.2 密码强度策略三、核心功能实现详解

浅谈配置MMCV环境,解决报错,版本不匹配问题

《浅谈配置MMCV环境,解决报错,版本不匹配问题》:本文主要介绍浅谈配置MMCV环境,解决报错,版本不匹配问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录配置MMCV环境,解决报错,版本不匹配错误示例正确示例总结配置MMCV环境,解决报错,版本不匹配在col

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

SpringKafka消息发布之KafkaTemplate与事务支持功能

《SpringKafka消息发布之KafkaTemplate与事务支持功能》通过本文介绍的基本用法、序列化选项、事务支持、错误处理和性能优化技术,开发者可以构建高效可靠的Kafka消息发布系统,事务支... 目录引言一、KafkaTemplate基础二、消息序列化三、事务支持机制四、错误处理与重试五、性能优

Tomcat版本与Java版本的关系及说明

《Tomcat版本与Java版本的关系及说明》:本文主要介绍Tomcat版本与Java版本的关系及说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Tomcat版本与Java版本的关系Tomcat历史版本对应的Java版本Tomcat支持哪些版本的pythonJ

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

IDEA中Git版本回退的两种实现方案

《IDEA中Git版本回退的两种实现方案》作为开发者,代码版本回退是日常高频操作,IntelliJIDEA集成了强大的Git工具链,但面对reset和revert两种核心回退方案,许多开发者仍存在选择... 目录一、版本回退前置知识二、Reset方案:整体改写历史1、IDEA图形化操作(推荐)1.1、查看提

如何使用Python实现一个简单的window任务管理器

《如何使用Python实现一个简单的window任务管理器》这篇文章主要为大家详细介绍了如何使用Python实现一个简单的window任务管理器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 任务管理器效果图完整代码import tkinter as tkfrom tkinter i