以上图为例,当对0<阅读量<100的文章进行查询时,先查到了一个结果,后来查询到了两个结果。这表明同一个事务的查询结果数不一,行数不一致。这样的问题使得在根据某些条件对数据筛选的时候,前后筛选结果不具有可靠性。
隔离级别
根据上面这三种问题,产生了四种隔离级别,表明数据库不同程度的隔离性质。
在实际的数据库设计中,隔离级别越高,导致数据库的并发效率会越低;而隔离级别太低,又会导致数据库在读写过程中会遇到各种乱七八糟的问题。
因此在大多数数据库系统中,默认的隔离级别时读已提交(如Oracle)或者可重复读RR(MySQL的InnoDB引擎)。
MVCC
又是一个难嚼的大块头。MVCC就是用来实现上面的第三个隔离级别,可重复读RR。
MVCC:Multi-Version Concurrency Control,即多版本的并发控制协议。
MVCC的特点就是在同一时刻,不同事务可以读取到不同版本的数据,从而可以解决脏读和不可重复读的问题。
MVCC实际上就是通过数据的隐藏列和回滚日志(undo log),实现多个版本数据的共存。这样的好处是,使用MVCC进行读数据的时候,不用加锁,从而避免了同时读写的冲突。
在实现MVCC时,每一行的数据中会额外保存几个隐藏的列,比如当前行创建时的版本号和删除时间和指向undo log的回滚指针。这里的版本号并不是实际的时间值,而是系统版本号。每开始新的事务,系统版本号都会自动递增。事务开始时的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。
每个事务又有自己的版本号,这样事务内执行数据操作时,就通过版本号的比较来达到数据版本控制的目的。
另外,InnoDB实现的隔离级别RR时可以避免幻读现象的,这是通过next-key lock机制实现的。
next-key lock实际上就是行锁的一种,只不过它不只是会锁住当前行记录的本身,还会锁定一个范围。比如上面幻读的例子,开始查询0<阅读量<100的文章时,只查到了一个结果。next-key lock会将查询出的这一行进行锁定,同时还会对0<阅读量<100这个范围进行加锁,这实际上是一种间隙锁。间隙锁能够防止其他事务在这个间隙修改或者插入记录。这样一来,就保证了在0<阅读量<100这个间隙中,只存在原来的一行数据,从而避免了幻读。
间隙锁:封锁索引记录中的间隔
虽然InnoDB使用next-key lock能够避免幻读问题,但却并不是真正的可串行化隔离。再来看一个例子吧。
首先提一个问题:
在T6时间,事务A提交事务之后,猜一猜文章A和文章B的阅读量为多少?
答案是,文章AB的阅读量都被修改成了10000。这代表着事务B的提交实际上对事务A的执行产生了影响,表明两个事务之间并不是完全隔离的。虽然能够避免幻读现象,但是却没有达到可串行化的级别。
这还说明,避免脏读、不可重复读和幻读,是达到可串行化的隔离级别的必要不充分条件。可串行化是都能够避免脏读、不可重复读和幻读,但是避免脏读、不可重复读和幻读却不一定达到了可串行化。
一致性
一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。
一致性是事务追求的最终目标,原子性、持久性和隔离性,实际上都是为了保证数据库状态的一致性而存在的。
这就不多说了吧。你细品。
了解完MySQL的基本架构,大体上能够对MySQL的执行流程有了比较清晰的认知。接下来我将为大家介绍一下日志系统。
MySQL日志系统是数据库的重要组件,用于记录数据库的更新和修改。若数据库发生故障,可通过不同日志记录恢复数据库的原来数据。因此实际上日志系统直接决定着MySQL运行的鲁棒性和稳健性。
MySQL的日志有很多种,如二进制日志(binlog)、错误日志、查询日志、慢查询日志等,此外InnoDB存储引擎还提供了两种日志:redo log(重做日志)和undo log(回滚日志)。这里将重点针对InnoDB引擎,对重做日志、回滚日志和二进制日志这三种进行分析。
重做日志(redo log)
重做日志(redo log)是InnoDB引擎层的日志,用来记录事务操作引起数据的变化,记录的是数据页的物理修改。
重做日记的作用其实很好理解,我打个比方。数据库中数据的修改就好比你写的论文,万一哪天论文丢了怎么呢?以防这种不幸的发生,我们可以在写论文的时候,每一次修改都拿个小本本记录一下,记录什么时间对某一页进行了怎么样的修改。这就是重做日志。
InnoDB引擎对数据的更新,是先将更新记录写入redo log日志,然后会在系统空闲的时候或者是按照设定的更新策略再将日志中的内容更新到磁盘之中。这就是所谓的预写式技术(Write Ahead logging)。这种技术可以大大减少IO操作的频率,提升数据刷新的效率。
脏数据刷盘
值得注意的是,redo log日志的大小是固定的,为了能够持续不断的对更新记录进行写入,在redo log日志中设置了两个标志位置,checkpoint和write_pos,分别表示记录擦除的位置和记录写入的位置。redo log日志的数据写入示意图可见下图。