MySQL innodb中的只读事物以及事物id的分配方式

一、只读事物
也许有人要问一个select算不算一个事物。其实在innodb中一个innodb的select是一个事物,他有trx_t结构体,并且放到了mysql_trx_list链表中,关于
innodb事物系统一级的事都做了,但是这种事物叫做只读事物
bool read_only; /*!< true if transaction is flagged
as a READ-ONLY transaction.
if auto_commit && will_lock == 0
then it will be handled as a
AC-NL-RO-SELECT (Auto Commit Non-Locking
Read Only Select). A read only
transaction will not be assigned an
UNDO log. */
在实际的使用中他没有自己的锁结构也没有自己的undo segment,这一点很好理解因为这个操作
始终是非锁定的,至少在innodb一级是这样(lock0lock.cc lock_table 都没调用),但是在MYSQL中,我们会发现实际上select语句也会
获得MDL LOCK。(再次声明这里只是说innodb select没有表级别锁存在,但是MYSQL上层会有MDL LOCK)
对于只读事物源码注释给出的流程如下:
Auto-commit non-locking read-only:
* NOT_STARTED -> ACTIVE -> NOT_STARTED
而我们一般的2pc TRX流程如下:
XA (2PC):
* NOT_STARTED -> ACTIVE -> PREPARED -> COMMITTED -> NOT_STARTED
可以看到他实际上就是没有commit的步骤,没有undo reodo这些当然是不需要的。但是不可否认它是一个事物
另外当需要一个事物的时候在现在innodb版本中调用如下:
trx_allocate_for_mysql --> trx_allocate_for_background --> trx_create_low
这里涉及到一个innodb 事物池的概念,trx_create_low 从事物池中取出一个事物TRX_T结构体指针给调用者
这个步骤完成后事物处于NOT_STARTED阶段,这个时候TRX_T结构各种属性都处于初始化阶段,为什么要说一下
事物池的概念因为后面说事物号分配的时候会用到这个概念。
然后根据调用者的需求适时激活事物。实际上会调用,而调用会通过
trx_start_if_not_started_low->trx_start_low完成,在trx_start_low做好事物结构的准备工作,我们来看一
下关于源码中重点的部分

trx->read_only =

        (trx->api_trx && !trx->read_write)
        || (!trx->ddl && !trx->internal
        && thd_trx_is_read_only(trx->mysql_thd))
        || srv_read_only_mode; //此处获取事物当前是否是只读属性,可以看到他和我们的read_only参数设置事物是ddl事物是否是内部事物有关

    if (!trx->auto_commit) { //是否自动提交否则需要设置will_Lock属性如果时候只读事物未TURE,如果是DML事物为flase
    //这里的auto_commit属性和我们平时设置的参数感觉不是一回事
        ++trx->will_lock;
    } else if (trx->will_lock == 0) {
        trx->read_only = true; //如果不需要will_lock属性它肯定是只读事物
    }
//以上也就说明了只读事物不需要锁结构因为 trx->will_lock = 0(false)
    /* We tend to over assert and that complicates the code somewhat.
    e.g., the transaction state can be set earlier but we are forced to
    set it under the protection of the trx_sys_t::mutex because some
    trx list assertions are triggered unnecessarily. */

    /* By default all transactions are in the read-only list unless they
    are non-locking auto-commit read only transactions or background
    (internal) transactions. Note: Transactions marked explicitly as
    read only can write to temporary tables, we put those on the RO
    list too. */
    //当然如果是非只读事物 我们需要开始分配undo rollback segment了 以及undo segment了
    //并且trx->mysql_thd == 0 表示是否是MYSQL线程建立的innodb事物
    //是否是读写事物这个是由调用者传入只读事物为false,DML事物为true,这里的读写和前面
    //trx->read_only有区别如果是只读事物建立临时表也是读写事物
    //是否是DDL事物 DDL也需要分配undo rollback segment了 以及undo segment
    if (!trx->read_only
    && (trx->mysql_thd == 0 || read_write || trx->ddl)) {

        trx->rsegs.m_redo.rseg = trx_assign_rseg_low(
            srv_undo_logs, srv_undo_tablespaces,
            TRX_RSEG_TYPE_REDO);

        /* Temporary rseg is assigned only if the transaction
        updates a temporary table */

        trx_sys_mutex_enter();

        trx_assign_id_for_rw(trx);//分配事物号
        /*
    (gdb) p trx_sys->max_trx_id
        $21 = 328707
    */

        trx_sys_rw_trx_add(trx); //将入集合

        ut_ad(trx->rsegs.m_redo.rseg != 0
        || srv_read_only_mode
        || srv_force_recovery >= SRV_FORCE_NO_TRX_UNDO);

        UT_LIST_ADD_FIRST(trx_sys->rw_trx_list, trx); //将事物放入rw_trx_list

        ut_d(trx->in_rw_trx_list = true);
#ifdef UNIV_DEBUG
        if (trx->id > trx_sys->rw_max_trx_id) {
            trx_sys->rw_max_trx_id = trx->id;
        }
#endif /* UNIV_DEBUG */

        trx->state = TRX_STATE_ACTIVE; //更改事物的状态为ACTIVE

        ut_ad(trx_sys_validate_trx_list());

        trx_sys_mutex_exit();

    } else {
        trx->id = 0; //任然没有分配事物ID给只读事物

        if (!trx_is_autocommit_non_locking(trx)) { //#define trx_is_autocommit_non_locking(t)    ((t)->auto_commit && (t)->will_lock == 0)

            /* If this is a read-only transaction that is writing
            to a temporary table then it needs a transaction id
            to write to the temporary table. */
            //如果是只读事物并且写入了临时表需要额外操作

            if (read_write) {

                trx_sys_mutex_enter();

                ut_ad(!srv_read_only_mode);

                trx_assign_id_for_rw(trx);

                trx_sys->rw_trx_set.insert(
                    TrxTrack(trx->id, trx));

                trx_sys_mutex_exit();
            }

            trx->state = TRX_STATE_ACTIVE;

        } else {
            ut_ad(!read_write);
            trx->state = TRX_STATE_ACTIVE;
        }
    }

    if (trx->mysql_thd != NULL) {
        trx->start_time = thd_start_time_in_secs(trx->mysql_thd); //开始计时这是系统时间LINUX系统调用time()
    } else {
        trx->start_time = ut_time();
    }

根据上面的注释,我们可以看到只读事物没有分配undo segment也不会分配LOCK锁结构
二、事物ID的分配
也许很多朋友不止我一个人在show engine innodb status的时候会看到如下两种截然不同,相差很大的事物ID
(MYSQL)---TRANSACTION 329759, ACTIVE 10 sec
1 lock struct(s), heap size 1160, 0 row lock(s), undo log entries 1
MySQL thread id 3, OS thread handle 140737154152192, query id 28 localhost root cleaning up
(MYSQL)---TRANSACTION 422212177398528, not started
0 lock struct(s), heap size 1160, 0 row lock(s)

这里事物id 329759和422212177398528相差很大,innodb是怎么分配的呢?
其实这里实际上的事物id只有329759,及trx_t.id,是一个正常的DML事物,而对于 not started状态的事物
以及只读事物,是没有事物id的其实就是0,但是show engine innodb status的时候会调用时候会简单的分配
一个而已。
其实TRX ID的分配也是在trx_start_low中调用(trx_assign_id_for_rw(trx);//分配事物号 )
而对于只读事物并不会分配(trx->id = 0; //任然没有分配事物ID给只读事物)都在上面的代码解释中,
而show engine innodb status的时候对于事物ID为0的事物做如下输出

UNIV_INLINE

trx_id_t
trx_get_id_for_print(
    const trx_t*    trx)
{
    /* Readonly and transactions whose intentions are unknown (whether
    they will eventually do a WRITE) don't have trx_t::id assigned (it is
    0 for those transactions). Transaction IDs in
    innodb_trx.trx_id,
    innodb_locks.lock_id,
    innodb_locks.lock_trx_id,
    innodb_lock_waits.requesting_trx_id,
    innodb_lock_waits.blocking_trx_id should match because those tables
    could be used in an SQL JOIN on those columns. Also trx_t::id is
    printed by SHOW ENGINE INNODB STATUS, and in logs, so we must have the
    same value printed everywhere consistently. */
 
    /* DATA_TRX_ID_LEN is the storage size in bytes. */
    static const trx_id_t    max_trx_id
        = (1ULL << (DATA_TRX_ID_LEN * CHAR_BIT)) - 1;

    ut_ad(trx->id <= max_trx_id);

    return(trx->id != 0
    ? trx->id
    : reinterpret_cast<trx_id_t>(trx) | (max_trx_id + 1));
}

我们从注释也能看出
Readonly and transactions whose intentions are unknown don't have trx_t::id assigned (it is 0 for those transactions)
实际上422212177398528这种id就是这里打印的时候分配的,没有什么实际的意义
这里的max_trx_id是一个常量281474976710655如果trx->id==0会调用
reinterpret_cast(trx) | (max_trx_id + 1)
将指针转换为一个64字节非负整数然后位或上(max_trx_id + 1),如下:
(gdb) p max_trx_id
$19 = 281474976710655
(gdb) p reinterpret_cast(trx)
$20 = 140737200690640
(gdb) p  reinterpret_cast(trx) | (max_trx_id + 1)
$21 = 422212177401296

而对于这里DML的事物号的分配如下:

void

trx_assign_id_for_rw(trx_t* trx)
{
    ut_ad(mutex_own(&trx_sys->mutex));

    trx->id = trx->preallocated_id
        ? trx->preallocated_id : trx_sys_get_new_trx_id();
    //先判断是否是这个事物分配过事物ID,因为从事物池中拿出来
    //很可能以前用过,那么就不需要再次分配了,否则新分配

    if (trx->preallocated_id) { //如果是以前使用过的不一定是最大需要加入到vertor中间
        // Maintain ordering in rw_trx_ids
        trx_sys->rw_trx_ids.insert(
            std::upper_bound(trx_sys->rw_trx_ids.begin(),
                    trx_sys->rw_trx_ids.end(),
                    trx->id), trx->id);
    } else {
        // The id is known to be greatest 新分配的肯定是最大 如果是最大加到某位即可
        trx_sys->rw_trx_ids.push_back(trx->id);
    }
}
这里涉及到事物池。
而对于trx_sys_get_new_trx_id如下:

trx_sys_get_new_trx_id()

/*====================*/
{
    ut_ad(trx_sys_mutex_own());

    /* VERY important: after the database is started, max_trx_id value is
    divisible by TRX_SYS_TRX_ID_WRITE_MARGIN, and the following if
    will evaluate to TRUE when this function is first time called,
    and the value for trx id will be written to disk-based
    Thus trx id values will not overlap when the database is
    repeatedly */

    if (!(trx_sys->max_trx_id % TRX_SYS_TRX_ID_WRITE_MARGIN)) {

        trx_sys_flush_max_trx_id(); //TRX_SYS_TRX_ID_WRITE_MARGIN为256 如果trx_sys->max_trx_id达到256的整数倍需要刷盘
        //到TRX_SYS_TRX_ID_STORE中.
    }

    return(trx_sys->max_trx_id++);//然后自身+1返回
}

如此我们看到DML事物的事物ID是innodb分配的,而只读事物或者not start事物的事物ID是在show engine的时候根据trx_t结构体
所在内存的指针算法出来的,没有实际的意义。

 
三、验证只读事物的存在
对于只读事物我们在show engine innodb 只会打印出not start的事物或者活跃的已经获得了锁结构的事物一般是DML操作
但是可以再innodb_trx中观察到,我这里就简单修改show engine innodb 源码打印输出将只读事物打印出来标记为RO TRX,
并且和innodb_trx对比

下面是我修改后show engine innodb的输出

LIST OF TRANSACTIONS FOR EACH SESSION(1)(CHANGE BY GAOPENG ALL mysql_trx_list and rw_trx_list):
(MYSQL)---TRANSACTION 422212177402680, ACTIVE 3 sec fetching rows
mysql tables in use 1, locked 0
0 lock struct(s), heap size 1160, 0 row lock(s), RO TRX
MySQL thread id 7, OS thread handle 140737153619712, query id 411 localhost root Sending data
select * from test.tuser
这里看到我们的只读事物为RO TRX,lock struct(s)为0,没有undo entries,因为有会打印出来。
再来看看innodb_trx的输出:

mysql> select * from information_schema.innodb_trx \G

*************************** 1. row ***************************
                    trx_id: 422212177402680
                trx_state: RUNNING
              trx_started: 2017-07-19 16:52:53
    trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 0
      trx_mysql_thread_id: 7
                trx_query: select * from test.tuser
      trx_operation_state: fetching rows
        trx_tables_in_use: 1
        trx_tables_locked: 0
          trx_lock_structs: 0
    trx_lock_memory_bytes: 1160
          trx_rows_locked: 0
        trx_rows_modified: 0
  trx_concurrency_tickets: 0
      trx_isolation_level: REPEATABLE READ
        trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 1
trx_autocommit_non_locking: 1
没有问题都能观察到,同样我们也如我们所说只读事物的事物ID是422212177402680,只是TRX_T结构体指针所在位置算出来的
算法在上面。这里注意事物也是有状态标识的比如这里的fetching rows。

四、其他
                                 
其实innodb中的事物比想象的要大很多,一个innodb的ddl是一个事物,一个innodb的select是一个事物,很多内部修改数据字典的操作也是一个事物
当然我们平时做的DML那更是事物了,上面说了只读事物这里简单提一下ddl事物和内部事物。
这里将trx_t结构体重关于他们标志给出来:

innodb的ddl事物:
bool ddl; /*!< true if it is an internal transaction for DDL */
函数调用: trx_start_for_ddl_low-->trx_start_internal_low
可以看到一个ddl既是一个内部事物也是一个ddl事物

innodb的内部事物:
bool internal; /*!< true if it is a system/internal
transaction background task. This
includes DDL transactions too.  Such
transactions are always treated as
read-write. */
函数调用:trx_start_internal_low 典型的innodb修改数据字典就是internal事物

关于只读事物实际上在官方手册也有说明具体在

Optimizing InnoDB Read-Only Transactions

我就不再说明什么了。

相关推荐