MySQL学习笔记之-事务,多版本并发控制

一、事务简介

在学习MySQL时我们首先肯定要知道事务这个概念,那么MySQL中事务是什么呢?

在MySQL中事务就是一组原子性的SQL语句,或者说是一个独立的工作单元。

该组SQL语句操作中要么全部执行成功,要么全部执行失败。

其中关于事务理解的最经典的例子之一就是银行转账例子:

你要给朋友小王转100块钱,而此时你的银行卡里只有100块钱。

转账过程具体到程序里会有一系列的操作,比如查询余额,做加减法,更新余额等,这些操作必须是一体的,一个独立的工作单元,不然等程序查完之后,还没做减法之前,你这100块钱,完全可以借着这个时间差再查一次,然后再给另外一个朋友转账,如果银行这么整,这不就乱套了吗?

二、ACID简介

提到事务,就肯定会想到事务中的4大特性:原子性(Atomicity)、一致性(Consistent)、隔离性(Isolation)、持久性(Durable) 简称ACID

  • 原子性:原子性是指事务的原子性操作,对数据的修改要么全部执行成功,要么全部执行失败,实现事务的原子性,是基于Redo/Undo日志来实现的。

    Redo log用来记录数据块被修改后的值,可以用来恢复未写入data file 的已经成功的事务更新的数据。

    Undo log用来记录数据更新前的值,保证数据更新失败能够回滚。保证事务的一致性

    假如某个时刻数据库崩溃了,在崩溃之前有事务A和事务B在执行,而事务A提交了,事务B还没有提交。当数据库重启的进行crash-recovery时,就会通过Redo log将已经提交事务的更改写到数据文件,而还没有提交的就通过Undo log进行roll back。

  • 一致性:一致性是指数据库执行事务前后的状态要保持一致,可以理解为数据的一致性。

  • 隔离性:隔离性是指事务之间相互隔离,不受影响,这个与设置的事务隔离级别有密切关系。不同的隔离级别事务间的影响不同。

  • 持久性:持久性就是指在一个事务提交后,这个事务的状态会被持久化到数据库中,也就是事务提交,对数据的新增、修改将被持久化到数据库中。

三、事务隔离级别

在上面提到的事务隔离性与其事务的隔离级别有关,那事务的隔离级别又有哪些呢??

  • 读未提交(READ UNCOMMITTED):该隔离级别下,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。
  • 读提交(READ COMMITTED):该隔离级别下,一个事务开始时,只能看见已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。
  • 可重复读(REPEATABLE READ):可重复读解决的脏读的问题,该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但该隔离级别依然不能解决幻读的问题。(MySQL的默认事务隔离级别,通过多版本并发控制解决的幻读)
  • 可串行化(SERIALIZABLE):最高的隔离级别。它会通过强制事务串行执行,避免了前面的幻读的问题。简单来说,串行化会在读取的每一行数据上都加上锁,所以可能导致大量的超时和锁争用的问题。

下面我们来举一个例子来理解一下各个隔离级别:

假设数据表T中只有一列,其中一行的值为1,下面是按照时间顺序执行两个事务的行为。

mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);

在这里插入图片描述

当我们在不同隔离级别下,事务A会有哪些不同的返回结果,也就是图中V1、V2、V3的返回值分别是什么?

  • 若隔离级别为“读未提交”,则V1=2。这时候事务B虽然还没有提交,但是结果已经能被事务A看到了,所以V2=2,V3=2。
  • 若隔离级别为“读提交”,则V1=1,V2=2,事务B的更新在提交事务后才能被事务A看到。所以V3=2。
  • 若隔离界别为"可重复读",则V1=1,V2=1,V3=2,之所以V2=1,在可重复读级别下事务在执行期间看到的数据前后必须是一致的。
  • 若隔离级别为"串行化",则在事务B执行”将1改为2时“,会被锁住,直到事务A提交后事务B才继续执行,所以V1,V2=1;V3=2。

事务隔离并发性能,隔离性对比

读未提交——>读提交——>可重复读——>串行化这四个隔离级别,隔离效果逐渐增强,但是性能越来越差。

它们的实现是通过加和**多版本并发控制(MVCC)**来实现的。

  • 读未提交:它没有加任何锁,也没有任何的隔离效果,所以所以它的性能也是最好的。

  • 串行化:它加的是一把大锁,读的时候加共享锁,不能写;写的时候加排它锁,阻塞其他事务的写入和读取,若是其他的事务长时间不能写入就会直接报超时,所以他的隔离效果是最好的,性能也是最差的,基本没有并发可言。

  • 读提交、可重复读:他们的实现既保证了一定的并发,也要兼顾一定的隔离性,他们实现上锁机制会比串行化优化很多,提高并发性,所以性能也会比较好。

四、多版本并发控制

MVCC可以被看做是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然不同数据库实现机制有所不同,但大多都实现了非阻塞的读操作,写操作也只锁定必要的行。

在InnoDB中MVCC的实现用到了一致性视图(快照),用于支持提交读和可重复读的实现。

通过在每行数据中额外保存两个隐藏的列,一个保存创建时的版本号,一个保存删除时的版本号

每开始一个新的事务,系统版本号就会自动递增。事务开始时刻的系统版本号会作为事务的ID。

  • 执行insert操作:

    插入一行数据时,将事务的ID作为数据行的创建版本号.

  • 执行delete操作:

    执行删除操作时,会将原数据行的删除版本号置为当前事务的ID,然后根据原数据行生成一条insert语句,写入undo log,用于事务执行失败时回滚。delete操作实际不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。但是会将数据行的删除版本号设置为当前的事务的ID,这样后面的事务B即便查到这行数据由于事务B的ID>删除版本号,也会忽略这条数据。

  • 执行更新操作:

    插入一条新的记录,保存当前事务ID为创建版本号,同时保存当前事务ID到原来的行作为删除版本号,生成一条INSERT语句,写入undo log,用于事务执行失败时回滚。

  • 执行查询操作:

    数据要被查询出来必须满足两个条件:

    • 数据行的删除版本号为空或者大于当前版本号的数据(否则数据已经被标记删除了)
    • 创建版本号小于等于当前事务ID的数据(否则数据是后面的事务创建出来的)

就查询而言:

  1. 如果该数据行没有被加行锁中的排它锁(也就是没有其他事务对数据进行修改),那么直接读取数据(前提是数据版本号小于等于当前事务ID的数据才会被放在查询结果集里面。)

  2. 该数据行被加了排它锁(也就是现在有其他事务对这个数据进行修改),那么读数据的事务不会进行等待,而是回去undo log端里面读之前版本的数据(这里存储的数据本身是用于回滚的),

    可重复读的隔离级别下,从undo log中读取的数据总是事务开始时的快照数据(也是就是版本号小于当前事务ID的数据),

    提交读的隔离级别下,从undo log中读取的总是最新的快照数据。(这里也就体现了重复读和读提交的区别)

参考书目:[1] 高性能MySQL (第3版)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值