隔离级别对性能的影响比较:可串行化>可重复读>读已提交>读未提交

隔离级别越高,所需要消耗的MySQL性能越大(如事务并发严重性),为了平衡二者,一般建议设置的隔离级别为可重复读,

MySQL默认的隔离级别也是可重复读。
mysql数据库有4种事物隔离级别:
Read uncommit 读取已提交
Read commit 读取未提交
Repeatable read 可重读
Serializable 可串行化

这四种隔离机制下,mysql在并发情况下的一些操作会有不同的效果。

在只之前先介绍一下mysql在并发情况下出现的一些状况:

1.脏读(Dirty Read):一个事务读到了另一个未提交事务修改过的数据(脏读只在读未提交隔离级别才会出现)

2.幻读(Phantom):一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。(幻读在读未提交、读已提交、可重复读隔离级别都可能会出现)------之所以说这是问题,因为,事务在执行期间看到的数据前后必须是一致的。

3.不可重复读(Non-Repeatable Read):一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。由于事务在执行期间看到的数据前后必须是一致的,而不可重复读不能够保证这一点,所以说“不可重复读”是一个有问题的状(不可重复读在读未提交和读已提交隔离级别都可能会出现)

Mysql的四种隔离级别的实现原理:
在实现上,数据库里面会创建一个视图(针对事物涉及的数据,并不是全部数据),访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动(第一个select)时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
而Serializable 可串行化是锁住整张表,并发性差,一般不会使用。

这种实现机制有它的名称:当前读,快照读,MVCC
当前读 (下面的sql语句就属于当前读):
  select…lock in share mode (共享读锁)
  select…for update
  update , delete , insert

当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。

例如,假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。

当前读的实现方式:next-key锁(行记录锁+Gap间隙锁)

间隙锁:只有在Read Repeatable、Serializable隔离级别才有,就是锁定那些范围空间内的数据,假设锁定id>3的数据,id有3,4,5,那么4,5和后面的数字都会被锁定,像6,7…,为什么要这样?因为如果我们不锁定没有的数据,当加入了新的数据id=6,就会出现幻读,间隙锁避免了幻读。

1. 对主键或唯一索引,如果select查询时where条件全部精确命中(=或者in),这种场景本身就不会出现幻读,所以只会加行记录锁(对一行一行的数据加锁)。

2.没有索引的列,当前读操作时,会加全表gap锁,生产环境要注意。

3.非唯一索引列,如果where条件部分命中(>、<、like等)或者全未命中,则会加附近Gap间隙锁。例如,某表数据如下,非唯一索引2,6,9,9,11,15。如下语句要操作非唯一索引列9的数据,gap锁将会锁定的列是(6,11],该区间内无法插入数据。

快照读
简单的select操作(不包括 select … lock in share mode, select … for update)。

Read Committed隔离级别:每次select都生成一个快照读。

Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。

快照读的实现方式:undolog和MVCC

下图右侧绿色的是数据:一行数据记录,主键ID是10,name=‘Jack’,age=10, 被update更新set为name= ‘Tom’,age=23。
为什么mysql常用的事物隔离级别是REPEATABLE READ
事务会先使用“排他锁”锁定改行,将该行当前的值复制到undolog中,然后再真正地修改当前行的值,最后填写事务的DB_TRX_ID,使用回滚指针DB_ROLL_PTR指向undolog中修改前的行DB_ROW_ID。

DB_TRX_ID: 6字节DB_TRX_ID字段,表示最后更新的事务id(update,delete,insert)。此外,删除操作也属于一种更新操作,删除操作中,行的特殊位被标记为已软删除

DB_ROLL_PTR: 7字节回滚指针,指向前一个版本的undolog记录,组成undo链表。如果更新了行,则撤消日志记录中的用于重建更新行之前的内容所需的信息。
  
DB_ROW_ID: 6字节的DB_ROW_ID字段,包含一个随着新行插入而单调递增的行ID, 当由innodb自动产生聚集索引时,聚集索引会包括这个行ID的值,否则这个行ID不会出现在任何索引中。如果表中没有主键或合适的唯一索引, 也就是无法生成聚簇索引的时候, InnoDB会帮我们自动生成聚集索引, 聚簇索引会使用DB_ROW_ID的值来作为主键; 如果表中有主键或者合适的唯一索引, 那么聚簇索引中也就不会包含 DB_ROW_ID了 。

其它:insert undolog只在事务回滚时需要, 事务提交就可以删掉了。update undolog包括update 和 delete , 回滚和快照读都需要这个undolog。
 
多版本并发控制MVCC:
  当事务1更改该行记录时,事务1 先用排它锁锁住该行记录(当前读,读到最新数据然后独占),复制到undolog,再更新字段, 把自己的事务id填入DB_TRX_ID, 让回滚指针DB_ROLL_PTR 指向undolog中修改前的原数据(开始只有最下面一行记录)
  事务2操作也是一样, 产生了第三行记录。

相关文章: