一 事务提交的整体过程
InnoDB和MySQL Server联合实现了事务的提交全程,相关过程参见表10-2,在这个表里面,从上到下,是一个事务提交过程的栈,上面是栈顶下面是栈底,底层的函数调用了上层的函数。
表10-2 事务提交的相关函数栈表
函数名称与作用 |
函数过程 |
在内存中标识事务完成,然后释放事务锁 |
1. 调用lock_trx_release_locks()释放锁 2. 断言事务的状态是在内存中已经完成(标志是上一步完成设置的) 3. 调用trx_undo_insert_cleanup()释放插入的UNDO日志 4. 生成新的LSN 5. 根据innodb_flush_log_at_trx_commit参数值的情况确定是否调用trx_flush_log_if_needed()刷出日志到物理存储(即是否实现预写日志机制) 6. 如果是回滚操作,则为回滚操作设置一些值,如设置事务的状态为宏TRX_STATE_FORCED_ROLLBACK 7. 重新初始化事务对象的结构体值以备再用 |
trx_commit_low
|
1. Mini-Transaction事务提交 2. 调用trx_commit_in_memory()完成事务在内存中的提交等操作 |
trx_commit |
1. 调用trx_commit_low()完成事务提交 2. 也会被trx_rollback_finish()调用,用于回滚操作 |
trx_commit_for_mysql |
1. 根据事务的状态,执行不同的操作。调用trx_commit是因为事务的状体需要从TRX_STATE_ACTIVE或TRX_STATE_PREPARED转变为TRX_STATE_COMMITTED_IN_MEMORY |
innobase_commit_low |
1. 调用trx_commit_for_mysql()完成事务提交 |
innobase_commit |
1. 调用innobase_commit_low()完成事务提交(设置事务提交标志并释放事务相关的锁) 2. 调用trx_commit_complete_for_mysql()刷出日志 |
|
以上是InnoDB层的事务提交相关代码,从上到下是从栈顶到栈底的主要函数 |
ha_commit_low |
MySQL Server层通过handle接口对底层的存储进行事务管理的操作,通过函数指针ht->commit()调用了InnoDB的innobase_commit()函数 |
TC_LOG_DUMMY::commit |
MySQL层不提供REDO日志服务,但提供了一个伪接口来模拟逻辑上的日志提交操作 |
ha_commit_trans |
MySQL层进行的事务提交 |
trans_commit_stmt 或 trans_commit |
MySQL层进行的事务提交,前者是对单语句事务进行提交,后者是对多语句事务进行提交 |
二 事务提交和日志刷出之间的关系
通常情况下,数据库引擎通过严格两阶段锁(SS2PL,参见第二章)来实现并发控制技术,但为了满足数据的一致性要求,事务的ACID特性中的一致性C要求事务在提交前,要先刷出日志即符合WAL预先日志机制。但是,InnoDB支持事务的提交但却没有遵循WAL预先日志机制,这就可能带来数据不一致的问题。
如下是对innobase_commit()函数的主要流程分析,从这个分析可以看出,事务先被设置的提交标识,然后锁被释放。之后再执行trx_commit_complete_for_mysql()函数完成日志的刷出操作。所以我们说InnoDB不符合WAL预先日志机制。
/*****************************************************************//**
Commits a transaction in an InnoDB database or marks an SQL statement ended.
@return 0 or deadlock error if the transaction was aborted by another higher priority transaction. */
static
int
innobase_commit( //提交一个事务,完成事务提交的标识,然后刷出日志(如此的方式,违反了WAL预写日志的机制)
handlerton* hton, /*!< in: InnoDB handlerton */ //InnoDB引擎的句柄
THD* thd, /*!< in: MySQL thread handle of the user for whom the transaction should be committed */ //用户会话
bool commit_trx) /*!< in: true - commit transaction false - the current SQL statement ended */ //是否提交
{...
if (commit_trx //值为TRUE表示要提交事务
|| (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))) {
...
innobase_commit_low(trx); //调用栈: -> trx_commit_for_mysql() -> trx_commit(trx) -> trx_commit_low() -> trx_commit_in_memory() -> lock_trx_release_locks(),在lock_trx_release_locks()这个函数中执行如下重要代码:
//trx->state = TRX_STATE_COMMITTED_IN_MEMORY; 即在内存中设置事务提交已经完成的标志,本事务的数据即刻被其他事务可见
//... 省略一些代码
//lock_release(trx); 在设置事务提交已经完成的标志后才释放锁。锁在设置提交标志后才释放,符合SS2PL协议
...
/* Now do a write + flush of logs. */
trx_commit_complete_for_mysql(trx); //重要的步骤:刷出日志(刷出日志的过程可参见下一节“日志落盘”中的标题二)
}
} else { //不提交事务
...
}
...
}