【MyDB】3-DataManager数据管理 之 4-数据页缓存

【MyDB】3-DataManager数据管理 之 3-数据页管理

本章涉及代码:top/xianghua/mydb/server/dm/pageCache/PageCacheImpl.java

页面缓存设计与实现

这里参考大部分数据库的设计,将默认数据页大小定为 8K。如果想要提升向数据库写入大量数据情况下的性能的话,也可以适当增大这个值。

上一节我们已经实现了一个通用的缓存框架,并且定义了页面。

那么这一节我们需要缓存页面,就可以直接借用那个缓存的框架了。但是首先,需要定义出页面的结构。注意这个页面是存储在内存中的,与已经持久化到磁盘的抽象页面有区别。

PageImpl页面定义

定义一个页面如下:

java 复制代码
public class PageImpl implements Page {
    private int pageNumber; // 页面页号,从1开始计数
    private byte[] data; // 页面实际包含的字节数据
    private boolean dirty; // 标志页面是否是脏页面。脏页面在缓存驱逐时需要被写回磁盘
    private Lock lock; // 页面锁,用于并发控制
    
    private PageCache pc; // 页面所属的缓存
}

这里保存了一个 PageCache(还未定义)的引用,用来方便在拿到 Page 的引用时可以快速对这个页面的缓存进行释放操作。

定义页面缓存的接口如下:

java 复制代码
public interface PageCache {
    int newPage(byte[] initData);
    Page getPage(int pgno) throws Exception;
    void close();
    void release(Page page);

    void truncateByBgno(int maxPgno);
    int getPageNumber();
    void flushPage(Page pg);
}

页面缓存的具体实现类,需要继承抽象缓存框架,并且实现 getForCache() 和 releaseForCache() 两个抽象方法。

  • getForCache():从文件中读取页面数据并包装成Page对象
  • releaseForCahce():驱逐页面时根据页面是否为脏页面,决定是否将其写回文件系统。

getForCache() 文件中读取页面数据

由于数据源就是文件系统,getForCache() 直接从文件中读取,并包裹成 Page 即可:

java 复制代码
    /**
     * 根据pageNumber从数据库文件中读取页数据,并包裹成Page
     */
    @Override
    protected Page getForCache(long key) throws Exception {
        int pgno = (int)key; // 获取pageNumber
        long offset = PageCacheImpl.pageOffset(pgno); // 计算页偏移量,(pgno-1) * PAGE_SIZE;

        // 分配一个页大小的ByteBuffer,8K
        ByteBuffer buf = ByteBuffer.allocate(PAGE_SIZE);
        fileLock.lock(); // 文件加锁
        try {
            // 定位到文件中并读取数据到ByteBuffer中
            fc.position(offset);
            fc.read(buf);
        } catch(IOException e) {
            Panic.panic(e);
        }
        fileLock.unlock(); // 文件解锁
        return new PageImpl(pgno, buf.array(), this); // 返回新的Page
    }

releaseForCache() 驱逐页面

而 releaseForCache() 驱逐页面时,也只需要根据页面是否是脏页面,来决定是否需要写回文件系统:

java 复制代码
    @Override
    protected void releaseForCache(Page pg) {
        // 是脏页面,则写回到文件中
        if(pg.isDirty()) {
            flush(pg);
            pg.setDirty(false);
        }
    }

    public void release(Page page) {
        release((long)page.getPageNumber());
    }

    public void flushPage(Page pg) {
        flush(pg);
    }

    private void flush(Page pg) {
        int pgno = pg.getPageNumber();
        long offset = pageOffset(pgno);

        fileLock.lock();
        try {
            ByteBuffer buf = ByteBuffer.wrap(pg.getData());
            fc.position(offset);
            fc.write(buf);
            fc.force(false);
        } catch(IOException e) {
            Panic.panic(e);
        } finally {
            fileLock.unlock();
        }
    }

AtomicInteger 记录当前打开数据库文件页

PageCache 还使用了一个 AtomicInteger,来记录了当前打开的数据库文件有多少页。这个数字在数据库文件被打开时就会被计算,并在新建页面时自增。

java 复制代码
public int newPage(byte[] initData) {
    int pgno = pageNumbers.incrementAndGet();
    Page pg = new PageImpl(pgno, initData, null);
    flush(pg);  // 新建的页面需要立刻写回
    return pgno;
}

提一点,同一条数据是不允许跨页存储的,这一点会从后面的章节中体现。这意味着,单条数据的大小不能超过数据库页面的大小。

剩余两个函数 recoverInsert() 和 recoverUpdate() 用于在数据库崩溃后重新打开时,恢复例程直接插入数据以及修改数据使用。

recoverInsert()和recoverUpdate()

用于在数据库崩溃后重新打开时,恢复例程直接插入数据以及修改数据使用。

  • recoverInsert():将数据插入到指定的偏移位置,并更新空闲空间的偏移量。
  • recoverUpdate():在指定的偏移位置直接更新数据,而不更新空闲空间的偏移量。
java 复制代码
// 将raw插入pg中的offset位置,并将pg的offset设置为较大的offset
public static void recoverInsert(Page pg, byte[] raw, short offset) {
    pg.setDirty(true); // 将pg的dirty标志设置为true,表示pg的数据已经被修改
    System.arraycopy(raw, 0, pg.getData(), offset, raw.length); // 将raw的数据复制到pg的数据中的offset位置

    short rawFSO = getFSO(pg.getData()); // 获取pg的当前空闲空间偏移量
    if (rawFSO < offset + raw.length) { // 如果当前的空闲空间偏移量小于offset + raw.length
        setFSO(pg.getData(), (short) (offset + raw.length)); // 将pg的空闲空间偏移量设置为offset + raw.length
    }
}

// 将raw插入pg中的offset位置,不更新update
public static void recoverUpdate(Page pg, byte[] raw, short offset) {
    pg.setDirty(true); // 将pg的dirty标志设置为true,表示pg的数据已经被修改
    System.arraycopy(raw, 0, pg.getData(), offset, raw.length); // 将raw的数据复制到pg的数据中的offset位置
}

参考资料

MYDB 3. 数据页的缓存与管理 | 信也のブログ (shinya.click)

数据页缓存 | EasyDB (blockcloth.cn)

相关推荐
YOLO数据集集合9 小时前
EvoAgent:面向PR研发治理的自进化Multi-Agent Harness系统
java·开发语言·目标检测·agent·自进化
java1234_小锋9 小时前
【技术专题】Mysql8 数据库 - Mysql8 数据类型简介
数据库·mysql
知行EDI10 小时前
知行之桥 MaBang 端口使用指南——Create Order 订单创建篇
java·服务器·数据库
笑鸿的学习笔记10 小时前
C++笔记之大块顺序写
java·c++·笔记
成旭先生10 小时前
【2026】企业信息模糊查询 API 实战:名称、注册号、统一社会信用代码、企业类型与法人一次查全
服务器·数据库·数据服务
打工仔折腾 AI10 小时前
从 Demo 到生产级 Agent:8 个关键设计机制与 Python 实现拆解
java·jvm·人工智能·后端·python·langchain·ai agent 实战
JosieBook10 小时前
【数据库】MySQL 实战精通系列 · 第10篇:分库分表与分布式事务实战
数据库·分布式·mysql
写后端的胖头鱼10 小时前
一文详解Cache Aside(旁路缓存模式)
redis·缓存·延迟双删·缓存一致
我叫洋洋11 小时前
Cadence CIS 元器件库合并实战:3000+ 焊盘、156 个符号零冲突并入自有库
数据库·单片机·嵌入式硬件·oracle·电路
AI 编程助手GPT11 小时前
GPT-6 Luna (Batch) 批量处理性能与质量深度评测
数据库·gpt·batch