赞
踩
参考: 菜狗日常
此项目是自己写的吗?
Github 参考golang 实现的简单数据库,依据项目的基本框架,改造成 Java版 的数据库
为什么要做此项目?
整体结构时怎样的?
前端 + 后端,通过socket进行交互
前端:读取用户输入,发送到后端执行,输出返回结果,等待下一次输入
后端: 解析SQL,如果时合法的SQL,尝试执行并返回结果
后端划分五个模块,每个模块通过接口向其依赖的模块提供方法
五个模块:
五个模块的依赖关系:
五大模块各自的职责(作用)?TM -> DM -> VM -> IM -> TBM
开发环境
Window 11 + Idea 2021 + JDK 8
TM 模块作用是什么?
通过 XID 文件来维护事务的状态,并提供接口供其他模块来查询某个事务的状态
什么是 XID 文件?
XID 文件的开头保存一个 8 字节的数字,记录 XID 文件中事务的个数,然后给每个事务分配 1 字节 , 用来保存事务的状态
举个例子:第 2 个事务(xid, id =2)在文件中的状态就存储在 (xid - 1) + 8 字节处,因为 第 1 个事务是超级事务
,永远是已提交状态(committed),不需要记录
事务有哪几种状态?
xid 有那些特点?
每个事务对应一个xid(唯一性)
xid是从1开始标号,并自增(不可重复性)
第1个事务 是超级事务,操作在没有事务下进行,操作的xid 设成 0 永远是committed状态 (特殊性)
接口就是以下七个方法:
1. 开始事务、提交事务、回滚事务
2. 事务是否进行、事务是否提交、事务是否撤销
3. TM 关闭
DM 模块是最底层的模块
分页管理和数据项管理涉及缓存,需要设计缓存框架
为什么用引用计数缓存而不是LRU呢?
LRU 策略中,资源驱逐不可控,上层模块无法感知
引用计数缓存只有上层模块主动释放引用,缓存在确保没有模块在使用这个资源时,才会驱逐资源
缓存满,引用计数无法释放缓存会报什么错?
OOM 内存溢出
引用计数器有哪些方法?
get(key) release(key)
引用计数的功能?
Java将数组看成一个对象,在内存中是以对象的形式存储的,无法实现共享内存数组,单纯松散地规定数组的可使用范围
public class SubArray {
public byte[] raw;
public int start;
public int end;
public SubArray(byte[] raw, int start, int end) {
this.raw = raw;
this.start = start;
this.end = end;
}
}
主要内容: DM 模块向下对文件系统的抽象部分
DM 将文件系统抽象成页面,每次对文件系统的读写都是以页面为单位,同样,从文件系统读进去的数据也是以页面为单位进行缓存的
默认数据页的大小为 8 K
。如果想要提升数据库写入大量数据情况下的性能的话,可以适当增大此值
缓存页面直接借用上一节的缓存框架
页面的结构是怎样的?【PageImpl Page】
接口:
getPage方法: 求页数
close方法:往中存页面关闭
release方法:强行释放某页的缓存
flushPage方法:将某页刷新到磁盘
getPageNumber方法:获得页数
truncateByBgno方法: 将某页截断,即删除
存储一些元数据、用来做启动检查
原理:
过程
启动时设置初识字节
关闭时拷贝字节
校验字节
此块比较难,代码在 PageX
XChecksum: 4字节的整数 后序所有日志计算的校验和
Log1 ~ LogN 常规的日志数据
BadTail 数据库崩溃时,没来得及写完的日志数据(不一定存在)
每条日志的格式:
Size: 四字节整数,标志Data段的字节数
Checksum: 该日志的校验和
单条日志的校验和,通过指定种子实现
所有日志校验和进行求和操作,就能得到日志文件的校验和
calChecksum: 对所有日志求校验和,就能得到日志文件的校验和
internNext: 不断从文件中读取下一条日志,并将其中的 Data 解析出来并返回
checkAndRemoveTail: 检查并移除bad tail
log : 将数据包裹成日志格式,写入文件后,再更新文件的校验和,更新校验和时,会刷新缓冲区,保证内容写入磁盘
日志策略:
在进行I 和 U 操作之前,必须先进行对应的日志操作,以保证日志写入磁盘后,才能进行数据操作
1. 对 Ti 之前所有事务的日志,进行重做(redo)
2. 接着检查Ti 的状态(XID),如果 Ti 的状态是已完成(committed 和 absorted) ,就将 Ti 重做,否则进行撤销(undo)
多线程下情况怎么样?
如何避免以上问题?
规定1: 正在进行的事务,不会读取其他任何未提交的事务产生的数据
如何避免以上问题?
规定2: 正在进行的事务,不会修改其他任何未提交的事务修改或产生的数据
并发情况下日志恢复?
在不会发生规定1或者规定2的基础(VM层会满足)上:
重做所有崩溃时已完成(committed或aborted)的事务
撤销所有崩溃时未完成(active)的事务
页面索引,缓存了每一页的空闲空间, 用于在上层模块进行插入操作时,能够快速找到一个合适空间的页面,而无需从磁盘或者缓存中检查每一个页面的信息
页面索引实现:
2.在启动时,就会遍历所有的页面信息,获取页面的空闲空间,安排到这 40 个区间中
到这了!!!
数据库冲突定义:
只看更新操作(U) 和 读操作®,两操作只要满足以下三条件
1. 两操作是由不同的事务执行
2. 两操作操作的是同一数据项
3. 两操作至少有一个是更新操作
就可以这两个操作相互冲突
数据库冲突的两种情况?
1. 两个不同事务的 U 操作冲突
2. 两个不同事务的 U 、 R 操作冲突
定义数据冲突的意义?
交换两个互不冲突的操作的顺序,不会对最终结果造成影响,而交换两个冲突操作的顺序,则是会有影响
例子:在并发情况下,两个事务同时操作 x , 假设 x 的初值为 0,最后的 x 的结果是 1 xxxx
– 两段锁协议( 2PL )
当采用 2PL 时,如果某个事务 i 已经对 x 加锁,且另一个事务 j 也想操作 x,如果 两操作相互冲突的话, 事务 j 就会进行相应阻塞
例子: T1 已经因为 U1(x) 锁定 x,那么 T2 对 x进行读或者写操作都会被阻塞, T2 必须等 T1 释放 对 x 的锁
例子: T1 想要更新记录 X 的值,T1 首先获取 X 的锁,接着更新,也就是创建了一个新的 X 的版本,假设为 x3。假设 T1 还没有释放 X 的锁时, T2 想要读取 X 的值,这时候就不会阻塞,会返回一个较老版本的X,例如 x2,这样最后的执行结果,就等价于,T2先执行,T1后执行,调度序列依然是串行化的,如果 X 没有一个更老的版本,那只能等待 T1 释放锁,所以说只是降低概率
为保证数据的可恢复性,VM 层传递到 DM 的操作序列需要满足:
规则1: 正在进行的事务,不会读取其他任何未提交的事务产生的数据
规则2:正在进行的事务,不会修改其他任何未提交的事务或修改或产生的数据
由于 2PL 与 MVCC 这两个条件就很轻易满足
Entry类维护
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。