手写数据库

XID文件

在数据库中,每一个事务都有一个id编号,用来唯一标识此事务。为了简单起见,假设所有事务从0开始,并自增。我们将所有的事务保存在xid文件中。为了记录事务总数,采用long数据类型进行记录,也就是使用8字节进行记录,所以xid文件的头部,要使用8字节表示总事务数。使用一字节表示每个事务,具体来说就是用一字节表示事务的状态。

数据库中,事务具有三种状态:active(运行状态,使用0表示),committed(提交状态,使用1表示),aborted(回滚状态,使用2表示)。

                                 XID文件内部格式

值得注意的是,我们在操作MySQL的时候不是都要开启事务的,有些不需要我们开启事务,针对这些不需要开启事务的操作,默认事务id=0。

代码实现

首先定义一些变量

// XID文件头长度,表示总事务数    static final int XID_HEADER_LENGTH = 8;    // XID文件中每个事务状态的长度    private static final int XID_STATE_LENGTH = 1;    // 超级事务,永远为提交状态    public static final long SUPER_XID = 0;
    // 事务的三种状态    private static final byte XID_STATE_ACTIVE = 0;    private static final byte XID_STATE_COMMITTED = 1;    private static final byte XID_STATE_ABORTED = 2;
    // xid文件后缀    static final String XID_SUFFIX = ".xid";    // 随机接入类,用于写xid文件    private RandomAccessFile file;    // 文件管道    private FileChannel fc;    // xid文件中记录的总事件数    private long xidCounter;    // 使用锁机制来保证线程安全    private Lock counterLock;

开启事务操作:要开启一个事务,首先要定义事务id,记录事务状态,最后更新XID文件中总事务个数。

/**     * 开启事务,并返回事务id     * @return 事务id     */    @Override    public long begin() {        // 保障原子性        counterLock.lock();        try {            // 获取事务id,并更改状态为运行,同时写入xid文件            long xid = xidCounter + 1;            updateXID(xid, XID_STATE_ACTIVE);            incrXIDCounter();            return xid;        } finally {            counterLock.unlock();        }    }                /**     * 更新xid事务状态,并持久化到xid文件中     * @param xid 事务id     * @param status 事务状态     */    private void updateXID(long xid, byte status) {        // 获取事务在xid文件中的偏移量        long offset = getXidPosition(xid);        byte[] tmp = new byte[XID_STATE_LENGTH];        tmp[0] = status;        ByteBuffer buf = ByteBuffer.wrap(tmp);        try {            fc.position(offset);            fc.write(buf);        } catch (IOException e) {            Panic.panic(e);        }        try {            // 不将文件元数据(比如权限等)刷新到磁盘            fc.force(false);        } catch (IOException e) {            Panic.panic(e);        }    }            /**     * 事务总数加一,更新文件头部     */    private void incrXIDCounter() {        xidCounter ++;        // 将总事务数转成8字节数据        ByteBuffer buf = ByteBuffer.wrap(Parser.long2Byte(xidCounter));        try {            fc.position(0);            fc.write(buf);        } catch (IOException e) {            Panic.panic(e);        }        try {            fc.force(false);        } catch (IOException e) {            Panic.panic(e);        }    }

提交事务、回滚事务操作:其实就是将事务状态进行更新

 /**     * 提交事务     * @param xid 事务id     */    @Override    public void commit(long xid) {        updateXID(xid, XID_STATE_COMMITTED);    }
    /**     * 回滚事务     * @param xid 事务id     */    @Override    public void abort(long xid) {        updateXID(xid, XID_STATE_ABORTED);    }

检查事务状态:首先获取事务在xid文件中的相应位置,并读取一字节状态数据,和输入状态进行对比。因为有id=0的操作(不需要事务),所以在判断时需要注意

/**     * 检测事务是否处于指定状态     * @param xid 事务id     * @param status 事务状态     * @return     */    private boolean checkXID(long xid, byte status) {        long offset = getXidPosition(xid);        // 读物事务一字节状态数据        ByteBuffer buf = ByteBuffer.wrap(new byte[XID_STATE_LENGTH]);        try {            fc.position(offset);            fc.read(buf);        } catch (IOException e) {            Panic.panic(e);        }        return buf.array()[0] == status;    }
    /**     * 检查事务是否处于运行状态     * @param xid 事务id     * @return     */    @Override    public boolean isActive(long xid) {        if(xid == SUPER_XID) return false;        return checkXID(xid, XID_STATE_ACTIVE);    }
    /**     * 检查事务是否处于提交状态     * @param xid 事务id     * @return     */    @Override    public boolean isCommitted(long xid) {        if(xid == SUPER_XID) return true;        return checkXID(xid, XID_STATE_COMMITTED);    }
    /**     * 检查事务是否处于回滚状态     * @param xid 事务id     * @return     */    public boolean isAborted(long xid) {        if(xid == SUPER_XID) return false;        return checkXID(xid, XID_STATE_ABORTED);    }

对于新建的文件对象要检查文件是否出错:判断是否是8字节表示总事务数,换句话就是文件长度必须大于8。然后根据文件长度计算出来的总长度和文件真实长度是否相同。

 /**     * 初始化事务文件,传入一个事务文件     * @param raf     * @param fc     */    public TransactionManagerImpl(RandomAccessFile raf, FileChannel fc) {        this.file = raf;        this.fc = fc;        counterLock = new ReentrantLock();        // 检查文件        checkXIDCounter();    }
    /**     * 检查XID文件是否合法,也就是头部用8字节表示总事务     */    private void checkXIDCounter() {        long fileLen = 0;        try {            fileLen = file.length();        } catch (IOException e) {            Panic.panic(Error.BadXIDFileException);        }
        // 文件长度最少都是8字节        if (fileLen < XID_HEADER_LENGTH){            Panic.panic(Error.BadXIDFileException);        }
        // 获取前8个字节,计算总事务数        ByteBuffer buf = ByteBuffer.allocate(XID_HEADER_LENGTH);        try {            fc.position(0);            fc.read(buf);        } catch (IOException e) {            Panic.panic(e);        }
        this.xidCounter = Parser.parseLong(buf.array());
        // 文件实际最大长度        long end = getXidPosition(this.xidCounter + 1);        if(end != fileLen) {            Panic.panic(Error.BadXIDFileException);        }
    }
    /**     * 根据事务id获取其在文件中的位置     * @param xid 事务的id     * @return 事务对应的位置     */    private long getXidPosition(long xid) {        return XID_HEADER_LENGTH + (xid - 1) * XID_STATE_LENGTH;    }

真实数据测试

为了更好的理解数据格式,我简单模拟了几个事务,XID文件数据格式如下:

总共有2个事务,事务1回滚,事务2提交。

其余内容可在公众号查看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值