MySQL事务原理

MYSQL事务

是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;
这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;
事务是一组不可再分割的操作集合(工作逻辑单元);
在这里插入图片描述


为什么要用事务?

因为要保证数据的一致性,如果没有事务,那么在读写,删除修改数据时,会造成数据的各种问题出现,导致数据不一致,无效数据,错误数据等等问题,所以出现了事务
那么,一下我们来看看,事务定义的几大特性,这是事务存在的几个作用,最终还是保证数据一致性


事务的四大特性

  • 原子性:
    一个事务内的操作,要么同时成功,要么同时失败

  • 一致性:
    一个事务必须使数据库从一个一致性状态变换到另一个一致性状态,所谓一致性,即,从实际的业务逻辑上来说,最终结果是对的、是跟程序员的所期望的结果完全符合的

  • 隔离性:
    一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

  • 持久性:
    也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响

1.原子性:实现原理undo log

  • 在说明原子性原理之前,首先介绍一下MySQL的事务日志。MySQL的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等,此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。

  • 下面说回undo log。实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。

  • undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。

  • 以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。

2.一致性:实现

可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。

实现一致性的措施包括:

  • 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证
  • 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等
  • 应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致

3.隔离性:实现原理「锁机制」

  • 首先来看两个事务的写操作之间的相互影响。隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。
  • 锁机制的基本原理可以概括为:事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
  • 行锁与表锁:按照粒度,锁可以分为表锁、行锁以及其他位于二者之间的锁。表锁在操作数据时会锁定整张表,并发性能较差;行锁则只锁定需要操作的数据,并发性能好。但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。MySQL中不同的存储引擎支持的锁是不一样的,例如MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。
	有多种方法可以查看InnoDB中锁的情况,例如:
	select * from information_schema.innodb_locks; #锁的概况
	show engine innodb status; #InnoDB整体状态,其中包括锁的情况

4.持久性:实现原理「redo log」

  • redo log和undo log都属于InnoDB的事务日志。下面先聊一下redo log存在的背景。
  • InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。
  • Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
  • 于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。

既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:

    1. 刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。
    1. 刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少

重点

为了保证事务操作的原子性,必须实现基于日志的REDO/UNDO机制,但原子性并不能完全保证一致性
而在事务处理的ACID中,一致性是最基本的属性,其他的三个特性都是为了保证一致性而存在的,一致性指的是语义上面的一致而非语法,即在多并发或复杂业务场景下,我们的各种增删改查与我们预估结果是一致的,满足我们的期望
所以是语义上的一致性,而为了保证数据的一致性,解决数据不一致的问题,衍生出了事务,进而逐步衍生隔离级别,隔离带来的问题,使用了锁,等等一系列发展,为了满足需要,解决问题,保证数据一致性
所以用了事务,而并发事务不隔离就跟多并发线程未加锁一样,数据各种问题,比如数据脏读,不可重复读,幻读等等

隔离性的问题及级别

  • read uncommitted(RU):读未提交,出现的问题:脏读,并发性能最高,隔离级别最低
  • read committed(RC):读已提交,解决的问题:脏读,出现的问题:不可重复读
  • repeatable read(RR): 可重复读,解决的问题:不可重复读,出现的问题:幻读,mysql默认的事务隔离级别
  • serializable: 串行化,解决的问题:幻读,并发能力最低,隔离级别最高
1.读未提交:事务B可以读到事务A未提交的数据,但如果事务回滚,则读出的数据会造成脏读,即读取的数据可能不存在,属于无效数据

2.读已提交:解决了脏读问题,但多事务并发下会出现问题:事务B去读数据,此时事务A去更新数据,事务B需要对读取到的数据进行检验,所以再次进行了读取操作,事务A刚好修改了数据进行提交,事务B取的就是修改后的数据,两次数据不一致,导致结果也不一样了,这种叫不可重复读,同时降低了一点并发

3.可重复读:解决了不可重复读的问题,保证在多事务并发下,事务B如果在读取数据,那么事务A就不去修改数据了,同样,事务A在修改时,事务B也不会去查询但会出现幻读问题,在以上的案例中,将事务A的修改变成事务A进行删除或插入,对于事务B来说,就不是数据不一致,而是数量不一致了,这种就是幻读,仿佛出现了幻觉。又降低了一点并发,mysql默认的事务隔离级别

4.串行化:以上问题都没了,隔离级别最高,但是并发能力最低,隔离级别与并发能力成反比例,在处理金额相关的重要数据时可设为临时级别,一般不建议使用

那我们了解了mysql的默认事务级别是可重复读(RR),前面我们说过,在mysql5.5之后采用的是InnoDB存储引擎,而InnoDB在RR的级别下,解决了幻读问题,Innodb使用MVCC和next-key locks解决幻读,MVCC解决的是普通读(快照读)的幻读,next-key locks解决的是当前读情况下的幻读。
在此之前我们先来了解一下,RR是如何实现的

RR级别下事务的原理,我们需要知道以下内容

  • 了解MYSQL中的两种视图
  • 了解RR级别下,如何实现的事务隔离
  • 了解什么是当前读,以及当前读会造成什么问题

1.在mysql中,视图有两种:

1.是View,常用来查询的虚拟表,在调用时执行查询语句获取结果,创建视图:create view。
2.是存储引擎层 InnoDB 用来实现 MVCC(Mutil-Version Concurrency Control | 多版本并发控制)时用到的一致性视图consistent read view,用于支持 RC 和 RR 隔离级别的实现。简单来说,就是定义在事务执行期间,事务内能看到什么样的数据。

事务真正启动的时机:

在使用 begin 或 start transation 时,事务并没有真正开始运行,而是在执行一个对 InnoDB 表的操作时(即第一个快照读操作时),事务才真正启动
如果想要立即开始一个事务,可以用 start transaction with consistent snapshot 命令。
那么在RR级别下的事务,如果其他事务修改了数据,事务中看到的数据与启动事务时看到的数据是一致的,并不会受其他事务的影响,下面看一种特殊情况

A马上开启事务,B也马上开启事务,C修改数据,B再修改数据,然后查询数据,A查询数据,并提交事务,此时A查询出来的数据是原值,B提交事务,此时B查询出来
的数据是加上了C的修改后的数据,而并非期待的原值上B进行修改的数据,事务隔离失败?这就涉及到MVCC中快照的原理

MVCC的实现—快照

在RR级别下,事务启动时会基于整库拍个快照,用于记录下当前状态下的数据信息,如此的话,对于数据库比较大的情况,事务是否会启动的非常慢?然而并不是,
事务启动的非常快,原因在与InnoDB中的快照的实现

快照的实现

在InnoDB中,每个事务都有唯一的事务ID,叫做transaction id,在开启事务时,严格按照d递增的顺序向InnoDB事务系统申请,数据库中,每行数据都有
多个版本,在每次开启更新事务时,都会生成一个新的数据版本,然后把此次事务生成的transaction id赋值给当前数据版本的事务ID,也就是每行数据都有
自己的数据版本以及对应的transaction id事务ID,记为 row trx_id,
在每次更新时,都会生成一条回滚日志(undo log),而每次事务开启的数据版本物理上并不真实存在,而是通过当前事务版本和undo log计算出来的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全粘架构师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值