
InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
MySQL 服务端是允许多个客户端连接的,这意味着 MySQL 会出现同时处理多个事务的情况。可能会出现以下问题。
如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。
在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况(中途发生过修改),就意味着发生了「不可重复读」现象。
当同一个查询在不同的时间产生不同的结果集时,事务中就会出现所谓的幻象问题。例如,如果 SELECT 执行了两次,但第二次返回了第一次没有返回的行,则该行是“幻像”行。
严重性上脏读 > 不可重复读 > 幻读。
SQL 标准提出了四种隔离级别来规避这些现象,隔离级别越高,性能效率就越低,安全性越高。这四个隔离级别如下:
MySQL 在「可重复读」隔离级别下,可以很大程度上避免幻读现象的发生(注意是很大程度避免,并不是彻底避免),所以 MySQL 并不会使用「串行化」隔离级别来避免幻读现象的发生,因为使用「串行化」隔离级别会影响性能。
四种隔离级别具体是如何实现的呢?
注意,执行「开始事务」命令,并不意味着启动了事务。在 MySQL 有两种开启事务的命令,分别是:
这两种开启事务的命令,事务的启动时机是不同的:
上述图为Read view的四个字段,以及聚簇索引记录的两个与事务有关的隐藏列。
一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况:
min_trx_id
值,表示这个版本的记录是在创建 Read View 前已经提交的事务生成的,所以该版本的记录对当前事务可见。max_trx_id
值,表示这个版本的记录是在创建 Read View 后才启动的事务生成的,所以该版本的记录对当前事务不可见。min_trx_id
和 max_trx_id
之间,需要判断 trx_id 是否在 m_ids 列表中:
m_ids
列表中,表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见。m_ids
列表中,表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见。这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)。
可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。
实际上判断记录的trx_id是否比min_trx_id,小的话是对当前事务可见的,否则如果是在创建当前read view之后创建的,不可见。
如果在min和max之间,对于可重复度也没有什么意义,查询是基于初始创建的read view。
读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。
读取到已经修改但是未提交的记录时,通过判断是否在m_ids之间来看,如果未提交就不会读取这个版本的记录。而是,沿着 undo log 链条往下找旧版本的记录,直到找到 trx_id 「小于」事务 B 的 Read View 中的 min_trx_id 值的第一条记录。