Mysql事务实现原理

事务追求的目标:

  1. 可靠性
  2. 并发处理

可靠性

数据库要保证当insert或update操作时抛出异常,或者数据库crash时需要保障数据库数据的操作前后一致;想要保证这一点,我们需要知道我在修改前和修改后的状态,于是Mysql引入了 undo log 和 redo log

并发处理

当有多个并发请求过来时,且其中有一个请求是对数据修改的操作,未来避免读到脏数据,所以需要对事务之间的读写进行隔离

实现Mysql事务功能的三个技术

  1. 日志文件(redo logundo log
  2. mysql 锁技术
  3. MVCC 技术

redo log 和 undo log 介绍

1. redo log

redo log叫做重做日志,用来实现事务的持久性 。该日志文件由两部分组成:重做日志缓冲重做日志文件,重做日志缓冲在内存中,后者在磁盘中;在事务的操作过程中,会将重做日志写入到缓冲中;事务提交后,会立刻将重做日志缓冲中的数据写入磁盘(即重做日志文件中)。
redo log 的作用
mysql 为了提升性能,并不是每次修改都实时写入到磁盘中,而是先将数据写入到Buffer Pool(缓冲池)中,缓存起来,然后后台线程在事务提交后异步地去将缓冲池中的数据同步到磁盘中;以减少磁盘IO,加速读写。
可是这样就引入了一个问题:如果在缓冲向磁盘同步过程中宕机了怎么办,这样就会造成数据丢失,造成数据库数据不可信的问题。
所以就引入redo log来记录事务的修改信息(记录在缓存中),并在事务提交时立刻写入磁盘。系统宕机重启后可以通过读取redo log来恢复最新数据(如果还未提交就宕机,相当于事务未提交,数据丢失就是可接受的)

redo log总结

redo log 就是用来恢复数据的,用于保障已提交书屋的持久化特性的


2. undo log

undo log 叫做回滚日志,用于记录数据被修改前的信息。和redo log说记录的操作相反,undo log主要记录的是数据的逻辑变化,未来发生错误时会滚到之前的数据。需要将之前的数据记录下来,在发生错误时会滚。

事务中的每次写入或修改数据之前,都会把原始数据备份到 undo log 中

undo log 的作用
undo log 用于回滚数据,用于保障未提交事务的原子性

3. Mysql锁技术以及MVCC技术

当都是读请求是没必要做任何操作,但一旦有修改请求时必须有一种措施来进行并发控制,避免出现脏读,或者(不可重复读、幻读)

读写锁

解决上述问题很简单,只要使用读写锁的组合来对读写请求进行控制即可

共享锁(shared lock),又叫做“读锁”

读锁可以共享,多个读请求共享一把读锁读数据,不会造成堵塞

排他锁(exclusive lock),又叫做“写锁”

写锁会排斥其他所有获取锁的请求,一直阻塞,知道写入完成释放锁

总结:

通过读写锁,可以做到读读可以并行,但是不能做到写读,写写并行。事务的隔离性就是根据读写锁来实现的!!

4. MVCC 技术

MVCC(MultiVersion Concurrency Control) 多版本控制

InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列, 一个保存了行的创建时间,一个保存了行的过期时间, 当然存储的并不是实际的时间值,而是系统版本号。

MVCC在mysql中的实现依赖的是undo log与read view

  1. undo log 用于记录某行数据的多个版本的数据(改一下一个版本,再改一下有一个版本)
  2. read view 用于判断当前版本数据的可见性

5. 事务的实现

前面讲的重做日志,回滚日志以及锁技术就是实现事务的基础。
  • 事务的A原子性是通过 undo log 来实现的
  • 事务的C持久性性是通过 redo log 来实现的
  • 事务的I隔离性是通过 (读写锁+MVCC)来实现的
  • 而事务的 **D一致性 **是通过原子性,持久性,隔离性来实现的!!!

(1)原子性的实现

原子性可以概括为就是要实现要么全部失败,要么全部成功。

数据库的实现方式就是使用回滚操作,即利用undo log来进行回滚。

1.每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上
2.所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。

(2)持久性的实现

事务一旦提交,其所作做的修改会永久保存到数据库中,此时即使系统崩溃修改的数据也不会丢失。
数据库就是利用redo log 来实现出现宕机时的数据持久化的

既然redo log也需要存储,也涉及磁盘IO为啥还用它?

(1)redo log 的存储是顺序存储,而缓存同步是随机操作。

(2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。

(3)隔离性的实现 —— 靠读写锁和MCC实现

隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。

Mysql 隔离级别有以下四种(级别由低到高):

  • READ UNCOMMITED (未提交读)
  • READ COMMITED (提交读)
  • REPEATABLE READ (可重复读)
  • SERIALIZABLE (可重复读)

隔离性是要管理多个并发读写请求的访问顺序。 这种顺序包括串行或者是并行
说明一点,写请求不仅仅是指insert操作,又包括update操作。

READ UNCOMMITED

事务中的修改即使还没提交,对其他事务是可见

读操作不会加任何锁,所以写操作加写锁,在读的过程中修改数据,会造成脏读。好处是可以提升并发处理性能,能做到读写并行

原因: 读未加读锁,写锁只能做到阻塞读锁和写锁,导致未加锁的事务可以访问到事务还未提交的修改后的数据

READ COMMITED

一个事务的修改在他提交之前的所有修改,对其他事务都不可见

InnoDB在 READ COMMITTED时,写使用排它锁, 读取数据不加锁而是使用了MVCC机制。或者换句话说他采用了读写分离机制

该级别会产生不可重读以及幻读问题。

不可重复读: 同一行数据,前后两次查询不一致
幻读: 前后两次查询的结果集,数量不一致
READ COMMITTED 级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。

读取的是最新操作的写事务在undo log 中的留下的快照

REPEATABLE READ

在一个事务内的多次读取的结果是一样的;避免脏读、不可重复读等查询问题
MVCC的实现

读不加锁,但是读取的时在undo log 中的同一份快照(永远是这个快照);写加写锁。

(4)一致性的实现

下面举个例子:zhangsan 从银行卡转400到理财账户

start transaction;
select balance from bank where name="zhangsan";
// 生成 重做日志redo log balance=600 和 回滚 undo log
update bank set balance = balance - 400; 
// 生成 重做日志redo log amount=400 和 回滚 undo log
update finance set amount = amount + 400;
commit;

1.假如执行完 update bank set balance = balance - 400;之发生异常了,银行卡的钱也不能平白无辜的减少,而是回滚到最初状态。

2.又或者事务提交之后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化。

3.假如有并发事务请求的时候也应该做好事务之间的可见性问题,避免造成脏读,不可重复读,幻读等。在涉及并发的情况下往往在性能和一致性之间做平衡,做一定的取舍,所以隔离性也是对一致性的一种破坏。

posted @ 2022-03-26 09:00  明月照江江  阅读(270)  评论(0编辑  收藏  举报