日志文件 (redo log 和 undo log)
redo log
redo log 叫做重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log), 前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中。
mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到 Boffer Pool (缓冲池) 里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。
那么问题来了,如果还没来的同步的时候宕机或断电了怎么办?还没来得及执行上面图中红色的操作。这样会导致丢部分已提交事务的修改信息!
所以引入了 redo log 来记录已成功提交事务的修改信息,并且会把 redo log 持久化到磁盘,系统重启之后在读取 redo log 恢复最新数据。
总结:
redo log 是用来恢复数据的 用于保障已提交事务的持久化特性。
undo log
undo log 叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log 主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。
每次写入数据或者修改数据之前都会把修改前的信息记录到 undo log。
undo log 有什么作用?
undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者 rollback 操作而回滚的话可以根据 undo log 的信息来进行回滚到没被修改前的状态。
锁技术
当有多个请求来读取表中的数据时可以不采取任何操作,但是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。不然很有可能会造成不一致。
读写锁
解决上述问题很简单,只需用两种锁的组合来对读写请求进行控制即可,这两种锁被称为:
共享锁 (shared lock), 又叫做 "读锁"
读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。
排他锁 (exclusive lock), 又叫做 "写锁"
写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。
MVCC
MVCC (MultiVersion Concurrency Control) 多版本并发控制
InnoDB 的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号
MVCC 在 mysql 中的实现依赖的是 undo log 与 read view
undo log :undo log 中记录某行数据的多个版本的数据。
read view : 用来判断当前版本数据的可见性
事务的原子性是通过undolog来实现的
事务的持久性性是通过redolog来实现的
事务的隔离性是通过(读写锁+MVCC)来实现的
而事务的终极大 boss 一致性是通过原子性,持久性,隔离性来实现的原子性 (Atomicity)
一个事务必须被视为不可分割的最小工作单位,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作,这就是事务的原子性。
1. 每条数据变更 (insert/update/delete) 操作都伴随一条 undo log 的生成,并且回滚日志必须先于数据持久化到磁盘上
2. 所谓的回滚就是根据回滚日志做逆向操作,比如 delete 的逆向操作为 insert,insert 的逆向操作为 delete,update 的逆向为 update 等。
undolog保证了回滚
undo log 记录了数据被修改前的信息以及新增和被删除的数据信息,根据 undo log 生成回滚语句,比如:
(1) 如果在回滚日志里有新增数据记录,则生成删除该条的语句
(2) 如果在回滚日志里有删除数据记录,则生成生成该条的语句
(3) 如果在回滚日志里有修改数据记录,则生成修改到原先数据的语句
一致性 (Consistency)
隔离型 (Isolation)
隔离性是要管理多个并发读写请求的访问顺序。 这种顺序包括串行或者是并行
READ UNCOMMITTED
在 READ UNCOMMITTED 隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。
因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行。
换句话说,读的操作不能排斥写请求。
READ COMMITTED
一个事务的修改在他提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。在很多场景下这种逻辑是可以接受的。
InnoDB 在 READ COMMITTED,使用排它锁,读取数据不加锁而是使用了 MVCC 机制。或者换句话说他采用了读写分离机制。
但是该级别会产生不可重读以及幻读问题。
在一个事务内多次读取的结果不一样。
这跟 READ COMMITTED 级别下的 MVCC 机制有关系,在该隔离级别下每次 select 的时候新生成一个版本号,所以每次 select 的时候读的不是一个副本而是不同的副本。
在每次 select 之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读
REPEATABLE READ(Mysql 默认隔离级别)
在一个事务内的多次读取的结果是一样的。这种级别下可以避免,脏读,不可重复读等查询问题。mysql 有两种机制可以达到这种隔离级别的效果,分别是采用读写锁以及 MVCC。
读写锁实现
读锁共享、写锁排他,无法做到读写并行
MVCC实现
多次读取只生成一个版本,读写并行
持久性 (Durability)
事务一旦提交,其所做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。
MySQL 的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘 IO, 然而即使是使用 SSD 磁盘 IO 也是非常消耗性能的。为此,为了提升性能 InnoDB 提供了缓冲池 (Buffer Pool),Buffer Pool 中包含了磁盘数据页的映射,可以当做缓存来使用:
读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取再放入缓冲池;
写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;
上面这种缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当 MySQL 系统宕机,断电的时候可能会丢数据!!!
因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。
于是 redo log 就派上用场了。