MySQL事务

一.什么是MySQL事务?

start transaction
要执行的sql语句
commit

一个事务包含多个sql语句,事务保证这些sql语句要么都执行成功,要不都失败

二.事务四大特性

A - 原子性
C - 一致性
I - 隔离性
D - 持久性

三.事务特性的底层实现原理

1.原子性: 一个事务是最小的操作单元,不可分割,,事务中事务sql要么全成功,,要么都失败(通过undo log实现)

其原理体现在(有一个sql失败,会执行rollback回滚,回滚到事务之前的状态):Undo log实现
当开始事务后,innodb会把每条sql的执行信息记录在undolog中,如果有sql执行失败,innodb会根据undolog中记录的内容去执行相反的操作:
原来insert,,那我就delete
原来delete,那我就insert
原来update,,那我就操作相反的update,把数据再改回去
所以,,,Undo log日志实现了事务的原子性

2.持久性: 事务一旦提交成功,则对数据库的更改是永久性的(通过redo log实现)

先了解下innodb读写数据原理:
数据库的数据是存在磁盘中的,每次读写通过磁盘io的话,效率是很低的. 因此,innodb提供了Buffer Pool作为访问数据库数据的缓存.
Buffer Pool是位于内存的,包含了磁盘中部分数据页的映射.

当从数据库读数据时,会先从buffer pool中取, buffer pool中如果没有,再去磁盘读,读到后会放到buffer中
当向数据库写数据时,也是先写到buffer pool中, 这些修改的数据页会在后期定期地刷新到磁盘上(这一过程叫做刷脏,由其他后台线程负责), 做一个持久化操作

这样设计的好处:
把大量磁盘io读写转成了内存读写,并且会把对一个页面的多次修改merge程一次io操作(刷脏是一次刷整个数据页,默认16k),大大减少了磁盘io操作,提高了数据库性能
但这样也会有一个问题: 
buffer pool是在内存的,容易丢失, 如果buffer pool中的数据还没有同步到磁盘, 此时mysql宕机了, 这样buffer pool中数据就丢失了,这就没法保证持久性了

基于这样的原因,innodb引入redo log是实现数据修改的持久化
为了提高性能,redo log也包含两部分: 一是内存中的日志缓冲(redo log buffer), 二是磁盘上的日志文件(redo log file)
==>当数据修改时,innodb除了修改buffer pool中的数据, 也会在redolog buffer中记录这次操作, 当事务提交时, 会对redolog buffer进行刷盘,记录到redolog file中.
如果mysql宕机了且buffer pool中数据还没有刷新到磁盘, mysql重启后,会通过已经写入磁盘的redo log来恢复没有被刷新到磁盘的数据页(这样就不用每次提交事务时实时进行buffer pool的刷脏了)

redo log是一个预写式日志: 刷脏时一定保证了对应的redo log已经落盘了

那么事务提交时,写入redo log相比于直接刷脏的好处主要有3点:
1).buffer pool的刷脏是随机io(每次修改数据位置是随机的),但写redo log是顺序io(文件尾部追加模式),顺序io比随机io要快很多
2).刷脏是以数据页page为单位的,mysql默认数据页是16k,即使只是数据页的一处更改也需要写入整个数据页;而redo log只写入真正修改的部分,这样无效io大大减少
3).刷脏有时候需要刷很多页数据,无法保证一定成功;但redolog buffer向redolog file写入时,是按扇区写入(写入的最小单位:512字节),因此保证写入必定成功
redo log 持久性3种策略:
0-表示当提交事务时,,并不将redolog缓冲区的日志写入磁盘文件,,而是等待主线程每秒刷新
1-在事务提交时将redolog缓冲区的日志同步写入磁盘,,保证一定会写入成功(推荐)
2-在事务提交时将redolog缓冲区的日志异步写入磁盘,,即不能完全保证commit时肯定会写入redolog日志

3.隔离性: 事务之间相互不影响(通过mvcc+锁实现控制)

1)数据库存在的并发操作使用的事务控制

mysql事务的隔离性是通过 mvcc+锁 来实现控制的...

数据库并发场景有三种,分别为:

  读-读:不存在任何问题,也不需要并发控制
  读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读...MVCC控制
  写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失...锁控制

2)锁的概念:

事务在修改数据之前,这个事务要获取相应的锁,只有拿到锁才能操作数据.....
如果其他事务想操作这个数据,必须等当前事务提交或回滚后释放锁以后, 再去竞争这个锁, 拿到锁后才能执行其自身事务

innodb下有行锁(默认, 但需要索引触发), 表锁, 间隙锁(范围概念,,锁多行)
myisam默认支持表锁

3)mvcc概念:

mvcc: 多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制, 主要是为了提高数据库的读写并发性能.
mvcc 可以更好的处理读请求,做到发生读写请求冲突时不用加锁...这里的读指的是快照读,,而不是当前读(当前读是一种加锁操作,,是悲观锁)...
mvcc 只在读已提交和可重复读两种隔离级别下工作,其他两种隔离级别与mvcc不兼容

MVCC 到底是啥?
MVCC 的英文全称是 Multiversion Concurrency Control ,中文意思是多版本并发控制技术。
原理是,通过数据行的多个版本管理来实现数据库的并发控制,简单来说就是保存数据的历史版本。可以通过比较版本号决定数据是否显示出来。读取数据的时候不需要加锁可以保证事务的隔离效果。

  MVCC 可以解决什么问题?
  读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,读不相互阻塞,写不阻塞读,这样可以提升数据并发处理能力。
  降低了死锁的概率,这个是因为 MVCC 采用了乐观锁的方式,读取数据时,不需要加锁,写操作,只需要锁定必要的行。
  解决了一致性读的问题,当我们朝向某个数据库在时间点的快照是,只能看到这个时间点之前事务提交更新的结果,不能看到时间点之后事务提交的更新结果。

4)啥是当前读?

它读取的数据库记录是当前最新的版本,,,会对当前读取的数据进行加锁,,,防止其他事务修改数据,,典型的悲观锁操作

以下操作触发当前读:
select * from .....lock in share mode(共享锁)
select * from .....for update(排它锁)
update(排它锁)
insert(排它锁)
delete(排它锁)
串行化事务隔离级别(串行化指的就是事务一个接一个执行,类似于单线程)

这里解释下数据库的共享锁和排它锁:
共享锁也叫读锁, 是指读可以并发,但是写会阻塞
排它锁也叫写锁, 无论读写都会阻塞

5)啥是快照读?

innodb下普通的select语句都是快照读, 快照读是不加锁的非阻塞读
它读取的版本可能不是最新版本, 可能是历史版本...
快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读

说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读

6)详解mvcc快照读

先说几个概念:
我们在数据库表中看到的一行记录实际上有多个版本,每个版本的记录除了有数据本身外,包含事务id trx_id(自增的) 和回滚指针roll_pointer, 如此记录的多个版本之间构成版本链, 每个版本记录通过回滚指针指向上一个版本

  ReadView(快照读对象)实现快照读:它让你知道版本链中哪个版本是可用的
  ReadView4个属性:
  m_ids: 生成ReadView时当前系统中未提交的事务id列表,,
  min_trx_id: 最小的事务id,
  max_trx_id: 下一个需要分配的事务id,,
  creator_trx_id: 生成该ReadView的事务id

对于一个快照来说,它能够读到那些版本数据,要遵循以下规则:

当前事务内的更新,可以读到;
版本未提交,不能读到;
版本已提交,但是却在快照创建后提交的,不能读到;
版本已提交,且是在快照创建前提交的,可以读到;

7)事务四种隔离级别

读未提交, 读已提交, 可重复读, 串行化

当读写冲突时, 一个事务能读到什么样的数据取决于事务的四种隔离级别

8)总结: MySQL是如何实现事务隔离的

读未提交 它是性能最好,也可以说它是最野蛮的方式,因为它压根儿就不加锁,所以根本谈不上什么隔离效果,可以理解为没有隔离。但有利就有弊,这基本上就相当于裸奔啊,所以它连脏读的问题都没办法解决

读已提交 是每次执行select语句的时候都重新生成一次快照

可重复读 是在事务开始的时候生成一个当前事务全局性的快照

两者主要的区别就是在快照的创建上,可重复读仅在事务开始是创建一次,而读提交每次执行select语句的时候都要重新创建一次

串行化 是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束
读的时候加共享锁,也就是其他事务可以并发读,但是不能写。写的时候加排它锁,其他事务不能并发写也不能并发读 InnoDB 默默的把所有纯 SELECT 语句都转成了 SELECT ... FOR SHARE ,也就默认都加读锁

特别注意:
MySQL 在可重复读级别解决了幻读问题,是通过行锁和间隙锁的组合 Next-Key 锁实现的

4.一致性

事务执行前后数据是完整一致的, 它是事务的最终目的

 

posted @ 2021-07-13 22:27  Sherlock先生  阅读(122)  评论(0编辑  收藏  举报