MYSQL事务的高级
基本概念
- 事务的特点(ACID)
- Atomicity(原子性):原子性表示mysql的事务是一个整体,一个事务要么全部完成,要么全部失败。innodb通过undo log来实现原子性。
- Consistency(一致性):一致性表示mysql的事务是让数据库从一个一致性状态装换到另一个一致性状态。(mysql通过保证原子性,持久性,隔离性来最终保证数据库的一致性)
- Isolation(隔离性):mysql的隔离性,表示一个事务内部的操作数据对其他并发的事务是隔离的,一个事务的执行不能被其他事务干扰,并发执行的各个事务之间不能相互干扰。(mysql通过锁机制和mvcc来实现隔离性)
- Durability(持久性):持久性表示mysql的事务提交后,数据对于数据库是永久性的,接下来的其他事务的发生和数据库故障不应该对这个事务已经提交的数据有任何影响
- 事务要做的的效果
- 可靠性:数据库要保证insert,update,delete操作时抛异常或数据库crash的时候需要保证数据的操作前后一致。
- 并发处理:多个并发读写请求,相互之间的数据不影响,这需要事务的隔离性。
redo log 与 undo log
-
redo log
-
redo log叫做重做日志,是用来实现事务的持久性的。该日志文件由两部分组成:重做日志缓存(redo log buffer)以及重做日志文件(redo log),前者存在内存中,后者存在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中。
-
start transaction; select balance from bank where name="zhangsan";// 生成 重做日志 balance=600 update bank set balance = balance - 400; // 生成 重做日志 amount=400 update finance set amount = amount + 400; commit;
-
-
-
-
redo log的作用
- mysql为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Buffer Pool缓存池里,把这个当做缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步
- 问题:如果还没来得及同步的时候宕机或者断电了怎么办?还没来得及执行上面红色箭头的同步操作。这样会导致丢失部分已提交事务的修改信息。
- mysql引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后再读取redo log恢复最新的数据。
-
总结:redo log是用来恢复数据的,用于保障已提交事务的“持久化”特性。
-
-
undo log
-
undo log叫做回滚日志,用于记录数据被修改前的信息。它正好跟redo log所记录的相反(重做日志记录的是数据被修改后的信息)。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将操作之前的所有操作都记录下来,然后在发生错误时才可以回滚。
-
每次写入数据或修改数据之前都会改修改前的信息记录到undo log。
-
undo log的作用
- undo log记录事务修改之前版本的数据信息,因此加入由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。
-
总结
- undo log作用是用来回滚数据,用于保障未提交事务的原子性。
-
Mysql锁技术以及MVCC基础
-
mysql锁技术
-
当有多个请求来读取表中的数据时可以不采用任何操作。但是多个请求中有读请求,又有修改请求时必须有一种措施来进行并发控制。不然可能会造成数据不一致。读写锁用于解决上述问题,只需要用两种锁的组合来对读写请求进行控制即可,这两种锁被称为
- 共享锁(shared lock):又叫做“读锁”,读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。
- 排他锁(exclusive lock):又叫做“写锁”,写锁会排斥其他所有获取锁的请求,未获取到锁的请求进入等待,一直阻塞,直到写入操作完成释放锁。
-
读锁 写锁 读锁 可并行 不可并行 写锁 不可并行 不可并行 -
总结
- 通过读写锁,可以做到读读并行,但做不到写读,写写并行,事务的隔离性就是通过读写锁来实现的
-
-
MVCC基础
-
MVCC(MutiVersion Concurrency Control)多版本并发控制
- InnoDB的MVCC,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号。
- MVCC(MutiVersion Concurrency Control)多版本并发控制,只在(Read Commit)已提交读和(Repeatable Read)可重复读两种模式下有效。其通过Undo log中的版本链和ReadView一致性视图来实现的。
- MVCC(MutlVersion Concurrency Control)就是在多个事务同时存在时,SELECT语句查询到的具体是版本链上的哪个版本,然后在找到版本上返回其中所记录的数据的过程
-
在Mysql中,会默认在表后面添加三个隐藏字段
- DB_ROW_ID:行ID,MYSQL的B+树索引特性要求每个表必须有一个主键。如果没有设置的话,mysql会自动寻找一个不包含NULL的唯一列作为主键。如果找不到一个不包含NUll的唯一列,mysql会在DB_ROW_ID上自动生成一个唯一值,以此来作为主键(该列和MVCC的关系不大);
- DB_TRX_ID:事务ID,记录的是当前事务在做INSERT或UPDATE语句操作时的事务ID(DELETE语句被当做是UPDATE语句的特殊情况)
- DB_ROLL_PTR:回滚指针,通过这个字段可以将不同的版本串联起来,形成版本链。相当于链表的next指针
-
ReadView
- ReadView一致性视图主要是由两部分组成:所有未提交事务的ID数组和已经创建的最大事务ID。比如:[100,200],300。事务100和200是当前未提交事务,而事务300是当前创建的最大事务(已经提交了)。当执行SELECT语句的时候会创建ReadView,但是在读取已提交和可重复读两个事务级别下,生成ReadView的策略是不一样的:读取已提交级别是每执行一次select语句就会生成一份ReadView。而可重复读是只会在事务开启时第一次select语句执行的时候会生成一份ReadView,后续的Select语句会沿用之前生成的readView(即使后面有更新语句的话,也会继续沿用)。
-
什么是当前读和快照读?
- 当前读:像select lock in share mode(共享锁),select for update;update,insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁
-
MVCC的主要思想是通过数据多版本控制和读写分离,从而实现不加锁读,进而做到读写并行。
-
MVCC在mysql中的实现依赖的是undo log 和read view
- undo log:undo log中记录某行数据的多个版本的数据。
- read view :用来判断当前版本数据的可见性。
-
-
MySQL事务的实现
-
重做日志,回滚日志以及锁技术就是实现事务的基础
- 事务的原子性是通过undo log来实现的
- 事务的持久性是通过redo log来实现的
- 事务的隔离性是通过mysql锁机制(读写锁)和MVCC来实现的
- 事务的一致性,mysql是通过原子性,持久性,隔离性来实现的
- ACID只是概念,最终是要保证数据库的数据的可靠性,一致性。
-
原子性的实现
- 事务的原子性:一个事务必须被视为不可分割的最小单元,一个事务中的所有操作要么全部成功提交,要么全部失败回滚,对于一个事务来说不可能只执行其中的部分操作。
- mysql通过回滚操作保证了事务的原子性,所谓回滚操作就是当发生错误异常或者显式的执行rollback语句时需要把数据还原到事务开始前的样子,这时候就需要使用undo log来进行回滚。
- undo log在实现原子性时的步骤
-
假设有两张表bank和balance,表中原始数据如图所示,当进行插入,删除以及更新操作时生成undo log
-
-
从上图可以看到数据变更都伴随着回滚日志undo log 的产生
- (1)产生了被修改前数据表bank(zhangsan,1000)的回滚日志
- (2)产生了被修改前数据表finance(zhangsan,0)的回滚日志
-
根据上面流程可以得出如下结论
- 每条数据变更(insert/delete/update)操作都伴随着一条undo log的生成,并且回滚日志undo log必须先于数据持久化到磁盘上
- 所谓的回滚就是根据回滚日志undo log逆向操作,比如delete的逆向操作为insert,insert的逆向操作是delete,update的逆向操作是update等
-
根据undo log进行回滚
-
为了做到同时成功或同时失败,当系统发生错误时或者执行rollback操作时需要根据undo log进行回滚
-
回滚操作就是要将表还原到事务之前的状态,undo log记录了数据被修改前的信息以及新增和被删除的数据信息,根据undo log生成回滚语句,比如
- 如果在回滚日志里有新增数据记录,则生成删除该条记录的sql语句
- 如果在回滚日志里有删除数据记录,则生成增加该条记录的sql语句
- 如果在回滚日志里有修改数据记录,则生成修改到原先数据的sql语句
-
-
-
持久性的实现
-
事务一旦提交,其所做的修改会永久保存到数据库,即使此时系统崩溃修改的数据也不会丢失。
-
先了解一下mysql的数据存储机制,mysql的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘IO,然而即使是使用SSD磁盘,它的IO操作也是非常消耗性能的。为此,为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:读数据,会首先从缓冲池(Buffer Pool)中读取,如果缓冲池中没有,则从磁盘读取再放入缓冲池;写数据,会首先将数据写入缓冲池(Buffer Pool),后台有线程会定期将缓冲池的数据同步到磁盘中。
-
注意:缓冲池是在内存中的,虽然缓冲池的措施在性能方面带来了质的飞跃。但是也带来了问题,当mysql系统宕机,断电的时候可能会丢失数据!!!
- 因为数据已经提交了,但是此时的数据还在缓冲池(Buffer Pool)中(也就是说还在内存中,还没来得及写入磁盘),还没来得及在磁盘持久化,所以需要一种机制存储已提交事务的数,为恢复数据使用。
-
于是redo log就派上用了。下面看下redo log何时产生的
-
既然redo log也需要存储,也涉及磁盘IO为什么还是要使用redo log?
- redo log的存储是顺序存储,而缓存同步是随机操作
- 缓存同步是以数据页为单位的,每次传输的数据大于redo log。
-
-
隔离性的实现
-
隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是事务之间不可见的。
-
级别越低的隔离级别可以执行越高的并发,但是同时实现复杂度以及开销也越大
-
Mysql隔离级别有以下四种(级别由低到高)
- READ UNCOMMITTED(读未提交 RU)
- READ COMMITTED(读已提交 RC)
- REPEATABLE READ(可重复读 RR)
- SERIALIZABLE(串行化)
-
只要彻底理解了隔离级别以及它的实现原理就相当于理解了ACID的隔离性。原子性,隔离性,持久性的目的都是为了做到一致性,但隔离性跟原子性和持久性有所区别。原子性和持久性是为了要实现数据的可靠性保障,比如做到系统宕机后,以及错误后的回滚。
-
那么隔离性要做到什么?隔离性是要管理多个并发读写请求的访问顺序。这种顺序包括串行或者是并行 说明这一点,写请求不仅仅是指inserr操作,又包括update操作。
-
总之,从隔离性的实现可以看出这是一场数据的可靠性和性能之间的权衡
- 可靠性高,并发性能低(比如 Serializable)
- 可靠性低,并发性能高(比如 Read Uncommitted)
-
READ UNCOMMITTED(读未提交)
-
在READ UNCOMMITTED隔离级别下,事务中的修改即使还没提交,对其他事务是可见的。事务可以读取未提交的数据,造成脏读。因为读不加任何锁,所以写操作在读的过程中修改数据,会造成脏读。好处是可以做到提升并发性能,可以做到读写并行。
-
换句话说,读操作不能排斥写操作
-
优点:读写并行,缺点:造成脏读,不可重复读,幻读
-
-
READ COMMITTED(已提交读)
- 一个事务的修改在它提交之前的所有修改,对其他事务都是不可见的。其他事务能读到已提交的修改变化。在很多场景下这种逻辑是可以接受的。
- InnoDB在READ COMMITTED,使用排它锁,读取数据不加锁而是使用MVCC机制,或者换句话说READ COMMITTED采用了读写分离。但是该级别会产生不可重复读和幻读的情况。
- 什么是不可重复读?
- 在一次事务中多次读取的结果不一样
- 为什么会产生不可重复读?
- 这跟READ COMMITTED级别下的MVCC机制有关,在该隔离级别下每次select的时候新生成一个版本号,所以每次在select的时候读的不是同一个副本而是不同的副本。
- 在每次select之间有其他事务更新了当前事务所要读取的数据并提交了,那就出现了不可重复读
-
REPEATABLE READ(可重复读)(Mysql默认隔离级别)
- 在一个事务内的多次读取结果是一样的。这种隔离级别下可以避免,脏读,不可重复读的问题。mysql有两种机制可以达到这种隔离级别的效果,分别采用读写锁以及MVCC
- 采用读写锁实现
-
-
上面的读写锁为什么能可重复读?在读写锁实现REPEATABLE READ的情况下,只要读操作没释放读锁,在第二次读的时候还是可以读到第一次读的数据
- 优点:实现起来简单
- 缺点:无法做到读写并行
-
- 采用MVCC实现
-
-
MVCC为什么能实现可重复读?因为多次读取只生成一个版本,读取到的自然是相同数据
- 优点:读写并行
- 缺点:实现的复杂度高
-
但是在该隔离级别下仍然会出现幻读的问题。
-
-
SERIABLIZABLE(串行化)
-
该隔离级别理解起来特别简单,实现也最简单。所有的操作都加上排他锁,在Serializable事务隔离级别下,除了不会造成数据不一致的问题,没有其他优点
-
隔离级别 脏读可能性 不可重复读可能性 幻读可能性 加锁读 READ COMMITTED Yes Yes Yes No READ UNCOMMITTED No Yes Yes No REPEATABLE READ No No Yes No SERIALIZABLE No No No Yes
-
-
-
一致性的实现
-
数据库总是从一个一致性的状态转换到另一个一致性的状态
-
下面举个例子:zhangsan从银行卡转400到理财账户
-
start transaction; select balance from bank where name="zhangsan";// 生成 重做日志 balance=600 update bank set balance = balance - 400; // 生成 重做日志 amount=400 update finance set amount = amount + 400;commit;
-
-
假如执行完update bank set balance = balance - 400; 发生异常了,银行卡的钱不能平白无故的减少,而是回滚到最初的状态。
-
又或者事务提交之后,缓冲池还没同步到磁盘的时候宕机了,这也是不能接受的,应该在重启的时候恢复并持久化。
-
假如有并发事务请求的时候也应该做好事务之间的可见性问题,避免造成脏读,不可重复读,幻读等。在涉及并发的情况下往往在性能和一致性之间做平衡,做一定的取舍,所以隔离性也是对一致性的一种破坏。
-