MySQL · 引擎特性 · InnoDB 事务系统 (3)

trx_id_t: 每个读写事务都会通过全局id产生器产生一个id,只读事务的事务id为0,只有当其切换为读写事务时候再分配事务id。为了保证在任何情况下(包括数据库不断异常恢复),事务id都不重复,InnoDB的全局id产生器每分配256(TRX_SYS_TRX_ID_WRITE_MARGIN)个事务id,就会把当前的max_trx_id持久化到ibdata的系统页上面。此外,每次数据库重启,都从系统页上读取,然后加上256(TRX_SYS_TRX_ID_WRITE_MARGIN)。

trx_rseg_t: undo segment内存中的结构体。每个undo segment都对应一个。update_undo_list表示已经被分配出去的正在使用的update undo链表,insert_undo_list表示已经被分配出去的正在使用的insert undo链表。update_undo_cached和insert_undo_cached表示缓存起来的undo链表,主要为了快速使用。last_page_no, last_offset, last_trx_no, last_del_marks表示这个undo segment中最后没有被Purge的undolog。

事务的启动

在InnoDB里面有两种事务,一种是读写事务,就是会对数据进行修改的事务,另外一种是只读事务,仅仅对数据进行读取。读写事务需要比只读事务多做以下几点工作:首先,需要分配回滚段,因为会修改数据,就需要找地方把老版本的数据给记录下来,其次,需要通过全局事务id产生器产生一个事务id,最后,把读写事务加入到全局读写事务链表(trx_sys->rw_trx_list),把事务id加入到活跃读写事务数组中(trx_sys->descriptors)。因此,可以看出,读写事务确实需要比只读事务多做不少工作,在使用数据库的时候尽可能把事务申明为只读。

start transaction语句启动事务。这种语句和begin work,begin等效。这些语句默认是以只读事务的方式启动。start transaction read only语句启动事务。这种语句就把thd->tx_read_only置为true,后续如果做了DML/DDL等修改数据的语句,会返回错误ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION。start transaction read write语句启动事务。这种语句会把thd->tx_read_only置为true,此外,允许super用户在read_only参数为true的情况下启动读写事务。start transaction with consistent snapshot语句启动事务。这种启动方式还会进入InnoDB层,并开启一个readview。注意,只有在RR隔离级别下,这种操作才有效,否则会报错。

上述的几种启动方式,都会先去检查前一个事务是否已经提交,如果没有则先提交,然后释放MDL锁。此外,除了with consistent snapshot的方式会进入InnoDB层,其他所有的方式都只是在Server层做个标记,没有进入InnoDB做标记,在InnoDB看来所有的事务在启动时候都是只读状态,只有接受到修改数据的SQL后(InnoDB接收到才行。因为在start transaction read only模式下,DML/DDL都被Serve层挡掉了)才调用trx_set_rw_mode函数把只读事务提升为读写事务。

新建一个连接后,在开始第一个事务前,在InnoDB层会调用函数innobase_trx_allocate分配和初始化trx_t对象。默认的隔离级别为REPEATABLE_READ,并且加入到mysql_trx_list中。注意这一步仅仅是初始化trx_t对象,但是真正开始事务的是函数trx_start_low,在trx_start_low中,如果当前的语句只是一条只读语句,则先以只读事务的形式开启事务,否则按照读写事务的形式,这就需要分配事务id,分配回滚段等。

事务的提交

相比于事务的启动,事务的提交就复杂许多。这里只介绍事务在InnoDB层的提交过程,Server层涉及到与Binlog的XA事务暂时不介绍。入口函数为innobase_commit。

函数有一个参数commit_trx来控制是否真的提交,因为每条语句执行结束的时候都会调用这个函数,而不是每条语句执行结束的时候事务都提交。如果这个参数为true,或者配置了autocommit=1, 则进入提交的核心逻辑。否则释放因为auto_inc而造成的表锁,并且记录undo_no(回滚单条语句的时候用到,相关参数innodb_rollback_on_timeout)。
提交的核心逻辑:

依据参数innobase_commit_concurrency来判断是否有过多的线程同时提交,如果太多则等待。

设置事务状态为committing,我们可以在show processlist看到(trx_commit_for_mysql)。

使用全局事务id产生器生成事务no,然后把事务trx_t加入到trx_serial_list。如果当前的undo segment没有设置最后一个未Purge的undo,则用此事务no更新(trx_serialisation_number_get)。

标记undo,如果这个事务只使用了一个undopage且使用量小于四分之三个page,则把这个page标记为(TRX_UNDO_CACHED)。如果不满足且是insert undo则标记为TRX_UNDO_TO_FREE,否则undo为update undo则标记为TRX_UNDO_TO_PURGE。标记为TRX_UNDO_CACHED的undo会被回收,方便下次重新利用(trx_undo_set_state_at_finish)。

把update undo放入所在undo segment的history list,并递增trx_sys->rseg_history_len(这个值是全局的)。同时更新page上的TRX_UNDO_TRX_NO, 如果删除了数据,则重置delete_mark(trx_purge_add_update_undo_to_history)。

把undate undo从update_undo_list中删除,如果被标记为TRX_UNDO_CACHED,则加入到update_undo_cached队列中(trx_undo_update_cleanup)。

在系统页中更新binlog名字和偏移量(trx_write_serialisation_history)。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppsgs.html