Mysql的事务隔离

起源

当多个事务同时执行时,可能会产生脏读、幻读、不可重复读等。为了解决这些问题,所以有了隔离级别。

脏读:事务还没有提交,就被其他事务读到修改值;

幻读:比如你执行update t set field1=newValue where field2=xxxx时,应该是满足field2=xxxx的行都被修改了,结果发现有一行没被改,因为这时候有个事务在插入了一行field2=xxxx field1=otherValue;

不可重复读:是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果 只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。

不可重复读的重点是修改 :
同样的条件,你读取过的数据, 再次读取出来发现值不一样了
幻读的重点在于新增或者删除
同样的条件 ,   第 1 次和第 2 次读出来的记录数不一样

mysql支持多种隔离级别,默认是可重复度

读未提交

其实就是没有隔离。未提交读隔离级别是最低的隔离级别,该级别不涉及到任何数据库锁,会产生脏读的情况,脏读是指如果一个事务的更改还未提交,其他数据库的会话就读了该更改数据。

读提交

读提交是oracle的默认隔离级别,所以如果做数据迁移,需要注意隔离级别要保持一致。当一个事务所作修改还没提交时,这些修改对其他事务是不可见的。

可重复读

Mysql的默认隔离级别。可重复读指的是在一个事务开启后,可能会对某一个变量重复读,那么多次读写的值是不变的。

串行化

最严格的隔离级别,用读写锁实现,一个事务对某些行进行读写会阻塞其他事务的读写。

 

事务隔离的实现

想知道事务隔离是如何实现的,先要了解视图(ReadView)的概念。

ReadView是某一个时间点,事务执行状态的一个快照(snapshot),可以用来判断事务的可见性。ReadView的基本结构如下:

ReadView {
    creator_trx_id
    low_limit_id
    up_limit_id
    ids
    ...
}

creator_trx_id 创建这个ReadView的事务ID,也就是本事务ID

low_limit_id 所有事务ID大于或等于low_limit_id对当前事务都不可见

up_limit_id 所有事务ID严格小于up_limit_id的事务对当前事务可见 

ids 未提交的事务ID列表

可见性的判断

 事务通过当前事务(或SQL语句,取决于隔离级别)的ReadView来判断一个事务id的操作是否对当前事务可见。

IsVisible(trx_id)
    if (trx_id == creator_trx_id)     // 当前事务
        return true;
    else if (trx_id < up_limit_id)    // ReadView创建时, 事务已提交
        return true;
    else if (trx_id >= low_limit_id)  // ReadView创建时,事务还未被创建
        return false;
    else if (trx_id is in m_ids)  // ReadView创建时,事务正在执行,但未提交
        return false
    else                          // ReadView创建时, 事务已提交
        return true;

 

以可重复读为例,了解一下隔离实现。在mysql中,update操作都会记录一条回滚操作。DB记录好最新的值,然后通过回滚操作,得到某个视图想要的版本的信息。

比如数据从1被改为2,3,4。那么回滚日志里就会记录下如下信息。

 

 

 当前值是4,其实在记录4这条信息的时候,还会记录不同回滚日志,不同的时刻启动的事务会有不同的read view就被以日志形式记录起来了。同一个信息在系统中可以存在多个版本,这就是多版本并发控制(MVCC)。(其实我暂时也不太懂mvcc,后续继续学习在补充吧。)这时候又有一个事务将4改成5,但是和read view A, B, C是不冲突的。

可见,在可重复读的情况下,读不加锁,只有写才加锁,读写互不阻塞,并发度相对于可串行化级别要高,但会有Write Skew异常。

事务在开始时创建一个ReadView,当读一条记录时,会遍历版本链表,通过当前事务的ReadView判断可见性,找到第一个对当前事务可见的版本,读这个版本。

那么回滚日志什么时候删除呢,总不能一直存着吧。当不再用到这个视图时会被删除。系统会判断是否还需要这条回滚日志的。

什么时候才不需要了呢?就是当系统里没有比这个回滚日志更早的read view的时候。

 

比如可重复读,因为我的事务还没结束,所以我的readview是不会更新的,readview结构体也不会变化,所以别人始终不可见,别人也就是怎么读都是一个值,我怎么读别人也是一个值(我也只是一个事务啊,事务id没有变化)。但是read-commited,由于每句sql开始都会创建一个readview,所以,事务执行期间,一旦有其他的事务A提交了A的修改的行,等我下一句sql开始时就能感知到了(虽然我的事务id没有变,但是我的read view刷新了,结构体里的数据变了),不用等到下次事务创建时才能见到。

 

posted @ 2019-09-18 15:55  guhowo  阅读(230)  评论(0编辑  收藏  举报