diff --git a/src/observer/storage/record/record.h b/src/observer/storage/record/record.h index 98a3269c3b3632d8d65176bd0cd22bff402d7b72..abe126f34889d7217a7af316f17558af5e04d0df 100644 --- a/src/observer/storage/record/record.h +++ b/src/observer/storage/record/record.h @@ -27,34 +27,28 @@ See the Mulan PSL v2 for more details. */ class Field; +/** + * @brief 标识一个记录的位置 + * 一个记录是放在某个文件的某个页面的某个槽位。这里不记录文件信息,记录页面和槽位信息 + */ struct RID { PageNum page_num; // record's page number SlotNum slot_num; // record's slot number - // bool valid; // true means a valid record RID() = default; - RID(const PageNum _page_num, const SlotNum _slot_num) : page_num(_page_num), slot_num(_slot_num) - {} + RID(const PageNum _page_num, const SlotNum _slot_num) : page_num(_page_num), slot_num(_slot_num) {} const std::string to_string() const { std::stringstream ss; - ss << "PageNum:" << page_num << ", SlotNum:" << slot_num; - return ss.str(); } - bool operator==(const RID &other) const - { - return page_num == other.page_num && slot_num == other.slot_num; - } + bool operator==(const RID &other) const { return page_num == other.page_num && slot_num == other.slot_num; } - bool operator!=(const RID &other) const - { - return !(*this == other); - } + bool operator!=(const RID &other) const { return !(*this == other); } static int compare(const RID *rid1, const RID *rid2) { @@ -76,6 +70,11 @@ struct RID static RID rid{0, 0}; return &rid; } + + /** + * @brief 返回一个“最大的”RID + * 我们假设page num和slot num都不会使用对应数值类型的最大值 + */ static RID *max() { static RID rid{std::numeric_limits::max(), std::numeric_limits::max()}; @@ -83,7 +82,13 @@ struct RID } }; -class Record +/** + * @brief 表示一个记录 + * 当前的记录都是连续存放的空间(内存或磁盘上)。 + * 为了提高访问的效率,record通常直接记录指向页面上的内存,但是需要保证访问这种数据时,拿着锁资源。 + * 为了方便,也提供了复制内存的方法。可以参考set_data_owner + */ +class Record { public: Record() = default; @@ -97,9 +102,9 @@ public: Record(const Record &other) { - rid_ = other.rid_; - data_ = other.data_; - len_ = other.len_; + rid_ = other.rid_; + data_ = other.data_; + len_ = other.len_; owner_ = other.owner_; if (other.owner_) { @@ -110,7 +115,7 @@ public: } } - Record &operator =(const Record &other) + Record &operator=(const Record &other) { if (this == &other) { return *this; @@ -124,49 +129,34 @@ public: void set_data(char *data, int len = 0) { this->data_ = data; - this->len_ = len; + this->len_ = len; } void set_data_owner(char *data, int len) { ASSERT(len != 0, "the len of data should not be 0"); this->~Record(); - this->data_ = data; - this->len_ = len; + this->data_ = data; + this->len_ = len; this->owner_ = true; } - - char *data() - { - return this->data_; - } - const char *data() const - { - return this->data_; - } - void set_rid(const RID &rid) - { - this->rid_ = rid; - } + char *data() { return this->data_; } + const char *data() const { return this->data_; } + + void set_rid(const RID &rid) { this->rid_ = rid; } void set_rid(const PageNum page_num, const SlotNum slot_num) { this->rid_.page_num = page_num; this->rid_.slot_num = slot_num; } - RID &rid() - { - return rid_; - } - const RID &rid() const - { - return rid_; - } + RID &rid() { return rid_; } + const RID &rid() const { return rid_; } private: RID rid_; - char *data_ = nullptr; - int len_ = 0; - bool owner_ = false; + char *data_ = nullptr; + int len_ = 0; /// 如果不是record自己来管理内存,这个字段可能是无效的 + bool owner_ = false; /// 表示当前是否由record来管理内存 }; diff --git a/src/observer/storage/record/record_manager.cpp b/src/observer/storage/record/record_manager.cpp index 9f2f206f04c2f83966f721bbccad70d34501dd11..d5cf1465601f894641a0567343d897b646f8ff58 100644 --- a/src/observer/storage/record/record_manager.cpp +++ b/src/observer/storage/record/record_manager.cpp @@ -20,16 +20,19 @@ See the Mulan PSL v2 for more details. */ using namespace common; -int align8(int size) -{ - return size / 8 * 8 + ((size % 8 == 0) ? 0 : 8); -} - -int page_fix_size() -{ - return sizeof(PageHeader); -} - +int align8(int size) { return size / 8 * 8 + ((size % 8 == 0) ? 0 : 8); } + +/** + * @brief 一个页面有一个页头和bitmap加上N个记录组成。这个函数返回一个页面一定会占用的固定大小 + */ +int page_fix_size() { return sizeof(PageHeader); } + +/** + * @brief 计算指定大小的页面,可以容纳多少个记录 + * + * @param page_size 页面的大小 + * @param record_size 记录的大小 + */ int page_record_capacity(int page_size, int record_size) { // (record_capacity * record_size) + record_capacity/8 + 1 <= (page_size - fix_size) @@ -37,34 +40,36 @@ int page_record_capacity(int page_size, int record_size) return (int)((page_size - page_fix_size() - 1) / (record_size + 0.125)); } -int page_bitmap_size(int record_capacity) -{ - return record_capacity / 8 + ((record_capacity % 8 == 0) ? 0 : 1); -} +/** + * @brief bitmap 记录了某个位置是否有有效的记录数据,这里给定记录个数时需要多少字节来存放bitmap数据 + * + * @param record_capacity 想要存放多少记录 + */ +int page_bitmap_size(int record_capacity) { return record_capacity / 8 + ((record_capacity % 8 == 0) ? 0 : 1); } +/** + * @brief 页面头固定信息加上bitmap需要的字节 + * + * @param record_capacity 想要存放多少记录 + */ int page_header_size(int record_capacity) { const int bitmap_size = page_bitmap_size(record_capacity); return align8(page_fix_size() + bitmap_size); } //////////////////////////////////////////////////////////////////////////////// -RecordPageIterator::RecordPageIterator() -{} -RecordPageIterator::~RecordPageIterator() -{} +RecordPageIterator::RecordPageIterator() {} +RecordPageIterator::~RecordPageIterator() {} void RecordPageIterator::init(RecordPageHandler &record_page_handler, SlotNum start_slot_num /*=0*/) { record_page_handler_ = &record_page_handler; - page_num_ = record_page_handler.get_page_num(); + page_num_ = record_page_handler.get_page_num(); bitmap_.init(record_page_handler.bitmap_, record_page_handler.page_header_->record_capacity); next_slot_num_ = bitmap_.next_setted_bit(start_slot_num); } -bool RecordPageIterator::has_next() -{ - return -1 != next_slot_num_; -} +bool RecordPageIterator::has_next() { return -1 != next_slot_num_; } RC RecordPageIterator::next(Record &record) { @@ -79,10 +84,7 @@ RC RecordPageIterator::next(Record &record) //////////////////////////////////////////////////////////////////////////////// -RecordPageHandler::~RecordPageHandler() -{ - cleanup(); -} +RecordPageHandler::~RecordPageHandler() { cleanup(); } RC RecordPageHandler::init(DiskBufferPool &buffer_pool, PageNum page_num, bool readonly) { @@ -102,13 +104,13 @@ RC RecordPageHandler::init(DiskBufferPool &buffer_pool, PageNum page_num, bool r } else { frame_->write_latch(); } - readonly_ = readonly; + readonly_ = readonly; char *data = frame_->data(); disk_buffer_pool_ = &buffer_pool; page_header_ = (PageHeader *)(data); - bitmap_ = data + page_fix_size(); + bitmap_ = data + page_fix_size(); LOG_TRACE("Successfully init page_num %d.", page_num); return ret; } @@ -131,7 +133,7 @@ RC RecordPageHandler::recover_init(DiskBufferPool &buffer_pool, PageNum page_num disk_buffer_pool_ = &buffer_pool; page_header_ = (PageHeader *)(data); - bitmap_ = data + page_fix_size(); + bitmap_ = data + page_fix_size(); buffer_pool.recover_page(page_num); @@ -141,20 +143,20 @@ RC RecordPageHandler::recover_init(DiskBufferPool &buffer_pool, PageNum page_num RC RecordPageHandler::init_empty_page(DiskBufferPool &buffer_pool, PageNum page_num, int record_size) { - RC ret = init(buffer_pool, page_num, false/*readonly*/); + RC ret = init(buffer_pool, page_num, false /*readonly*/); if (ret != RC::SUCCESS) { LOG_ERROR("Failed to init empty page page_num:record_size %d:%d.", page_num, record_size); return ret; } - int page_size = BP_PAGE_DATA_SIZE; - int record_phy_size = align8(record_size); - page_header_->record_num = 0; - page_header_->record_capacity = page_record_capacity(page_size, record_phy_size); - page_header_->record_real_size = record_size; - page_header_->record_size = record_phy_size; + int page_size = BP_PAGE_DATA_SIZE; + int record_phy_size = align8(record_size); + page_header_->record_num = 0; + page_header_->record_capacity = page_record_capacity(page_size, record_phy_size); + page_header_->record_real_size = record_size; + page_header_->record_size = record_phy_size; page_header_->first_record_offset = page_header_size(page_header_->record_capacity); - bitmap_ = frame_->data() + page_fix_size(); + bitmap_ = frame_->data() + page_fix_size(); memset(bitmap_, 0, page_bitmap_size(page_header_->record_capacity)); @@ -192,7 +194,7 @@ RC RecordPageHandler::insert_record(const char *data, RID *rid) // 找到空闲位置 Bitmap bitmap(bitmap_, page_header_->record_capacity); - int index = bitmap.next_unsetted_bit(0); + int index = bitmap.next_unsetted_bit(0); bitmap.set_bit(index); page_header_->record_num++; @@ -288,17 +290,11 @@ PageNum RecordPageHandler::get_page_num() const return frame_->page_num(); } -bool RecordPageHandler::is_full() const -{ - return page_header_->record_num >= page_header_->record_capacity; -} +bool RecordPageHandler::is_full() const { return page_header_->record_num >= page_header_->record_capacity; } //////////////////////////////////////////////////////////////////////////////// -RecordFileHandler::~RecordFileHandler() -{ - this->close(); -} +RecordFileHandler::~RecordFileHandler() { this->close(); } RC RecordFileHandler::init(DiskBufferPool *buffer_pool) { @@ -330,13 +326,16 @@ RC RecordFileHandler::init_free_pages() // NOTE: 由于是初始化时的动作,所以不需要加锁控制并发 RC rc = RC::SUCCESS; + BufferPoolIterator bp_iterator; bp_iterator.init(*disk_buffer_pool_); RecordPageHandler record_page_handler; - PageNum current_page_num = 0; + PageNum current_page_num = 0; + while (bp_iterator.has_next()) { current_page_num = bp_iterator.next(); - rc = record_page_handler.init(*disk_buffer_pool_, current_page_num, true/*readonly*/); + + rc = record_page_handler.init(*disk_buffer_pool_, current_page_num, true /*readonly*/); if (rc != RC::SUCCESS) { LOG_WARN("failed to init record page handler. page num=%d, rc=%d:%s", current_page_num, rc, strrc(rc)); return rc; @@ -354,15 +353,19 @@ RC RecordFileHandler::init_free_pages() RC RecordFileHandler::insert_record(const char *data, int record_size, RID *rid) { RC ret = RC::SUCCESS; - // 找到没有填满的页面 RecordPageHandler record_page_handler; - bool page_found = false; - PageNum current_page_num = 0; + bool page_found = false; + PageNum current_page_num = 0; + + // 当前要访问free_pages对象,所以需要加锁。在非并发编译模式下,不需要考虑这个锁 lock_.lock(); + + // 找到没有填满的页面 while (!free_pages_.empty()) { current_page_num = *free_pages_.begin(); - ret = record_page_handler.init(*disk_buffer_pool_, current_page_num, false/*readonly*/); + + ret = record_page_handler.init(*disk_buffer_pool_, current_page_num, false /*readonly*/); if (ret != RC::SUCCESS) { lock_.unlock(); LOG_WARN("failed to init record page handler. page num=%d, rc=%d:%s", current_page_num, ret, strrc(ret)); @@ -376,7 +379,7 @@ RC RecordFileHandler::insert_record(const char *data, int record_size, RID *rid) record_page_handler.cleanup(); free_pages_.erase(free_pages_.begin()); } - lock_.unlock(); // 如果找到了一个有效的页面,那么此时已经拿到了页面的写锁 + lock_.unlock(); // 如果找到了一个有效的页面,那么此时已经拿到了页面的写锁 // 找不到就分配一个新的页面 if (!page_found) { @@ -387,6 +390,7 @@ RC RecordFileHandler::insert_record(const char *data, int record_size, RID *rid) } current_page_num = frame->page_num(); + ret = record_page_handler.init_empty_page(*disk_buffer_pool_, current_page_num, record_size); if (ret != RC::SUCCESS) { frame->unpin(); @@ -395,7 +399,8 @@ RC RecordFileHandler::insert_record(const char *data, int record_size, RID *rid) return ret; } - frame->unpin(); // frame 在allocate_page的时候,是有一个pin的,在init_empty_page时又会增加一个,所以这里手动释放一个 + // frame 在allocate_page的时候,是有一个pin的,在init_empty_page时又会增加一个,所以这里手动释放一个 + frame->unpin(); // 这里的加锁顺序看起来与上面是相反的,但是不会出现死锁 // 上面的逻辑是先加lock锁,然后加页面写锁,这里是先加上 @@ -413,6 +418,7 @@ RC RecordFileHandler::insert_record(const char *data, int record_size, RID *rid) RC RecordFileHandler::recover_insert_record(const char *data, int record_size, RID *rid) { RC ret = RC::SUCCESS; + RecordPageHandler record_page_handler; ret = record_page_handler.recover_init(*disk_buffer_pool_, rid->page_num); @@ -427,16 +433,24 @@ RC RecordFileHandler::recover_insert_record(const char *data, int record_size, R RC RecordFileHandler::delete_record(const RID *rid) { RC rc = RC::SUCCESS; + RecordPageHandler page_handler; - if ((rc = page_handler.init(*disk_buffer_pool_, rid->page_num, false/*readonly*/)) != RC::SUCCESS) { + if ((rc = page_handler.init(*disk_buffer_pool_, rid->page_num, false /*readonly*/)) != RC::SUCCESS) { LOG_ERROR("Failed to init record page handler.page number=%d. rc=%s", rid->page_num, strrc(rc)); return rc; } + rc = page_handler.delete_record(rid); - page_handler.cleanup(); // 📢 这里注意要清理掉资源,否则会与insert_record中的加锁顺序冲突而可能出现死锁 - if (rc == RC::SUCCESS) { + // 📢 这里注意要清理掉资源,否则会与insert_record中的加锁顺序冲突而可能出现死锁 + // delete record的加锁逻辑是拿到页面锁,删除指定记录,然后加上和释放record manager锁 + // insert record是加上 record manager锁,然后拿到指定页面锁再释放record manager锁 + page_handler.cleanup(); + if (OB_SUCC(rc)) { + // 因为这里已经释放了页面锁,并发时,其它线程可能又把该页面填满了,那就不应该再放入 free_pages_ + // 中。但是这里可以不关心,因为在查找空闲页面时,会自动过滤掉已经满的页面 lock_.lock(); free_pages_.insert(rid->page_num); + LOG_TRACE("add free page %d to free page list", rid->page_num); lock_.unlock(); } return rc; @@ -444,13 +458,13 @@ RC RecordFileHandler::delete_record(const RID *rid) RC RecordFileHandler::get_record(RecordPageHandler &page_handler, const RID *rid, bool readonly, Record *rec) { - RC ret = RC::SUCCESS; if (nullptr == rid || nullptr == rec) { LOG_ERROR("Invalid rid %p or rec %p, one of them is null.", rid, rec); return RC::INVALID_ARGUMENT; } - if ((ret != page_handler.init(*disk_buffer_pool_, rid->page_num, readonly)) != RC::SUCCESS) { + RC ret = page_handler.init(*disk_buffer_pool_, rid->page_num, readonly); + if (OB_FAIL(ret)) { LOG_ERROR("Failed to init record page handler.page number=%d", rid->page_num); return ret; } @@ -460,17 +474,17 @@ RC RecordFileHandler::get_record(RecordPageHandler &page_handler, const RID *rid RC RecordFileHandler::visit_record(const RID &rid, bool readonly, std::function visitor) { - RC rc = RC::SUCCESS; - RecordPageHandler page_handler; - if ((rc != page_handler.init(*disk_buffer_pool_, rid.page_num, readonly)) != RC::SUCCESS) { + + RC rc = page_handler.init(*disk_buffer_pool_, rid.page_num, readonly); + if (OB_FAIL(rc)) { LOG_ERROR("Failed to init record page handler.page number=%d", rid.page_num); return rc; } Record record; rc = page_handler.get_record(&rid, &record); - if (rc != RC::SUCCESS) { + if (OB_FAIL(rc)) { LOG_WARN("failed to get record from record page handle. rid=%s, rc=%s", rid.to_string().c_str(), strrc(rc)); return rc; } @@ -481,23 +495,17 @@ RC RecordFileHandler::visit_record(const RID &rid, bool readonly, std::function< //////////////////////////////////////////////////////////////////////////////// -RecordFileScanner::~RecordFileScanner() -{ - close_scan(); -} +RecordFileScanner::~RecordFileScanner() { close_scan(); } -RC RecordFileScanner::open_scan(Table *table, - DiskBufferPool &buffer_pool, - Trx *trx, - bool readonly, - ConditionFilter *condition_filter) +RC RecordFileScanner::open_scan( + Table *table, DiskBufferPool &buffer_pool, Trx *trx, bool readonly, ConditionFilter *condition_filter) { close_scan(); - table_ = table; + table_ = table; disk_buffer_pool_ = &buffer_pool; - trx_ = trx; - readonly_ = readonly; + trx_ = trx; + readonly_ = readonly; RC rc = bp_iterator_.init(buffer_pool); if (rc != RC::SUCCESS) { @@ -513,54 +521,82 @@ RC RecordFileScanner::open_scan(Table *table, return rc; } +/** + * @brief 从当前位置开始找到下一条有效的记录 + * + * 如果当前页面还有记录没有访问,就遍历当前的页面。 + * 当前页面遍历完了,就遍历下一个页面,然后找到有效的记录 + */ RC RecordFileScanner::fetch_next_record() { RC rc = RC::SUCCESS; if (record_page_iterator_.is_valid()) { + // 当前页面还是有效的,尝试看一下是否有有效记录 rc = fetch_next_record_in_page(); if (rc == RC::SUCCESS || rc != RC::RECORD_EOF) { + // 有有效记录:RC::SUCCESS + // 或者出现了错误,rc != (RC::SUCCESS or RC::RECORD_EOF) + // RECORD_EOF 表示当前页面已经遍历完了 return rc; } } + // 上个页面遍历完了,或者还没有开始遍历某个页面,那么就从一个新的页面开始遍历查找 while (bp_iterator_.has_next()) { PageNum page_num = bp_iterator_.next(); record_page_handler_.cleanup(); rc = record_page_handler_.init(*disk_buffer_pool_, page_num, readonly_); - if (rc != RC::SUCCESS) { - LOG_WARN("failed to init record page handler. rc=%d:%s", rc, strrc(rc)); + if (OB_FAIL(rc)) { + LOG_WARN("failed to init record page handler. page_num=%d, rc=%s", page_num, strrc(rc)); return rc; } record_page_iterator_.init(record_page_handler_); rc = fetch_next_record_in_page(); if (rc == RC::SUCCESS || rc != RC::RECORD_EOF) { + // 有有效记录:RC::SUCCESS + // 或者出现了错误,rc != (RC::SUCCESS or RC::RECORD_EOF) + // RECORD_EOF 表示当前页面已经遍历完了 return rc; } } + + // 所有的页面都遍历完了,没有数据了 next_record_.rid().slot_num = -1; return RC::RECORD_EOF; } +/** + * @brief 遍历当前页面,尝试找到一条有效的记录 + * + * @return RC + */ RC RecordFileScanner::fetch_next_record_in_page() { RC rc = RC::SUCCESS; while (record_page_iterator_.has_next()) { rc = record_page_iterator_.next(next_record_); if (rc != RC::SUCCESS) { + const auto page_num = record_page_handler_.get_page_num(); + LOG_TRACE("failed to get next record from page. page_num=%d, rc=%s", page_num, strrc(rc)); return rc; } + // 如果有过滤条件,就用过滤条件过滤一下 if (condition_filter_ != nullptr && !condition_filter_->filter(next_record_)) { continue; } + // 如果是某个事务上遍历数据,还要看看事务访问是否有冲突 if (trx_ == nullptr) { return rc; } + // 让当前事务探测一下是否访问冲突,或者需要加锁、等锁等操作,由事务自己决定 rc = trx_->visit_record(table_, next_record_, readonly_); if (rc == RC::RECORD_INVISIBLE) { + // 可以参考MvccTrx,表示当前记录不可见 + // 这种模式仅在 readonly 事务下是有效的 continue; } return rc; @@ -585,10 +621,7 @@ RC RecordFileScanner::close_scan() return RC::SUCCESS; } -bool RecordFileScanner::has_next() -{ - return next_record_.rid().slot_num != -1; -} +bool RecordFileScanner::has_next() { return next_record_.rid().slot_num != -1; } RC RecordFileScanner::next(Record &record) { diff --git a/src/observer/storage/record/record_manager.h b/src/observer/storage/record/record_manager.h index 9e2cbb5303fea1ce5b0231b9f0abd9e612808c15..d0a75b088543aef8958a284db2039e366c479713 100644 --- a/src/observer/storage/record/record_manager.h +++ b/src/observer/storage/record/record_manager.h @@ -25,13 +25,39 @@ class RecordPageHandler; class Trx; class Table; +/** + * 这里负责管理在一个文件上表记录(行)的组织/管理 + * + * 表记录管理的内容包括如何在文件上存放、读取、检索。也就是记录的增删改查。 + * 这里的文件都会被拆分成页面,每个页面都有一样的大小。更详细的信息可以参考BufferPool。 + * 按照BufferPool的设计,第一个页面用来存放BufferPool本身的元数据,比如当前文件有多少页面、已经分配了多少页面、 + * 每个页面的分配状态等。所以第一个页面对RecordManager来说没有作用。RecordManager 本身没有再单独拿一个页面 + * 来存放元数据,每一个页面都存放了一个页面头信息,也就是每个页面都有 RecordManager 的元数据信息,可以参考 + * PageHeader,这虽然有点浪费但是做起来简单。 + * + * 对单个页面来说,最开始是一个页头,然后接着就是一行行记录(会对齐)。 + * 如何标识一个记录,或者定位一个记录? + * 使用RID,即record identifier。使用 page num 表示所在的页面,slot num 表示当前在页面中的位置。因为这里的 + * 记录都是定长的,所以根据slot num 可以直接计算出记录的起始位置。 + * 问题1:那么如果记录不是定长的,还能使用 slot num 吗? + * 问题2:如何更有效地存放不定长数据呢? + * 问题3:如果一个页面不能存放一个记录,那么怎么组织记录存放效果更好呢? + * + * 按照上面的描述,这里提供了几个类,分别是: + * - RecordFileHandler:管理整个文件/表的记录增删改查 + * - RecordPageHandler:管理单个页面上记录的增删改查 + * - RecordFileScanner:可以用来遍历整个文件上的所有记录 + * - RecordPageIterator:可以用来遍历指定页面上的所有记录 + * - PageHeader:每个页面上都会记录的页面头信息 + */ + /** * 数据文件,按照页面来组织,每一页都存放一些记录/数据行 * 每一页都有一个这样的页头,虽然看起来浪费,但是现在就简单的这么做 * 从这个页头描述的信息来看,当前仅支持定长行/记录。如果要支持变长记录, * 或者超长(超出一页)的记录,这么做是不合适的。 */ -struct PageHeader +struct PageHeader { int32_t record_num; // 当前页面记录的个数 int32_t record_capacity; // 最大记录个数 @@ -43,51 +69,103 @@ struct PageHeader /** * 遍历一个页面中每条记录的iterator */ -class RecordPageIterator +class RecordPageIterator { public: RecordPageIterator(); ~RecordPageIterator(); + /** + * @brief 初始化一个迭代器 + * + * @param record_page_handler 负责某个页面上记录增删改查的对象 + * @param start_slot_num 从哪个记录开始扫描,默认是0 + */ void init(RecordPageHandler &record_page_handler, SlotNum start_slot_num = 0); bool has_next(); - RC next(Record &record); + RC next(Record &record); - bool is_valid() const - { - return record_page_handler_ != nullptr; - } + bool is_valid() const { return record_page_handler_ != nullptr; } private: RecordPageHandler *record_page_handler_ = nullptr; - PageNum page_num_ = BP_INVALID_PAGE_NUM; - common::Bitmap bitmap_; - SlotNum next_slot_num_ = 0; + PageNum page_num_ = BP_INVALID_PAGE_NUM; + common::Bitmap bitmap_; // bitmap 的相关信息可以参考 RecordPageHandler 的说明 + SlotNum next_slot_num_ = 0; // 当前遍历到了哪一个slot }; /** * 负责处理一个页面中各种操作,比如插入记录、删除记录或者查找记录 + * + * 当前定长记录模式下每个页面的组织大概是这样的: + * |-------------------------------------| + * | PageHeader | record allocate bitmap | + * |-------------------------------------| + * | record1 | record2 | ..... | recordN | + * |-------------------------------------| */ -class RecordPageHandler +class RecordPageHandler { public: RecordPageHandler() = default; ~RecordPageHandler(); + + /** + * @brief 初始化 + * + * @param buffer_pool 关联某个文件时,都通过buffer pool来做读写文件 + * @param page_num 当前处理哪个页面 + * @param readonly 是否只读。在访问页面时,需要对页面加锁 + */ RC init(DiskBufferPool &buffer_pool, PageNum page_num, bool readonly); + + /// 当前所有的recover函数都没有使用,可以忽略 RC recover_init(DiskBufferPool &buffer_pool, PageNum page_num); + + /** + * @brief 对一个新的页面做初始化 + * + * @param buffer_pool 关联某个文件时,都通过buffer pool来做读写文件 + * @param page_num 当前处理哪个页面 + * @param record_size 每个记录的大小 + */ RC init_empty_page(DiskBufferPool &buffer_pool, PageNum page_num, int record_size); + + /** + * @brief 操作结束后做的清理工作,比如释放页面、解锁 + */ RC cleanup(); + /** + * @brief 插入一条记录 + * + * @param data 要插入的记录 + * @param rid 如果插入成功,通过这个参数返回插入的位置 + */ RC insert_record(const char *data, RID *rid); RC recover_insert_record(const char *data, RID *rid); + /** + * @brief 删除指定的记录 + * + * @param rid 要删除的记录标识 + */ RC delete_record(const RID *rid); + /** + * @brief 获取指定位置的记录数据 + * + * @param rid 指定的位置 + * @param rec 返回指定的数据。这里不会将数据复制出来,而是使用指针,所以调用者必须保证数据使用期间受到保护 + */ RC get_record(const RID *rid, Record *rec); PageNum get_page_num() const; + /** + * @brief 当前页面是否已经没有空闲位置插入新的记录 + */ bool is_full() const; protected: @@ -97,30 +175,37 @@ protected: } protected: - DiskBufferPool *disk_buffer_pool_ = nullptr; - bool readonly_ = false; - Frame *frame_ = nullptr; - PageHeader *page_header_ = nullptr; - char *bitmap_ = nullptr; + DiskBufferPool *disk_buffer_pool_ = nullptr; // 当前操作的buffer pool(文件) + bool readonly_ = false; // 当前的操作是否都是只读的 + Frame *frame_ = nullptr; // 当前操作页面关联的frame(frame的更多概念可以参考buffer pool和frame) + PageHeader *page_header_ = nullptr; // 当前页面上页面头 + char *bitmap_ = nullptr; // 当前页面上record分配状态信息bitmap内存起始位置 private: friend class RecordPageIterator; }; -class RecordFileHandler +/** + * @brief 管理整个文件中记录的增删改查 + * 整个文件的组织格式请参考该文件中最前面的注释 + */ +class RecordFileHandler { public: RecordFileHandler() = default; ~RecordFileHandler(); - + + /** + * @brief 初始化 + * + * @param buffer_pool 当前操作的是哪个文件 + */ RC init(DiskBufferPool *buffer_pool); - void close(); /** - * 更新指定文件中的记录,rec指向的记录结构中的rid字段为要更新的记录的标识符, - * pData字段指向新的记录内容 + * @brief 关闭,做一些资源清理的工作 */ - RC update_record(const Record *rec); + void close(); /** * 从指定文件中删除标识符为rid的记录 @@ -135,21 +220,42 @@ public: /** * 获取指定文件中标识符为rid的记录内容到rec指向的记录结构中 + * @param page_handler[in] + * 访问记录时,会拿住一些资源不释放,比如页面锁,使用这个对象保存相关的资源,并在析构时会自动释放 + * @param rid 想要获取的记录ID + * @param readonly 获取的记录是只读的还是需要修改的 + * @param rec[out] 通过这个参数返回获取到的记录 + * @note rec 参数返回的记录并不会复制数据内存。page_handler 对象会拿着相关的资源,比如 pin 住页面和加上页面锁。 + * 如果page_handler 释放了,那也不能再访问rec对象了。 */ RC get_record(RecordPageHandler &page_handler, const RID *rid, bool readonly, Record *rec); + /** + * @brief 与get_record类似,访问某个记录,并提供回调函数来操作相应的记录 + * + * @param rid 想要访问的记录ID + * @param readonly 是否会修改记录 + * @param visitor 访问记录的回调函数 + */ RC visit_record(const RID &rid, bool readonly, std::function visitor); private: + /** + * @brief 初始化记录当前没有填满记录的页面 + */ RC init_free_pages(); private: - DiskBufferPool *disk_buffer_pool_ = nullptr; + DiskBufferPool *disk_buffer_pool_ = nullptr; std::unordered_set free_pages_; // 没有填充满的页面集合 - common::Mutex lock_; // 当编译时增加-DCONCURRENCY=ON 选项时,才会真正的支持并发 + common::Mutex lock_; // 当编译时增加-DCONCURRENCY=ON 选项时,才会真正的支持并发 }; -class RecordFileScanner +/** + * @brief 遍历某个文件中所有记录 + * 遍历所有的页面,同时访问这些页面中所有的记录 + */ +class RecordFileScanner { public: RecordFileScanner() = default; @@ -164,11 +270,7 @@ public: * 删除时也需要遍历找到数据,然后删除,这时就需要加写锁 * @param condition_filter 做一些初步过滤操作 */ - RC open_scan(Table *table, - DiskBufferPool &buffer_pool, - Trx *trx, - bool readonly, - ConditionFilter *condition_filter); + RC open_scan(Table *table, DiskBufferPool &buffer_pool, Trx *trx, bool readonly, ConditionFilter *condition_filter); /** * 关闭一个文件扫描,释放相应的资源 @@ -176,21 +278,21 @@ public: RC close_scan(); bool has_next(); - RC next(Record &record); + RC next(Record &record); private: RC fetch_next_record(); RC fetch_next_record_in_page(); private: - Table * table_ = nullptr; - DiskBufferPool * disk_buffer_pool_ = nullptr; - Trx * trx_ = nullptr; - bool readonly_ = false; // 遍历出来的数据,是否可能对它做修改 - - BufferPoolIterator bp_iterator_; // 遍历buffer pool的所有页面 - ConditionFilter * condition_filter_ = nullptr; // 过滤record - RecordPageHandler record_page_handler_; - RecordPageIterator record_page_iterator_; // 遍历某个页面上的所有record - Record next_record_; + Table *table_ = nullptr; // 当前遍历的是哪张表。这个字段仅供事务函数使用,如果设计合适,可以去掉 + DiskBufferPool *disk_buffer_pool_ = nullptr; // 当前访问的文件 + Trx *trx_ = nullptr; // 当前是哪个事务在遍历 + bool readonly_ = false; // 遍历出来的数据,是否可能对它做修改 + + BufferPoolIterator bp_iterator_; // 遍历buffer pool的所有页面 + ConditionFilter *condition_filter_ = nullptr; // 过滤record + RecordPageHandler record_page_handler_; + RecordPageIterator record_page_iterator_; // 遍历某个页面上的所有record + Record next_record_; };