MySql|Mysql事务详解-[数据库的隔离级别、脏读、不可重复读、幻读以及ACID性质与redo log与undo log]


Mysql事务详解-[数据库的隔离级别、脏读、不可重复读、幻读以及ACID性质与redo log与undo log]

    • 1.数据库事务
    • 2.事务的性质
    • 3.Mysql Innodb中事务性质的原理保证
      • 1.redo log
      • 2.undo log

1.数据库事务 事务:事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或者撤销操作请求,即这些操作要么同时成功要么同时失败。
2.事务的性质
  • 原子性(Atomicity)
    事务是不可分割的最小操作单元,事务要么全部成功,要么全部失败。
  • 一致性(Consistency)
    事务完成时,必须使所有的数据都保持一致性状态。包括数据的一致性和约束的一致性。比如事务的操作包括将A账户减去100,给B账户增加100,那么事务结束之后,A与B的余额之和应当与事务操作之前一致。
  • 隔离性(Isolation)
    数据库系统提供的隔离机制,保证不同事务之间的操作具有隔离级别下的隔离性,保证事务在不受外部并发操作影响的独立环境下运行。
    数据库的隔离级别包括:Read uncommitedRead commited, Repeatable Read, Serializable四种,Mysql的默认隔离级别是Repeatable Read。数据库的隔离级别用来解决并发事务可能存在的问题。
    并发事务存在的问题包括:脏读、不可重复读、幻读。
    1. 脏读:一个事务读取到了另一个事务未提交的事务。
    2. 不可重复读:在同一个事务中,先后读取同一条记录,但是两次读取的数据不同。
    3. 幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,导致无法插入,再次查询发现其仍未存在,好像出现了幻影。
    值得注意的是:这三种情况都是在并发事务对同一个数据操作情况下,其实和Java多线程对共享变量的读取有点相似。
    前两种问题脏读和不可重复读容易理解。
    幻读具体来讲就是假设存在两个事务A和B,A事务开启准备再student表中插入一条id为3的数据,先执行了select语句,发现student表中没有id为3的数据,此时B事务开启,其向student表中插入了id为3的数据,并进行了提交。此时A事务在发现id为3的数据不存在时,准备插入数据,却发现数据已经存在导致插入失败,在同一个事务中,查询一条数据时发现数据不存在,但是插入时却因为已经存在导致插入失败的情况就是幻读。可能这样介绍还是不能直观感受到幻读。我们知道数据库的隔离级别就是用来解决并发事务所带来的问题的。Read uncommitedRead commited, Repeatable Read此三种隔离级别都未能解决幻读问题。
    我们可以将数据库的隔离级别设置为Repeatable Read,此时上述介绍的幻读例子就变成如下:
    存在两个事务A和B,A事务开启准备再student表中插入一条id为3的数据,先执行了select语句,发现student表中没有id为3的数据,此时B事务开启,其向student表中插入了id为3的数据,并进行了提交。此时A事务在发现id为3的数据不存在时,准备插入数据,却发现数据已经存在导致插入失败,但是此时继续进行查询,发现仍然查询不到该条数据。这样在查询的时候查询不到,但是插入的时候却因为已经存在导致插入错误的情况就是幻读。那么为什么,第二次select的时候已经读取不到呢?是因为在可重复读隔离界别下,select语句在该条件下的数据,只要A事务未对其进行修改,那么多次进行select的结果就是相同的。
    事务隔离级别解决并发事务操作问题的能力如下所示:
    MySql|Mysql事务详解-[数据库的隔离级别、脏读、不可重复读、幻读以及ACID性质与redo log与undo log]
    文章图片

    注:虽然Mysql的默认隔离级别是Repeatable read, 但是其利用next-key lock机制解决了幻读问题,达到了Serializable隔离级别的安全性。
    隔离级别越高,则安全性越高,但是性能必定会受到影响。
  • 持久性(Durability)
    事务一旦提交和回滚,它对数据库中的数据的改变就是永久的。
3.Mysql Innodb中事务性质的原理保证 这四大特性,可以分为两个部分,其中原子性、一致性、持久性是由InnoDB中的两份日志来保证的,一份是redo log,一份是undo log。redo log保证了持久性,undo log保证了原子性,持久性、原子性、隔离性保证了一致性。而隔离性则是由数据库的锁和MVCC(多版本并发控制)来保证的。
1.redo log
redo log 重做日志,记录的是事务提交数据页的物理修改,用来实现事务的持久性。
【MySql|Mysql事务详解-[数据库的隔离级别、脏读、不可重复读、幻读以及ACID性质与redo log与undo log]】该日志文件由两部分组成:重做日志缓冲(redo log buffer)和重做日志文件(redo log file),前者是在内存中,后者在磁盘中。当事务提交之后会把所有信息都存到日志文件中,用于刷新脏页到磁盘,发生错误时,进行数据恢复使用。
MySql|Mysql事务详解-[数据库的隔离级别、脏读、不可重复读、幻读以及ACID性质与redo log与undo log]
文章图片

InnoDB引擎中的内存结构中,主要的内存区域就是缓冲池,在缓冲池中缓存了很多的数据页。当我们在一个事务中,执行多个增删改的操作时,InnoDB引擎会先操作缓冲池中的数据,如果缓冲区没有对应的数据,会通过后台线程将磁盘中的数据加载出来,存放在缓冲区中,然后将缓冲池中的数据修改,修改后的数据页我们称为脏页。而脏页则会在一定的时机,通过后台线程刷新到磁盘中,从而保证缓冲区与磁盘的数据一致。而缓冲区的脏页数据并不是实时刷新的,而是一段时间之后将缓冲区的数据刷新到磁盘中,假如刷新到磁盘的过程出错了,而提示给用户事务提交成功,而数据却没有持久化下来,这就出现问题了,没有保证事务的持久性。
**在InnoDB中提供了一份日志redolog来解决该问题。**有了redolog之后,当对缓冲区的数据进行增删改之后,会首先将操作的数据页的变化,记录在redo log buffer中。在事务提交时,会先将redo log buffer中的数据刷新到redo log磁盘文件中。过一段时间之后,如果刷新缓冲区的脏页到磁盘时,发生错误,此时就可以借助于redo log进行数据恢复,这样就保证了事务的持久性。而如果脏页成功刷新到磁盘或或者涉及到的数据已经落盘,此时redo log就没有作用了,就可以删除了,所以存在的两个redo log文件是循环写的。
那为什么每一次提交事务,要刷新redo log到磁盘中呢,而不是直接将buffer pool中的脏页刷新到磁盘呢?因为在业务操作中,我们操作数据一般都是随机读写磁盘的,而不是顺序读写磁盘。而redo log在往磁盘文件中写入数据,由于是日志文件,所以都是顺序写的。顺序写的效率,要远大于随机写。这种先写日志的方式,称之为WAL(Write-Ahead Logging)
2.undo log
回滚日志,用于记录数据被修改前的信息,作用包含两个:提供回滚(保证事务的原子性)和MVCC(多版本并发控制)。
undo logredo log记录物理日志(记录的是数据的变化)不一样,它是逻辑日志(记录的是操作)。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。当执行rollback时,就可以从undolog中的逻辑记录读取到相应的内容并进行回滚。
Undolog销毁:undolog在事务执行时产生,事务提交时,并不会立即删除undolog,因为这些日志可能还用于MVCC。
MVCC 多版本并发控制介绍可以参考博文:
数据库InnoDB-MVCC-多版本并发控制

    推荐阅读