【SimpleDB】Part 5 - Persistence to Disk

之前的一通insert、select的操作范围还仅仅只停留在程序运行时的内存中。当结束运行后,我们的insert数据就会丢失。这节就是将我们的数据写到文件中。
在这里插入图片描述
之前已经完成了Core部分,然后是Backend部分。就像sqlite一样,我们将整个数据库保存到文件中。

我们已经将记录serialize到内存页中,为了持久化,我们的思路如下:

  • 将这些内存页写入到文件中
  • 在程序启动时,将内容从文件中读出来。
    方便起见,我们定义(抽象)一个结构pager。get_page: 从pager中可以获得page number x,pager页存储相应的内存页。读取的时候会先查看cache,cache中没有的时候则从磁盘中读取。
    (系统设计两大思想:cache和抽象)
  1. 前置知识:lseek系统调用
    lseek:
    lseek函数lseek用于显式地为一个已打开的文件设置其偏移量
    每个打开的文件都有一个与其相关联的“当前文件偏移量”(current file offset)。它通常是一个非负整数,用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。

    按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。
    off_t lseek(int fd, off_t offset, int whence);//打开一个文件的下一次读写的开始位置
    参数:
    fd 表示要操作的文件描述符

    offset是相对于whence(基准)的偏移量

    whence 可以是SEEK_SET(文件指针开始),SEEK_CUR(文件指针当前位置) ,SEEK_END为文件指针尾

    返回值:文件读写指针距文件开头的字节大小,出错,返回-1

    三个参数:这里我们用第一个

    • SEEK_SET:从文件头部开始偏移offset个字节。

    • SEEK_CUR:从文件当前读写的指针位置开始,增加offset个字节的偏移量。

    • SEEK_END:文件偏移量设置为文件的大小加上偏移量字节。

  2. Pager结构定义

typedef struct{
    
    
    int file_descriptor;
    uint32_t file_length;
    void* pages[MAX_PAGE_NUM];
}Pager;
  1. page页读取
    传入pager和page_num,如果对应的page为空,那么就从文件中加载该页面(lseek确定文件从哪里开始读取,read函数负责读取);否则直接读取。
//返回最后一个page的开始位置
void* get_page(Pager *pager,uint32_t page_num){
    
    
    if(page_num>MAX_PAGE_NUM){
    
    
        printf("Tried to fetch page number out of bounds\n");
        exit(EXIT_FAILURE);
    }

    if(pager->pages[page_num]==NULL){
    
    
        void* page= malloc(PAGE_SIZE);
        uint32_t num_pages=pager->file_length/PAGE_SIZE;

        if(pager->file_length%PAGE_SIZE!=0){
    
    
            num_pages++;
        }
        if(page_num<=num_pages){
    
    
            lseek(pager->file_descriptor,page_num*PAGE_SIZE,SEEK_SET);
            ssize_t read_bytes=read(pager->file_descriptor,page,PAGE_SIZE);
            if(read_bytes==-1){
    
    
                printf("Error reading file\n");
                exit(EXIT_FAILURE);
            }
        }
        pager->pages[page_num]=page;
    }

    return pager->pages[page_num];

}

//获取到一页,再定位行的位置
void* read_slot(Table* table,uint32_t row_num){
    
    
	uint32_t page_num=row_num/ROW_NUM_PER_PAGE;
    void* page=get_page(table->pager,page_num);
    uint32_t row_offset=row_num%ROW_NUM_PER_PAGE;
    uint32_t offset=row_offset*ROW_SIZE;
    return offset+page;
}

  1. 从new_table()到db_open()
    Table* db_open(const char* filename)
    db_open()要求在程序开始运行初始阶段,把文件中的数据读出来
    table中存pager,num_rows
Pager *pager_open(const char * filename){
    
    
    int fd= open(filename,
                 O_RDWR |
                 O_CREAT,
                 S_IWUSR |
                 S_IRUSR
                 );
    if(fd==-1){
    
    
        printf("Unable to open file\n");
        exit(EXIT_FAILURE);
    }

    off_t file_length=lseek(fd,0,SEEK_END);

    Pager *pager=malloc(sizeof(Pager));
    pager->file_descriptor=fd;
    pager->file_length=file_length;
    for(uint32_t i=0;i<MAX_PAGE_NUM;i++){
    
    
        pager->pages[i]=NULL;
    }

    return pager;

}

Table *db_open(const char* filename){
    
    
	// 1. 打开文件,初始化Pager;注意打开table≠读取文件中数据,只有需要数据时候才会将页面加载到内存中
    Pager* pager=pager_open(filename);
    uint32_t row_num=pager->file_length/ROW_SIZE;
  //printf("test:row_num %d\n",pager->file_length);
  //  printf("%d\n",pager->file_length);
  //2. 初始化table
    Table *table=(Table*) malloc(sizeof (table));
    table->row_num=row_num;
    table->pager=pager;
    return table;
}
  1. db_close()
    退出时进行刷盘操作,将内存中的数据写到磁盘中
void pager_flush(Pager* pager,uint32_t page_num,uint32_t size){
    
    
    //printf("test\n");
    if(pager->pages[page_num]==NULL){
    
    
        printf("Tried to flush null page!\n");
        exit(EXIT_FAILURE);
    }

     off_t offset=lseek(pager->file_descriptor,page_num*PAGE_SIZE,SEEK_SET);
    if(offset==-1){
    
    
        printf("Error seeking!\n");
        exit(EXIT_FAILURE);
    }

    int write_bytes= write(pager->file_descriptor,pager->pages[page_num],size);
   // printf("test\n");
   // printf("%d\n",write_bytes);
    if(write_bytes==-1){
    
    
        printf("write failed!\n");
        exit(EXIT_FAILURE);
    }
}

void db_close(Table* table){
    
    
    Pager * pager=table->pager;
    uint32_t row_num=table->row_num;
    uint32_t full_page_num=row_num/ROW_NUM_PER_PAGE;


    for(uint32_t i=0;i<full_page_num;i++){
    
    
        if(pager->pages[i]==NULL){
    
    
            continue;
        }
        pager_flush(pager,i,PAGE_SIZE);//如果内存页有数据,需要刷到盘上
        free(pager->pages[i]);
        pager->pages[i]=NULL;
    }
    uint32_t additional_rows=row_num%ROW_NUM_PER_PAGE;
	//最后一页可能不完整,刷的数据大小是additional_rows*ROW_SIZE
    if(additional_rows!=0){
    
    
        if(pager->pages[full_page_num]!=NULL){
    
    
            pager_flush(pager,full_page_num,additional_rows*ROW_SIZE);
            free(pager->pages[full_page_num]);
            pager->pages[full_page_num]=NULL;
        }
    }

    int close_result= close(pager->file_descriptor);
    if(close_result==-1){
    
    
        printf("Close file failed!\n");
        exit(EXIT_FAILURE);
    }

    for(uint32_t i=0;i<MAX_PAGE_NUM;i++){
    
    
        if(pager->pages[i]){
    
    
            free(pager->pages[i]);
            pager->pages[i]=NULL;
        }
    }

    free(pager);
    free(table);

  1. 至此完成了一个miniminiDB的雏形

猜你喜欢

转载自blog.csdn.net/qq_39679772/article/details/124766719
今日推荐