一起写一个数据库(一)

项目来源:https://ziyang.moe/article/mydb1.html

事务的定义及相关操作

XID的定义

在MyDB中,每一个事务都有一个XID,这个XID唯一标识了这个事务。

  • XID编号从1开始自增,不可重复
  • 编号0为超级事务(Super Transaction)
    • 当一些操作想在没有申请事务的情况下进行,那么可以将该事务的XID编号设置为0
    • XID为0的事务的状态永远为committed

“ TransactionManager通过维护一个XID格式的文件来维护事务的状态并提供接口查询某个事务的状态。”

  • XID文件的头部保存了一个8字节的数字,记录这个XID文件管理的事务个数
  • XID文件给每个事务分配了一个字节的空间用来保存其状态
  • XID 编号为0的状态不需要记录
  • 主要属性及全局实例定义:
	//XID文件头长度
    private static final int LEN_XID_HEADER_LENGTH = 8;

    //给每个事务分配一个字节的空间用来存储其状态
    private static final int XID_FIELD_SIZE = 1;

    //事务的三种状态
    private static final byte FIELD_TRAN_ACTIVE = 0;
    private static final byte FIELD_TRAN_COMMITTED = 1;
    private static final byte FIELD_TRAN_ABORTED = 2;

    //超级事务,永远为Committed状态
    public static final long SUPER_XID = 0;

    private RandomAccessFile file;
    private FileChannel fc;
    private long xidCounter;
    private Lock counterLock;

构造方法

通过属性也能看出,基本上就是通过FileChannel结合RandomAccessFile的方式来实现的

    private TransactionManager (RandomAccessFile raf,FileChannel fc){
        this.file = raf;
        this.fc = fc;
        counterLock = new ReentrantLock();
        checkXIDCounter();
    }

其中的checkXIDCounter()方法用于检查XID文件是否合乎规定

    /*
    检查XID文件是否合乎规定
    校验:通过文件头的8字节数字反推文件的理论长度,与文件的实际长度做对比
     */
    private void checkXIDCounter() {
        long fileLen = 0;
        try {
            fileLen = file.length();
        }catch (IOException e){
            Panic.panic(new RuntimeException("非法的XID文件!"));
        }
        if (fileLen < LEN_XID_HEADER_LENGTH) {
            Panic.panic(new RuntimeException("非法的XID文件!"));
        }

        ByteBuffer buf =  ByteBuffer.allocate(LEN_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);
        //读取XID_FILE_HEADER中的xidCounter,根据它计算文件的理论长度,对比实际长度
        if ( end != fileLen ) {
            Panic.panic(new RuntimeException("非法的XID文件!"));
        }
    }
  • getXIDPosition()方法
    //根据事务xid取得其在XID文件中对应的位置
    private long getXIDPosition(long xid){
        return LEN_XID_HEADER_LENGTH + (xid-1)*XID_FIELD_SIZE;
    }

创建方式

    public static TransactionManager create(String path){
        File f = new File(path);
        try {
            if (!f.createNewFile()){
                Panic.panic(new RuntimeException("File already exists!"));
            }
        } catch (Exception e) {
            Panic.panic(e);
        }
        if (!f.canRead() || !f.canWrite()){
            Panic.panic(new RuntimeException("File can not Read or Write!"));
        }

        FileChannel fc = null;
        RandomAccessFile raf = null;
        try {
            raf = new RandomAccessFile(f,"rw");
            fc = raf.getChannel();		//通过RandomAccessFile获取FileChannel的实例
        } catch (FileNotFoundException e){
            Panic.panic(e);
        }

        return new TransactionManager(raf,fc);
    }

FileChannel1

  • FileChannel使用一个InputStream/OutputStream或RandomAccessFile来获取一个FileChannel实例

    RandomAccessFile raf = new RandomAccessFile("test.txt");
    raf.getChannel();
    
  • Channel的读写方法

    从文件A中读取内容并写入到文件B:

    ByteBuffer buf = ByteBuffer.allocate(1024);
    buf.clear();
    
    channelA.read(buf);
    
    buf.flip();
    
    while(buf.hasRemaining()) {
      channelB.write(buf);
    }
    
  • 用完后关闭Channel

    • public final void close( )
  • 特定位置的读写操作

    long pos = channel.position();
    channel.position(pos + 100);
    

    将位置设在文件符结束之后

    • 然后试图从文件通道中读取数据,读方法将返回-1(代表文件结束)
    • 试图向通道中写数据,文件将撑大到当前位置并写入数据,这可能导致“文件空洞”,磁盘上的物理文件中写入的数据间有空隙,撑大的那一部分将以0填充,但却不占用实际的磁盘空间(了解即可,不建议这种操作)
  • 返回实例所关联文件的大小

    • size( )
  • 截取指定长度的文件,指定长度后面的部分将被删除

    • truncate( 1024 ) //1024临时示意
  • 将通道里尚未写入磁盘的数据强制写到磁盘上

    出于性能方面的考虑,操作系统会将数据缓存到内存中,无法保证写入到Channel里的数据一定会即时写到磁盘上

    • force( boolean )

事务操作的相关方法

  • private void updateXID( long xid, byte status )
    • 更新事务的状态为status
  • private void incrXIDCounter( )
    • 将XID+1,并更新XIDHeader
  • public long begin( )
    • 开始一个事务并返回XID
  • public void commit(long xid)
    • 提交XID事务
  • public void abort(long xid)
    • 回滚XID事务
  • private boolean checkXID(long xid, byte status)
    • 检查XID事务是否处于status状态
  • public boolean isActive( long xid )
    • 事务的状态是否正在进行
  • public boolean isCommitted( long xid )
    • 事务的状态是否已经提交
  • public boolean isAborted( long xid )
    • 事务的状态是否已经取消

关闭流

    public void close() {
        try {
            fc.close();
            file.close();
        } catch (IOException e){
            Panic.panic(e);
        }
    }

公共类

public class Parser {
    public static long parseLong(byte[] buf){
        ByteBuffer buffer = ByteBuffer.wrap(buf,0,8);
        return buffer.getLong();
    }

    public static byte[] long2Byte(long value){
        return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(value).array();
    }
}

工具类

public class Panic {
    public static void panic(Exception err){
        err.printStackTrace();
        System.exit(1);
    }
}