一、Mini-Transaction
MySQL
对底层页面中的一次原子访问的过程称之为一个Mini-Transaction
,简称mtr
。一个事务可以包含若干条语句,每一条语句其实是由若干个mtr组成,每一个mtr又可以包含若干条redo日志。
1.1 原子操作
所谓原子操作即要么全部成功,要么全部失败,不存在中间状态。
在事务执行过程中,每条语句作为一个mtr来执行。而在执行语句的过程中产生的redo日志被划分成了若干个不可分割的组
。
如:
- 更新Max Row ID属性时产生的redo日志是不可分割的。
- 向聚簇索引对应B+树的页面中插入一条记录时产生的redo日志是不可分割的。
- 向某个二级索引对应B+树的页面中插入一条记录时产生的redo日志是不可分割的。
以向某个索引对应的B+树插入一条记录为例,在向B+树中插入这条记录之前,需要先定位到这条记录应该被插入到哪个叶子节点代表的数据页中,定位到具体的数据页之后,有两种可能的情况:
- 情况一:该数据页的剩余的空闲空间充足,足够容纳这一条待插入记录,那么事情很简单,直接把记录插入到这个数据页中,记录一条类型为MLOG_COMP_REC_INSERT的redo日志就好了,我们把这种情况称之为乐观插入。假如某个索引对应的B+树长这样:
现在我们要插入一条键值为10的记录,很显然需要被插入到页b中,由于页b现在有足够的空间容纳一条记录,所以直接将该记录插入到页b中就好了,如图所示:
- 情况二:该数据页剩余的空闲空间不足。遇到这种情况要进行所谓的页分裂操作,也就是新建一个叶子节点,然后把原先数据页中的一部分记录复制到这个新的数据页中,然后再把记录插入进去,把这个叶子节点插入到叶子节点链表中,最后还要在内节点中添加一条目录项记录指向这个新创建的页面。很显然,这个过程要对多个页面进行修改,也就意味着会产生多条redo日志,因此将这种情况称之为悲观插入。假如某个索引对应的B+树长这样:
现在如果要插入一条键值为10的记录,很显然需要被插入到页b中,但是从图中也可以看出来,此时页b已经塞满了记录,没有更多的空闲空间来容纳这条新记录了,所以我们需要进行页面的分裂操作,就像这样:
如果作为内节点的页a的剩余空闲空间也不足以容纳增加一条目录项记录,那需要继续做内节点页a的分裂操作,也就意味着会修改更多的页面,从而产生更多的redo日志。
二、mtr源码解析
2.1 prepare_write()
/** Prepare to write the mini-transaction log to the redo log buffer.
@return number of bytes to write in finish_write() */
ulint mtr_t::Command::prepare_write() {
switch (m_impl->m_log_mode) {
case MTR_LOG_SHORT_INSERTS:
ut_ad(0);
/* fall through (write no redo log) */
case MTR_LOG_NO_REDO:
case MTR_LOG_NONE:
ut_ad(m_impl->m_log.size() == 0);
log_mutex_enter();
m_end_lsn = m_start_lsn = log_sys->lsn;
return (0);
case MTR_LOG_ALL:
break;
}
ulint len = m_impl->m_log.size();
ulint n_recs = m_impl->m_n_log_recs;
ut_ad(len > 0);
ut_ad(n_recs > 0);
if (len > log_sys->buf_size / 2) {
// 如果当前日志的大小(len)超过了redo日志缓冲区大小的