OceanBase的MemTable包含两个部分:索引结构及行操作链。其中,索引结构存储行头信息,采用内存B树实现;行操作链表中存储了不同版本的修改操作,从而支持多版本并发控制。
OceanBase支持多线程并发修改,写操作拆分为两个阶段:
- 预提交(多线程执行):事务执行线程首先锁住待更新数据行,接着,将事务中针对数据行的操作追加到该行的未提交行操作链表中,最后,往提交任务队列中加入一个提交任务。
- 提交(单线程执行):提交线程不断地扫描并取出提交任务队列中的提交任务,将这些任务的操作日志追加到日志缓冲区中。如果日志缓冲区到达一定大小,将日志缓冲区中的数据同步到备机,同时写入主机的磁盘日志文件。操作日志写成功后,将未提交行操作链表中的cell操作追加到已提交行操作链表的末尾,释放锁并回复客户端写操作成功。
如下图所示,MemTable行操作链表包含两个部分:已提交部分和未提交部分。另外,每个事务管理结构记录了当前事务正在操作的数据行的行头,每个数据行的行头包含已提交和未提交行操作链表的头部指针。在预提交阶段,每个事务会将cell操作递加到未提交行操作链表中,并在行头保存未提交行操作链表的头部指针以及锁信息,同时,将行头信息记录到事务管理结构中;在提交阶段,根据事务管理结构中记录的行头信息找到未提交行操作链表,链接到已提交行操作链表的末尾,并释放行头记录的锁。
class ObTransExecutor
{
public:
//处理预提交任务
void handle_trans(void* ptask, void* pdata);
//处理提交任务
void handle_commit(void* ptask, void* pdata);
};
obTransExecutor是UpdateServer读写事务处理的入口类,它主要包含两个方法:handle_trans以及handle_commit。其中,handle_trans处理预提交任务,handle_commit处理提交任务。handle_trans首先将写事务预提交到MemTable中,接着将写事务加入提交任务队列。提交线程不断地从提交任务队列中取出提交任务,并调用handle_commit进行处理。每个写事务会根据提交时的系统时间生成一个事务版本,读事务只会读取在它之前提交的写事务的修改操作。
如下图所示,对主键为1的商品有2个写事务,事务T1(提交版本号为
2)将商品购买人数修改为100,事务T2(提交版本号为4)将商品购买人数修改为50。那么,事务T2预提交时,T1已经提交,该商品的已提交行操作链包含一个cell:(update,购买人数,100),未提交操作链包含一个cell:(update,购买人数,50)。事务T2成功提交后,该商品的已提交行操作链将包含两个cell:(update,购买人数,100)以及(update,购买人数,50),未提交行操作链为空。对于只读事务:
- T3:事务版本号为1,T1和T2均未提交,该行数据为空。
- T4:事务版本号为3,T1已提交,T2未提交,读取到(update,购买人数,100)。尽管T2在T4执行过程中将购买人数修改为50,T4第二次读取时会过滤掉T2的修改操作,因而两次读取将得到相同的结果。
- T5:事务版本号为5,T1和T2均已提交,读取到(update,购买人数,10)以及(update,购买人数,50),购买人数最终值为50。