阅读 143

innodb的mini-transaction

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恢复过程是并发操作的,但也能保证一致。


作者:大远哥
链接:https://juejin.cn/post/7023574554899382279


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐