mysql memo_MySQL redo

标签:MySQL

标签:MySQL结构

标签:MySQL redo

Redo log 相关结构和记入过程:

1. log_sys对象

(1). log_sys作用

log_sys是redo日志系统的关键全局变量。负责三项事情:

log record从mtr_buf复制到logbuffer:

用户线程mtr_commit时,将mtr_buf中的redo record,拷贝到log buffer中;

log record从log buffer复制到logfile:write_mutex控制logbuffer顺序的刷盘;

dirty page从buffer pool刷写到datafile:log_flush_order_mutex控制flushlist的顺序刷盘;执行CHECKPOINT或preflush。

其中,三个锁分别控制

log_t互斥访问(mutex)、

日志刷盘(write_mutex)、

数据刷盘(log_flush_order_mutex)的操作。

(2). 与日志组、日志相关变量

ulint n_files:Ib_logfile的文件个数。

lsn_t file_size:文件大小。

ulint space_id:Redo log 的space id, 固定大小,值为SRV_LOG_SPACE_FIRST_ID。

ulint state:LOG_GROUP_OK 或者 LOG_GROUP_CORRUPTED。

lsn_t lsn:该group内写到的lsn。

lsn_t lsn_offset:上述lsn对应的文件偏移量。在log_group_t里的lsn和lsn_offset字段已经记录了某个日志lsn和其存放在文件内的偏移量之间的对应关系。

计算时需要结合LOG_FILE_START_LSN,判断是否跨了logfile文件。

byte** file_header_bufs:

Buffer区域,用于设定日志文件头信息,并写入ib logfile。当切换到新的ib_logfile时,更新该文件的起始lsn,写入头部。

头部信息还包含: LOG_GROUP_ID, LOG_FILE_START_LSN(当前文件起始lsn)、LOG_FILE_WAS_CREATED_BY_HOT_BACKUP(函数log_group_file_header_flush)。

lsn_t scanned_lsn:用于崩溃恢复时辅助记录扫描到的lsn号。

byte* checkpoint_buf:

Checkpoint缓冲区,用于向日志文件写入checkpoint信息(下文详细描述)。

只有做完checkpoint后,其之前的日志才可以不再保留,否则系统崩溃时则无法恢复。在系统崩溃后的恢复,需要从checkpoint点开始。

但我们需要把checkpoint的相关信息持久化的保存下来,才能在系统崩溃时不会丢失这些检查点相关的信息。Checkpoint相关的信息只存放在ib _logfile0中。

lsn_t write_lsn:最近一次完成写入到文件的LSN。

lsn_t current_flush_lsn:当前正在fsync到的LSN。

lsn_t flushed_to_disk_lsn:最近一次完成fsync到文件的LSN。

innodb_log_files_in_group=4

innodb_log_file_size=4G

总文件大小: 17179869184

log_sys->max_modified_age_async = 12175607164 (71%)

log_sys->max_modified_age_sync = 13045293390 (76%)

log_sys->max_checkpoint_age_async = 13480136503 (78%)

log_sys->max_checkpoint_age = 13914979615 (81%)

当当前未刷脏的最老lsn和当前lsn的距离超过max_modified_age_async(71%)时,且开启了选项innodb_adaptive_flushing时,page cleaner线程会去尝试做更多的dirty page flush工作,避免脏页堆积。

当当前未刷脏的最老lsn和当前Lsn的距离超过max_modified_age_sync(76%)时,用户线程需要去做同步刷脏,这是一个性能下降的临界点,会极大的影响整体吞吐量和响应时间。

当上次checkpoint的lsn和当前lsn超过max_checkpoint_age(81%),用户线程需要同步地做一次checkpoint,需要等待checkpoint写入完成。

当上次checkpoint的lsn和当前lsn的距离超过max_checkpoint_age_async(78%)但小于max_checkpoint_age(81%)时,用户线程做一次异步checkpoint(后台异步线程执行CHECKPOINT信息写入操作),无需等待checkpoint完成。

(3). 与redo log 内存缓冲区相关的成员变量:

变量名描述

ulint buf_size:Log buffer 大小,受参数innodb_log_buffer_size控制,但可能会自动extend。

byte* buf:Log buffer起始位置指针。

ulint buf_free:Log buffer中当前空闲可写的位置。redo record可拷贝写入的buffer的位置(buf_free)。

ulint max_buf_free:值为log_sys->buf_size / LOG_BUF_FLUSH_RATIO - LOG_BUF_FLUSH_MARGIN.

其中: LOG_BUF_FLUSH_RATIO=2, LOG_BUF_FLUSH_MARGIN=(4 * 512 + 4* page_size),page_size默认为16k.

当buf_free超过该值时,可能触发用户线程去写redo;在事务拷redo record到buffer后,也会判断该值,如果超过buf_free,设置log_sys->check_flush_or_checkpoint为true。

ulint buf_next_to_write:Log buffer偏移量,下次写入redo log文件的起始位置,即本次写入的结束位置。redo缓冲区将要向磁盘刷盘的位置(buf_next_to_write)

volatile bool is_extending:Log buffer是否正在进行扩展 (防止过大的redo log entry无法写入buffer), 实际上,当写入的redo log长度超过buf_size/2时,就会去调用函数log_buffer_extend,一旦扩展Buffer,就不会在缩减回去了!

ulint write_end_offset:本次写入的结束位置偏移量(从逻辑来看有点多余,直接用log_sys->buf_free就行了)。

(4). 和Checkpoint检查点相关的成员变量:

变量名描述

ib_uint64_t next_checkpoint_no:每完成一次checkpoint递增该值。

lsn_t last_checkpoint_lsn:最近一次checkpoint时的lsn,每完成一次checkpoint,将next_checkpoint_lsn的值赋给last_checkpoint_lsn。

lsn_t next_checkpoint_lsn:下次checkpoint的lsn(本次发起的checkpoint的lsn)。

mtr_buf_t* append_on_checkpoint:

5.7新增,在做DDL时(例如增删列),会先将包含MLOG_FILE_RENAME2日志记录的buf挂到这个变量上。

在DDL完成后,再清理掉。(log_append_on_checkpoint),主要是防止DDL期间crash产生的数据词典不一致。

ulint n_pending_checkpoint_writes:大于0时,表示有一个checkpoint写入操作正在进行。用户发起checkpoint时,递增该值。后台线程完成checkpoint写入后,递减该值(log_io_complete)

rw_lock_t checkpoint_lock:checkpoint锁,每次写checkpoint信息时需要加x锁。由异步io线程释放该x锁

byte* checkpoint_buf:Checkpoint信息缓冲区,每次checkpoint前,先写该buf,再将buf刷到磁盘。

2. 日志文件头

每个日志文件的前2048字节是存放的文件头信息。

其中几个重要的字段在这里加以说明:

日志文件头共占用4个OS_FILE_LOG_BLOCK_SIZE的大小,即2KB。这里对部分字段做简要介绍:

1) LOG_GROUP_ID               这个log文件所属的日志组,占用4个字节,当前都是0;

2) LOG_FILE_START_LSN     这个log文件记录的开始日志的lsn,占用8个字节;

3) LOG_FILE_WAS_CRATED_BY_HOT_BACKUP   备份程序所占用的字节数,共占用32字节;

4) LOG_CHECKPOINT_1/LOG_CHECKPOINT_2   两个记录InnoDB checkpoint信息的字段,分别从文件头的第二个和第四个block开始记录,只使用日志文件组的第一个日志文件。

从地址2KB偏移量开始,其后就是顺序写入的各个日志块(log block)。

55e248fb315ab4944833c533aa80b2f3.png

3. 日志块

日志块结构

所有的redo日志记录是以日志块为单位组织在一起的,日志块的大小为OS_FILE_LOG_BLOCK_SIZE(默认值为512字节),所有的日志记录以日志块为单位顺序写入日志文件。

每一条记录都有自己的LSN(log sequence number, 表示从日志记录创建开始到特定的日志记录已经写入的字节数)。

每个日志块包含一个日志头段(12字节)、一个尾段(4字节),以及一组日志记录(512 – 12 – 4 = 496字节) 。

二、 日志写入简要过程

1. mtr log生成

2. 从mtr_buf到logbuffer

拷贝mtr中的record

unsigned long buf_free:buf中空闲的第一个位置,按照该位置向logbuffer中拷贝record。

max_buf_free:推荐的buf_free的最大值,会判断该值,如果超过max_buf_free,需要刷logbuffer;并设置log_sys->check_flush_or_checkpoint为true。

3. 从log buffer到logfile

日志的刷盘是通过调用void log_write_up_to( lsn_t lsn, bool flush_to_disk),如果flush_to_disk为True,则表示将参数lsn之前日志都write&flush;同时更新相应偏移量。

三、 mtr

1. Mini transaction

Mini transaction(简称mtr)是InnoDB对物理数据文件操作的最小事务单元,用于管理对Page加锁、修改、释放、以及日志提交到公共buffer等工作。

一个mtr操作必须是原子的,一个事务可以包含多个mtr。每个mtr完成后需要将本地产生的日志拷贝到公共缓冲区,将修改的脏页放到flush list上。

2. mtr_t结构

struct mtr_t {

/* mtr_t 内嵌一个结构体Impl. */

struct Impl {

mtr_buf_t m_memo;         /* 一个被加锁的对象以及它加的锁. */

mtr_buf_t m_log;          /* mini-transaction 的 log. */

bool m_made_dirty;        /* 是否修改了Buffer Pool中的Page为脏页. */

bool m_modifications;     /* 是否修改了Buffer Pool中的Page. */

ib_uint32_t m_n_log_recs; /* 该 mini-transaction 包含多少条log. */

mtr_log_t m_log_mode;     /* mini-transaction 的工作模式(MTR_LOG_ALL, MTR_LOG_NO_REDO,MTR_LOG_NONE).

Mtr的工作模式,包括四种:

MTR_LOG_ALL:默认模式,记录所有会修改磁盘数据的操作;

MTR_LOG_NONE:不记录redo,脏页也不放到flush list上;

MTR_LOG_NO_REDO:不记录redo,但脏页放到flush list上;

MTR_LOG_SHORT_INSERTS:插入记录操作REDO,在将记录从一个page拷贝到另外一个新建的page时用到,此时忽略写索引信息到redo log中。*/

mtr_state_t m_state;      /* mini-transaction 的状态: MTR_STATE_INIT, MTR_STATE_ACTIVE, MTR_STATE_COMMITTING, MTR_STATE_COMMITTED. */

}

}

3. mtr_t中成员memo

是个latch持有状态的数组列表,采用的是dyn_array_t的动态内存结构来保存的,每个单元存储的是 mtr_memo_slot_t 。

m_memo中元素是mtr_memo_slot_t, 记录加锁的对象和加锁的类型.

object是latch的对象,可以是rw_lock_t对象,也可以是buf_block_t对象。

/** mini-transaction memo stack slot. */

struct mtr_memo_slot_t {

void *object;  /* 加锁的对象. */

ulint type;    /* 持有的锁类型,W or R. */

};

4. mt_t中的成员log

是也是一个dyn_array_t动态结构的内存,用来保存mtr产生的日志信息。日志的写入是通过mtr0log.h来写入的。

重做日志虽然有很多种类型,但重做的日志格式是统一的,日志格式是有日志头和日志体组成。

日志头信息是由type、space和page no组成,由mlog_write_initial_log_record_fast函数写入到mtr_t的log中的。

log body的数据写入是通过mtr0log.h中的日志写入方法进行写入的,每写入一条操作日志,n_log_recs会加1。

5. mini-transaction 具体流程

mtr_t mtr

mtr.start()

/* ... */

/* 写入数据至 mini-transaction 的 m_log. */

/* ... */

mtr.commit()

(1). mini-transaction 的start()

/** 启动一个 mini-transaction. */

@param synctrue if it is a synchronous mini-transaction

@param read_onlytrue if read only mini-transaction */

void mtr_t::start(bool sync, bool read_only) {

UNIV_MEM_INVALID(this, sizeof(*this));

UNIV_MEM_INVALID(&m_impl, sizeof(m_impl));

m_sync = sync;

m_commit_lsn = 0;

new (&m_impl.m_log) mtr_buf_t();  /* 记录Redo log的mtr本地Buffer. */

new (&m_impl.m_memo) mtr_buf_t();

/* 初始化 mini-transaction 字段. */

m_impl.m_mtr = this;

m_impl.m_log_mode = MTR_LOG_ALL;

m_impl.m_inside_ibuf = false;

m_impl.m_modifications = false;

m_impl.m_made_dirty = false;

m_impl.m_n_log_recs = 0;

m_impl.m_state = MTR_STATE_ACTIVE;

m_impl.m_flush_observer = NULL;

ut_d(m_impl.m_magic_n = MTR_MAGIC_N);

}

(2). 锁的处理

不同的 mini-transaction 如何互斥?

在操作数据前,会根据锁类型,加不同类型的锁,之后将object和锁类型存入m_memo:

mtr_memo_push(mtr, object, type);

commit完成之后调用release_latches(RELEASE_ALL)将数据上的锁释放.

(3). mini-transaction 插入数据

byte *mlog_open(mtr_t *mtr, ulint size): 打开mtr的m_log

mlog_write_initial_log_record_low()函数向m_log中写入type,space id,page no,并增加m_n_log_recs的数量

mtr->get_log()->push()按不同的类型写数据

mlog_close(): 更新m_log中的位置

(4). mini-transaction 的 commit 过程

commit过程将mini-transaction的m_log数据拷贝到Redo Log Buffer中.

将m_state设置为MTR_STATE_COMMITTING后,调用mtr_t::Command::execute()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值