1.概述
由前两篇文章可知重做日志的实现是往磁盘页顺序写物理逻辑日志,如果数据库异常宕机,启动后扫描重做日志并进行恢复可保证数据不丢失,但是我们忽略了一点就是数据一致性问题,如何保证单页数据的一致性其实就是我们本文关注的内容。
2.详细介绍
mini-transaction和我们理解的数据库事务不是一个东西。从一致性来讲,数据库事务是保证多条语句操作的一致性,往往涉及到多个页的修改。而mini-transaction是单页数据一致性,是避免内存页的并发更新影响。当然数据库事务一致性实现也是建立在mini-transaction的基础上的。
所有对页的操作都要在mini_transaction中执行。
在一个mini-transaction操作中,需要对对应的page加锁。锁中代码逻辑主要就是操作页,然后生成redo和undolog,完成之后释放锁。
mini_transaction(){
Lock page
Transform page
Generate undo and redo log
Unlock page
}
复制代码
为什么这样能保证一致性?
1.对页加锁,能保证这个内存页并发修改的安全性。
2.在锁中append log也能保证log的顺序性(从前面的文章可知,innodb是通过log保证持久,所有内存页写操作都要写log)
3.在innodb中,每当一个事务提交的时候,所有的mini_transaction产生的log必须持久化(当然可以通过参数配置,但是不丢失数据的场景是如此)。
4.Innodb每个数据页都有一个lsn,对于页修改需要更新lsn,在持久化页时,保证对应lsn的log都被持久化即可。
3.代码实现
mini-transaction数据结构
struct mtr_struct{
ulint state; /* MTR_ACTIVE, MTR_COMMITTING, MTR_COMMITTED */
dyn_array_t memo; /* memo stack for locks etc. */
dyn_array_t log; /* mini-transaction log */
ibool modifications;
/* TRUE if the mtr made modifications to
buffer pool pages */
ulint n_log_recs;
/* count of how many page initial log records
have been written to the mtr log */
ulint log_mode; /* specifies which operations should be
logged; default value MTR_LOG_ALL */
dulint start_lsn;/* start lsn of the possible log entry for
this mtr */
dulint end_lsn;/* end lsn of the possible log entry for
this mtr */
ulint magic_n;
};
复制代码
主要存储了状态,对页写入的相关信息以及日志信息。
memo存储了持有latch的信息,是一个stack。栈存储的为mtr_memo_slot_struct。其实就是实现锁的获取和释放。
typedef struct mtr_memo_slot_struct mtr_memo_slot_t;
struct mtr_memo_slot_struct{
ulint type; /* type of the stored object (MTR_MEMO_S_LOCK, ...) */
void* object; 主要是latch对象 参考rw_lock_t、buf_block_t
};
复制代码
n_log_recs表示修改页的数量,因为一个操作可能会影响多个页,如果涉及多个页的修改,会按顺序对页进行加锁。
上文所说所有操作都要在mini-transaction中执行。其实就是下面的代码逻辑。
mtr_t mtr;
mtr_start(&mtr);
(代码逻辑)
mtr_commit(&mtr);
复制代码
mtr_start
UNIV_INLINE mtr_t* mtr_start(mtr_t* mtr)
{
dyn_array_create(&(mtr->memo));
dyn_array_create(&(mtr->log));
mtr->log_mode = MTR_LOG_ALL;
mtr->modifications = FALSE;
mtr->n_log_recs = 0;
#ifdef UNIV_DEBUG
mtr->state = MTR_ACTIVE;
mtr->magic_n = MTR_MAGIC_N;
#endif
return(mtr);
}
复制代码
这个方法只是对mtr_struct数据结构的初始化。
代码逻辑拿到初始化好的mtr可进行相关操作。对页操作前先获取锁,然后push到mtr。
UNIV_INLINE void mtr_memo_push(
mtr_t* mtr, /* in: mtr */
void* object, /* in: object */
ulint type) /* in: object type: MTR_MEMO_S_LOCK, ... */
{
dyn_array_t* memo;
mtr_memo_slot_t* slot;
ut_ad(object);
ut_ad(type >= MTR_MEMO_PAGE_S_FIX);
ut_ad(type <= MTR_MEMO_X_LOCK);
ut_ad(mtr);
ut_ad(mtr->magic_n == MTR_MAGIC_N);
memo = &(mtr->memo);
slot = dyn_array_push(memo, sizeof(mtr_memo_slot_t));
slot->object = object;
slot->type = type;
}
复制代码
mtr_commit
void mtr_commit(mtr_t* mtr)
{
ut_ad(mtr);
ut_ad(mtr->magic_n == MTR_MAGIC_N);
ut_ad(mtr->state == MTR_ACTIVE);
if (mtr->modifications) {
mtr_log_reserve_and_write(mtr);
}
mtr_memo_pop_all(mtr);
if (mtr->modifications) {
log_release();
}
dyn_array_free(&(mtr->memo));
dyn_array_free(&(mtr->log));
}
复制代码
1.如果modifications为true则将transaction产生的日志写入到redolog buf中。在redo log恢复过程中也要启动事务,但是不需要再写redo log。
写入的时候需要持有log_sys->mutex
2.mtr_memo_pop_all方法调用mtr_memo_slot_release释放所有的latch。
3.释放log_sys->mutex
4.总结
mini-transaction其实是保证单个内存也写操作的一致性。在并发场景下,多线程写内存页,一方面能保证线程安全,另一方面在写安全的基础上保证redolog的顺序性。即使redo log恢复过程是并发操作的,但也能保证一致。
近期评论