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

相关文章

Linux之软件包管理器yum详解

《Linux之软件包管理器yum详解》文章介绍了现代类Unix操作系统中软件包管理和包存储库的工作原理,以及如何使用包管理器如yum来安装、更新和卸载软件,文章还介绍了如何配置yum源,更新系统软件包... 目录软件包yumyum语法yum常用命令yum源配置文件介绍更新yum源查看已经安装软件的方法总结软

IDEA如何切换数据库版本mysql5或mysql8

《IDEA如何切换数据库版本mysql5或mysql8》本文介绍了如何将IntelliJIDEA从MySQL5切换到MySQL8的详细步骤,包括下载MySQL8、安装、配置、停止旧服务、启动新服务以及... 目录问题描述解决方案第一步第二步第三步第四步第五步总结问题描述最近想开发一个新应用,想使用mysq

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.

Debian如何查看系统版本? 7种轻松查看Debian版本信息的实用方法

《Debian如何查看系统版本?7种轻松查看Debian版本信息的实用方法》Debian是一个广泛使用的Linux发行版,用户有时需要查看其版本信息以进行系统管理、故障排除或兼容性检查,在Debia... 作为最受欢迎的 linux 发行版之一,Debian 的版本信息在日常使用和系统维护中起着至关重要的作

Java实现任务管理器性能网络监控数据的方法详解

《Java实现任务管理器性能网络监控数据的方法详解》在现代操作系统中,任务管理器是一个非常重要的工具,用于监控和管理计算机的运行状态,包括CPU使用率、内存占用等,对于开发者和系统管理员来说,了解这些... 目录引言一、背景知识二、准备工作1. Maven依赖2. Gradle依赖三、代码实现四、代码详解五

Redis事务与数据持久化方式

《Redis事务与数据持久化方式》该文档主要介绍了Redis事务和持久化机制,事务通过将多个命令打包执行,而持久化则通过快照(RDB)和追加式文件(AOF)两种方式将内存数据保存到磁盘,以防止数据丢失... 目录一、Redis 事务1.1 事务本质1.2 数据库事务与redis事务1.2.1 数据库事务1.

你的华为手机升级了吗? 鸿蒙NEXT多连推5.0.123版本变化颇多

《你的华为手机升级了吗?鸿蒙NEXT多连推5.0.123版本变化颇多》现在的手机系统更新可不仅仅是修修补补那么简单了,华为手机的鸿蒙系统最近可是动作频频,给用户们带来了不少惊喜... 为了让用户的使用体验变得很好,华为手机不仅发布了一系列给力的新机,还在操作系统方面进行了疯狂的发力。尤其是近期,不仅鸿蒙O

什么是 Ubuntu LTS?Ubuntu LTS和普通版本区别对比

《什么是UbuntuLTS?UbuntuLTS和普通版本区别对比》UbuntuLTS是Ubuntu操作系统的一个特殊版本,旨在提供更长时间的支持和稳定性,与常规的Ubuntu版本相比,LTS版... 如果你正打算安装 Ubuntu 系统,可能会被「LTS 版本」和「普通版本」给搞得一头雾水吧?尤其是对于刚入

windows端python版本管理工具pyenv-win安装使用

《windows端python版本管理工具pyenv-win安装使用》:本文主要介绍如何通过git方式下载和配置pyenv-win,包括下载、克隆仓库、配置环境变量等步骤,同时还详细介绍了如何使用... 目录pyenv-win 下载配置环境变量使用 pyenv-win 管理 python 版本一、安装 和

SpringBoot嵌套事务详解及失效解决方案

《SpringBoot嵌套事务详解及失效解决方案》在复杂的业务场景中,嵌套事务可以帮助我们更加精细地控制数据的一致性,然而,在SpringBoot中,如果嵌套事务的配置不当,可能会导致事务不生效的问题... 目录什么是嵌套事务?嵌套事务失效的原因核心问题:嵌套事务的解决方案方案一:将嵌套事务方法提取到独立类