record_manager.h 11.6 KB
Newer Older
羽飞's avatar
羽飞 已提交
1 2 3 4 5 6 7 8 9 10 11
/* Copyright (c) 2021 Xie Meiyi(xiemeiyi@hust.edu.cn) and OceanBase and/or its affiliates. All rights reserved.
miniob is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
         http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details. */

//
12
// Created by Meiyi & Longda on 2021/4/13.
羽飞's avatar
羽飞 已提交
13
//
羽飞's avatar
羽飞 已提交
14
#pragma once
羽飞's avatar
羽飞 已提交
15

L
Longda 已提交
16
#include <sstream>
羽飞's avatar
羽飞 已提交
17
#include <limits>
羽飞's avatar
羽飞 已提交
18
#include "storage/default/disk_buffer_pool.h"
羽飞's avatar
羽飞 已提交
19
#include "storage/trx/latch_memo.h"
羽飞's avatar
羽飞 已提交
20
#include "storage/record/record.h"
羽飞's avatar
羽飞 已提交
21
#include "common/lang/bitmap.h"
羽飞's avatar
羽飞 已提交
22 23

class ConditionFilter;
24
class RecordPageHandler;
羽飞's avatar
羽飞 已提交
25 26
class Trx;
class Table;
羽飞's avatar
羽飞 已提交
27

羽飞's avatar
羽飞 已提交
28
/**
29 30 31 32
 * @defgroup RecordManager
 * @file record_manager.h
 * 
 * @brief 这里负责管理在一个文件上表记录(行)的组织/管理
羽飞's avatar
羽飞 已提交
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
 *
 * 表记录管理的内容包括如何在文件上存放、读取、检索。也就是记录的增删改查。
 * 这里的文件都会被拆分成页面,每个页面都有一样的大小。更详细的信息可以参考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:每个页面上都会记录的页面头信息
 */

57 58 59 60 61 62
/**
 * 数据文件,按照页面来组织,每一页都存放一些记录/数据行
 * 每一页都有一个这样的页头,虽然看起来浪费,但是现在就简单的这么做
 * 从这个页头描述的信息来看,当前仅支持定长行/记录。如果要支持变长记录,
 * 或者超长(超出一页)的记录,这么做是不合适的。
 */
羽飞's avatar
羽飞 已提交
63
struct PageHeader
64
{
65 66
  int32_t record_num;           /// 当前页面记录的个数
  int32_t record_capacity;      /// 最大记录个数
羽飞's avatar
羽飞 已提交
67 68 69
  int32_t record_real_size;     // 每条记录的实际大小
  int32_t record_size;          // 每条记录占用实际空间大小(可能对齐)
  int32_t first_record_offset;  // 第一条记录的偏移量
L
Longda 已提交
70 71
};

72
/**
73 74 75
 * @ingroup RecordManager
 * 
 * @brief 遍历一个页面中每条记录的iterator
76
 */
羽飞's avatar
羽飞 已提交
77
class RecordPageIterator
78
{
羽飞's avatar
羽飞 已提交
79 80 81 82
public:
  RecordPageIterator();
  ~RecordPageIterator();

羽飞's avatar
羽飞 已提交
83 84 85 86 87 88
  /**
   * @brief 初始化一个迭代器
   *
   * @param record_page_handler 负责某个页面上记录增删改查的对象
   * @param start_slot_num      从哪个记录开始扫描,默认是0
   */
羽飞's avatar
羽飞 已提交
89
  void init(RecordPageHandler &record_page_handler, SlotNum start_slot_num = 0);
羽飞's avatar
羽飞 已提交
90 91

  bool has_next();
羽飞's avatar
羽飞 已提交
92
  RC   next(Record &record);
羽飞's avatar
羽飞 已提交
93

羽飞's avatar
羽飞 已提交
94
  bool is_valid() const { return record_page_handler_ != nullptr; }
L
Longda Feng 已提交
95

羽飞's avatar
羽飞 已提交
96 97
private:
  RecordPageHandler *record_page_handler_ = nullptr;
羽飞's avatar
羽飞 已提交
98 99 100
  PageNum            page_num_            = BP_INVALID_PAGE_NUM;
  common::Bitmap     bitmap_;             // bitmap 的相关信息可以参考 RecordPageHandler 的说明
  SlotNum            next_slot_num_ = 0;  // 当前遍历到了哪一个slot
羽飞's avatar
羽飞 已提交
101 102
};

103
/**
104
 * @brief 负责处理一个页面中各种操作,比如插入记录、删除记录或者查找记录
羽飞's avatar
羽飞 已提交
105 106 107
 *
 * 当前定长记录模式下每个页面的组织大概是这样的:
 * | PageHeader | record allocate bitmap |
108
 * |------------|------------------------|
羽飞's avatar
羽飞 已提交
109
 * | record1 | record2 | ..... | recordN |
110
 */
羽飞's avatar
羽飞 已提交
111
class RecordPageHandler
112
{
羽飞's avatar
羽飞 已提交
113
public:
羽飞's avatar
羽飞 已提交
114
  RecordPageHandler() = default;
羽飞's avatar
羽飞 已提交
115
  ~RecordPageHandler();
羽飞's avatar
羽飞 已提交
116 117 118 119 120 121 122 123

  /**
   * @brief 初始化
   *
   * @param buffer_pool 关联某个文件时,都通过buffer pool来做读写文件
   * @param page_num    当前处理哪个页面
   * @param readonly    是否只读。在访问页面时,需要对页面加锁
   */
羽飞's avatar
羽飞 已提交
124
  RC init(DiskBufferPool &buffer_pool, PageNum page_num, bool readonly);
羽飞's avatar
羽飞 已提交
125

126 127 128 129 130 131
  /**
   * @brief 数据库恢复时,与普通的运行场景有所不同,不做任何并发操作,也不需要加锁
   * 
   * @param buffer_pool 关联某个文件时,都通过buffer pool来做读写文件
   * @param page_num    操作的页面编号
   */
羽飞's avatar
羽飞 已提交
132
  RC recover_init(DiskBufferPool &buffer_pool, PageNum page_num);
羽飞's avatar
羽飞 已提交
133 134 135 136 137 138 139 140

  /**
   * @brief 对一个新的页面做初始化
   *
   * @param buffer_pool 关联某个文件时,都通过buffer pool来做读写文件
   * @param page_num    当前处理哪个页面
   * @param record_size 每个记录的大小
   */
羽飞's avatar
羽飞 已提交
141
  RC init_empty_page(DiskBufferPool &buffer_pool, PageNum page_num, int record_size);
羽飞's avatar
羽飞 已提交
142 143 144 145

  /**
   * @brief 操作结束后做的清理工作,比如释放页面、解锁
   */
L
Longda 已提交
146
  RC cleanup();
羽飞's avatar
羽飞 已提交
147

羽飞's avatar
羽飞 已提交
148 149 150 151 152 153
  /**
   * @brief 插入一条记录
   *
   * @param data 要插入的记录
   * @param rid  如果插入成功,通过这个参数返回插入的位置
   */
羽飞's avatar
羽飞 已提交
154
  RC insert_record(const char *data, RID *rid);
155 156 157 158 159 160 161 162

  /**
   * @brief 数据库恢复时,在指定位置插入数据
   * 
   * @param data 要插入的数据行
   * @param rid  插入的位置
   */
  RC recover_insert_record(const char *data, const RID &rid);
羽飞's avatar
羽飞 已提交
163

羽飞's avatar
羽飞 已提交
164 165 166 167 168
  /**
   * @brief 删除指定的记录
   *
   * @param rid 要删除的记录标识
   */
羽飞's avatar
羽飞 已提交
169 170
  RC delete_record(const RID *rid);

羽飞's avatar
羽飞 已提交
171 172 173 174 175 176
  /**
   * @brief 获取指定位置的记录数据
   *
   * @param rid 指定的位置
   * @param rec 返回指定的数据。这里不会将数据复制出来,而是使用指针,所以调用者必须保证数据使用期间受到保护
   */
羽飞's avatar
羽飞 已提交
177 178 179 180
  RC get_record(const RID *rid, Record *rec);

  PageNum get_page_num() const;

羽飞's avatar
羽飞 已提交
181 182 183
  /**
   * @brief 当前页面是否已经没有空闲位置插入新的记录
   */
羽飞's avatar
羽飞 已提交
184 185
  bool is_full() const;

L
Longda 已提交
186 187 188
protected:
  char *get_record_data(SlotNum slot_num)
  {
羽飞's avatar
羽飞 已提交
189
    return frame_->data() + page_header_->first_record_offset + (page_header_->record_size * slot_num);
L
Longda 已提交
190 191 192
  }

protected:
193 194 195 196 197
  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内存起始位置
羽飞's avatar
羽飞 已提交
198 199 200

private:
  friend class RecordPageIterator;
羽飞's avatar
羽飞 已提交
201 202
};

羽飞's avatar
羽飞 已提交
203 204 205 206 207
/**
 * @brief 管理整个文件中记录的增删改查
 * 整个文件的组织格式请参考该文件中最前面的注释
 */
class RecordFileHandler
208
{
羽飞's avatar
羽飞 已提交
209
public:
羽飞's avatar
羽飞 已提交
210
  RecordFileHandler() = default;
羽飞's avatar
羽飞 已提交
211
  ~RecordFileHandler();
羽飞's avatar
羽飞 已提交
212 213 214 215 216 217

  /**
   * @brief 初始化
   *
   * @param buffer_pool 当前操作的是哪个文件
   */
羽飞's avatar
羽飞 已提交
218
  RC init(DiskBufferPool *buffer_pool);
羽飞's avatar
羽飞 已提交
219 220

  /**
羽飞's avatar
羽飞 已提交
221
   * @brief 关闭,做一些资源清理的工作
羽飞's avatar
羽飞 已提交
222
   */
羽飞's avatar
羽飞 已提交
223
  void close();
羽飞's avatar
羽飞 已提交
224 225 226 227 228 229 230

  /**
   * 从指定文件中删除标识符为rid的记录
   */
  RC delete_record(const RID *rid);

  /**
羽飞's avatar
羽飞 已提交
231
   * 插入一个新的记录到指定文件中,data为指向新纪录内容的指针,返回该记录的标识符rid
羽飞's avatar
羽飞 已提交
232 233
   */
  RC insert_record(const char *data, int record_size, RID *rid);
234
  RC recover_insert_record(const char *data, int record_size, const RID &rid);
羽飞's avatar
羽飞 已提交
235 236 237

  /**
   * 获取指定文件中标识符为rid的记录内容到rec指向的记录结构中
羽飞's avatar
羽飞 已提交
238 239 240 241 242 243 244
   * @param page_handler[in]
   * 访问记录时,会拿住一些资源不释放,比如页面锁,使用这个对象保存相关的资源,并在析构时会自动释放
   * @param rid 想要获取的记录ID
   * @param readonly 获取的记录是只读的还是需要修改的
   * @param rec[out] 通过这个参数返回获取到的记录
   * @note rec 参数返回的记录并不会复制数据内存。page_handler 对象会拿着相关的资源,比如 pin 住页面和加上页面锁。
   *       如果page_handler 释放了,那也不能再访问rec对象了。
羽飞's avatar
羽飞 已提交
245
   */
羽飞's avatar
羽飞 已提交
246 247
  RC get_record(RecordPageHandler &page_handler, const RID *rid, bool readonly, Record *rec);

羽飞's avatar
羽飞 已提交
248 249 250 251 252 253 254
  /**
   * @brief 与get_record类似,访问某个记录,并提供回调函数来操作相应的记录
   *
   * @param rid 想要访问的记录ID
   * @param readonly 是否会修改记录
   * @param visitor  访问记录的回调函数
   */
羽飞's avatar
羽飞 已提交
255
  RC visit_record(const RID &rid, bool readonly, std::function<void(Record &)> visitor);
羽飞's avatar
羽飞 已提交
256

羽飞's avatar
羽飞 已提交
257
private:
羽飞's avatar
羽飞 已提交
258 259 260
  /**
   * @brief 初始化记录当前没有填满记录的页面
   */
羽飞's avatar
羽飞 已提交
261
  RC init_free_pages();
L
Longda Feng 已提交
262

羽飞's avatar
羽飞 已提交
263
private:
羽飞's avatar
羽飞 已提交
264
  DiskBufferPool             *disk_buffer_pool_ = nullptr;
265 266
  std::unordered_set<PageNum> free_pages_;  /// 没有填充满的页面集合
  common::Mutex               lock_;  /// 当编译时增加-DCONCURRENCY=ON 选项时,才会真正的支持并发
羽飞's avatar
羽飞 已提交
267 268
};

羽飞's avatar
羽飞 已提交
269 270 271 272 273
/**
 * @brief 遍历某个文件中所有记录
 * 遍历所有的页面,同时访问这些页面中所有的记录
 */
class RecordFileScanner
274
{
羽飞's avatar
羽飞 已提交
275
public:
羽飞's avatar
羽飞 已提交
276
  RecordFileScanner() = default;
羽飞's avatar
羽飞 已提交
277
  ~RecordFileScanner();
羽飞's avatar
羽飞 已提交
278 279 280 281

  /**
   * 打开一个文件扫描。
   * 如果条件不为空,则要对每条记录进行条件比较,只有满足所有条件的记录才被返回
羽飞's avatar
羽飞 已提交
282 283 284 285 286
   * @param table        遍历的哪张表
   * @param buffer_pool  访问的文件
   * @param readonly     当前是否只读操作。访问数据时,需要对页面加锁。比如
   *                     删除时也需要遍历找到数据,然后删除,这时就需要加写锁
   * @param condition_filter 做一些初步过滤操作
羽飞's avatar
羽飞 已提交
287
   */
羽飞's avatar
羽飞 已提交
288
  RC open_scan(Table *table, DiskBufferPool &buffer_pool, Trx *trx, bool readonly, ConditionFilter *condition_filter);
羽飞's avatar
羽飞 已提交
289 290 291 292 293 294

  /**
   * 关闭一个文件扫描,释放相应的资源
   */
  RC close_scan();

羽飞's avatar
羽飞 已提交
295
  bool has_next();
羽飞's avatar
羽飞 已提交
296
  RC   next(Record &record);
羽飞's avatar
羽飞 已提交
297

羽飞's avatar
羽飞 已提交
298 299
private:
  RC fetch_next_record();
羽飞's avatar
羽飞 已提交
300
  RC fetch_next_record_in_page();
L
Longda Feng 已提交
301

羽飞's avatar
羽飞 已提交
302
private:
303 304 305 306 307 308 309 310
  // TODO 对于一个纯粹的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
羽飞's avatar
羽飞 已提交
311
  RecordPageHandler  record_page_handler_;
312
  RecordPageIterator record_page_iterator_;  /// 遍历某个页面上的所有record
羽飞's avatar
羽飞 已提交
313
  Record             next_record_;
羽飞's avatar
羽飞 已提交
314
};