重点知识学习(9.3)--[浅入MySQL数据库事务,浅入MVCC]

Posted 小智RE0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重点知识学习(9.3)--[浅入MySQL数据库事务,浅入MVCC]相关的知识,希望对你有一定的参考价值。

文章目录


1. 事务


当时学习Spring的时候,也是学过一点事务的知识:
Spring框架学习笔记(3) — [在spring中初步上手实现AOP,以及对事务的初步配置使用]

Spring框架学习笔记(4) — [Spring的事务传播行为]
然后当时后面和小伙伴一块写项目的时候,对于事务方面处理的话,也仅仅是在对整个服务层进行事务声明,也是保证了数据操作的安全性.


本次还是得学学事务的知识,起码要明白一些专业术语的含义以及部分实践.

  • 事务就是一次完整的数据库操作,该操作过程可能包含多条sql的执行.
  • 要么都执行成功,要么都执行失败.
  • 注意,mysql中目前仅InnoDB引擎支持事务.
  • 事务主要是为了增删改操作,查询操作不会有太大问题的.

先简单熟悉一下操作;

(1)自动提交事务
开启自动提交

SET SESSION  autocommit=1;
SET GLOBAL   autocommit=1;

SESSION 表示会话;
GLOBAL 表示全局;

禁止自动提交

SET SESSION  autocommit=0; 
SET GLOBAL   autocommit=0;

(2)手动提交事务

  • 开启一个事务.
BEGIN; 
# 或者
START TRANSACTION; 
  • 事务回滚.
ROLLBACK; 
  • 提交当前事务.
COMMIT;

试试吧;注意数据表的存储引擎是InnoDB的;

先关闭自动提交;

#关闭自动提交;
SET GLOBAL autocommit=0;

查看当前的事务状态;

SHOW  GLOBAL VARIABLES LIKE 'autocommit';

开启事务;执行语句;

#测试;
BEGIN;

执行一条sql;

INSERT INTO t_test(id,account) VALUES(1,'杰哥');

数据存入成功;

现在可以执行一下事务回滚试试;

ROLLBACK;

表数据没了


现在试试,开启事务,提交之后,是否还能回滚呢;

一行一行执行;

#测试;
BEGIN;
INSERT INTO t_test(id,account) VALUES(1,'杰哥');
#提交;
COMMIT;
#回滚;
ROLLBACK;

由于事务已提交,无法再回滚到空数据状态;


1.1 事务的四特性

ACID原则:

  • 原子性/不可分开性(Atomicity)
  • 一致性(Consistency)
  • 隔离性/独立性(Isolation)---- 重点学习部分
  • 持久性(Durability)。

原子性:在一次事务过程中的多个执行操作,要么都成功,要么都失败

一致性:在事务开始之前和事务结束之后,我们的数据库完整性没有被破坏.

比如说现在采用多种方式对银行账户的余额进行多次操作,那么最后账户里面余额应该是我们所预期的结果,绝对不能出现错误.不然就得出问题.

隔离性:在同一时间,数据库允许多个事务进行访问, 那么就得对这些事务进行隔离.

关于隔离性,后面有四个级别可以看看.

持久性:事务一旦提交,咱们的数据就不可能改变.就算数据库服务层出现了问题.


1.2 并发事务带来的问题


并发操作事务时,就可能出现 脏读,不可重复读,幻读…等问题.

(1)脏读

可以看看这个案例

事务B正在进行,还没来得及提交/回滚数据; 事务A抢先出手了,他把数据读取走了;
没想到事务B留了一手,它又回滚了,把数据恢复了;
嘿嘿,事务A读到的就是垃圾数据.

当前事务读取到其他事务未提交的数据;读取到垃圾数据.


(2)不可重复读


看这个案例

事务A这次比较聪明,快速把数据读取了一下, 注意此时事务A读到的年龄数是20;
事务B也要出手了,它先把数据更新了一下,然后提交数据;
事务A又出手了;它又去读取数据,结果读取到了年龄数为22.
事务A想不明白,就在自己这一个事务中进行了两次读取操作,按理说读取到的不应该数据一致吗?

A事务开启后 读取两次数据,结果两次读到的内容不一样( 预期的效果是A在同一个事务中读取到数据应该是一样的)


(3)幻读


看这个案例.

事务A先去读取了年龄大于18岁的数据; 读取到了一行;注意事务A还没结束;
这时事务B出手了,它添加了一行数据; 然后提交.
事务A又去读取年龄大于18岁的数据,读取到了两行,

哎呀,事务A出现幻觉了,咋在一次事务中读取到了两个不同的呢.
幻读可以这么理解.

A事务开启后, 读取到的两次数据数量不一致


1.3事务的4个隔离级别


查看隔离级别

SELECT @@global.transaction_isolation,@@transaction_isolation;

设置隔离级别

#这里SESSION(设置当前会话)/GLOBAL(设置全局) 设置一个就行
SET SESSION/GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别
  • 读 ---未提交 (read uncommitted):A 事务可以读到B事务未提交的数据 ;会有脏读,幻读不可重复读问题,几乎不用这个.

  • 读---已提交 (read committed): A不能读到B未提交的数据,只能读到B已提交的数据; 可解决脏读问题, 但是解决不了不可重复读问题.

  • 可重复读 (repeatable read): A事务开启后,第一次读到某个数据后,那么在这个事务中,第二次再查询同样的数据时,和原来是一致,属于重复读, (咱们数据库默认用的就是可重复读级别,注意下,咱们用的mysql8已经解决了幻读问题).
    [这边重复读可以理解为一种快照机制,它会把第一次读取的数据拍照存储,后面读取的时候就不是在数据库读取,而是读取快照中的存储信息,一会可以了解一下MVCC机制]

  • 串行化 (serializable): 解决所有问题, 一次只允许一个事务进行操作; 是最安全的,效率是最低的.



实际操作看看吧;

# 查看隔离级别;
SELECT @@global.transaction_isolation,@@transaction_isolation;


(1)试试读--未提交案例

(1)首先试试 读--未提交 隔离级别

搭建一个脏读案例

首先将当前数据库设置为读未提交级别

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

开两个命令窗口;暂且称呼左边为事务A;右边为事务B;
在数据表t_user进行操作;
首先开启事务A,开启事务B;


然后在事务B中添加一行数据,然后在事务A中读取数据;
注意啊,事务B还没有结束啊;这时事务A就跑去读取数据了;


然后事务B回滚操作了; 这属于是脏读案例了;
事务A中读取数据,没读到


(2)试试读--已提交案例

(2)试试 读--已提交 隔离级别

搭建不可重复读案例

首先将当前数据库设置为读,已提交级别

#设置为 读 已提交级别;
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

同样地,开启事务A,开启事务B;

首先在事务B中添加数据;但是没有提交;

然后在事务A中查询读取数据,并没有读取到数据;(这就是读-已提交级别的魅力)

首先,事务B提交;
这时事务A才能读取到数据;

虽然说解决了脏读的问题;但是这里出现了不可重复读的问题;

嘿嘿,这个案例还可以继续试试,加强理解;

开启事务A,读取数据表的数据;(注意小智的年龄是22岁)
开启事务B;

然后在事务B中修改了数据,然后进行了提交;

然后在事务A中再次读取数据;发现数据不一致了;
也就是不可重复读问题;


(3)试试可重复读案例

(3)试试 可重复读 隔离级别

嘿嘿,这可是数据库的默认隔离级别哦

先设置为可重复读的隔离级别;

#设置为 可重复读级别;
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;


当然,开启事务A,开启事务B;

事务A中; 先看下数据表t_user的当前数据;

然后这时,在事务B中执行修改数据; 将小智修改为100岁;
进行事务B提交;

然后,注意啊,咱们这时候呢,事务A还没结束呢;
咱们让事务A再次读取数据;发现小智的年龄还是原来的数据 23岁;
也就是说,这可是解决了不可重复读的问题哦,
这就是可重复读级别的厉害之处.

好了,现在把事务A提交了,读取数据;
已经是修改后的数据了.


当然,再试试实践,看看是不是解决了幻读问题;

开启事务A,开启事务B;

在事务A中首先读取数据;
注意读取到了一行数据;

然后在事务B中执行一个添加数据;并没有提交;
这时在事务A中;再次查询数据;注意读取的数据行为1行;

这时提交事务B;

在事务A中,再次查询数据,还是一行数据;

然后提交事务A;
开启一个新事务,查询读取数据;这才读取到两行数据;

幻读问题解决了;


2. 浅入MVCC(多版本并发控制 Multi-Version Concurrent Control)


,配合 Undo log 和版本链,替代锁,让不同事务的读-写、写-读操作可以并发执行,达到提升系统性能的效果.

可提升读-写,写-读两个操作同时进行

注意:写-写 操作互斥mysql支持行级锁的,(行级锁下篇笔记再记录);
好家伙,要是多个操作同一个数据,那不行啊,要出大问题的;

MVCC常用于: 读已提交(READ COMMITTED可重复读(REPEATABLE READ)隔离级别的事务

比如咱们两个事务,一个读一个取是没问题的;

要是同一时间一起写,就有大问题了;


版本链问题

InnoDB 存储引擎数据表的聚簇索引记录中包含着必须隐藏列:

trx_id:当我们对某条记录进行改动时,会把对应的事务 id 赋值给trx_id 隐藏列

roll_pointer
每次对表中的记录操作时,会保存到日志(undolog) 里面;会记录当前事务的id号,就像是一个指针;后面可以通过记录查询到修改前的信息。如果有多个事务操作时,他们就会根据事务id,找到自己操作的版本记录.

注意:每行数据都存在一个版本,每次数据更新时都更新该版本, 然后在事务中进行数据更新时,首先会复制一份版本记录,里面记录修改的数据版本记录;(感觉就像版本控制工具的分支);
最终;要是提交了事务就会覆盖原记录;要是回滚事务就删掉执行的记录版本.

每次更新版本记录后,就会把将旧版本的值放到一条 undolog 中,跟随着更新的次数叠加;所有的版本都会被 roll_pointer 属性连接成一个链表,就是版本链.

  • 每个版本节点中还会包括该版本时对应的事务 id,明白是哪个事务修改的版本;
  • 版本链的头节点就是当前记录最新的值。

READ COMMITTED(读已提交)每次读取数据前都生成一个 ReadView(临时读视图),
由于读已提交会产生不可重复读问题, 只要事务的中数据发生改变,版本链中也会发生修改, 每次读的时候ReadView中的数据就发生改变.

REPEATABLE READ(可重复读)在第一次读取数据时生成一个 ReadView(临时读视图),在后面就算数据或者版本链发生变化也没事,因为第一次读的时候,已经做好拍照了,当前事务中,要是再查数据,就以快照中的数据为准.

ReadView (临时读视图) 主要包含当前系统中还有哪些活跃的读写事务,会将事务 id 存入列表m_ids中 。
所以呢,在开启一次会话进行 SQL 读写时,开始事务就生成ReadView,把当前系统中正在执行的写操作存入到m_ids 列表.


以上是关于重点知识学习(9.3)--[浅入MySQL数据库事务,浅入MVCC]的主要内容,如果未能解决你的问题,请参考以下文章

小知识点记录:mysql 的 MVCC机制

重点知识学习(9.4)--[浅入MySQL数据库锁机制以及SQL优化]

重点知识学习(9.2)--[MySQL数据库索引,浅入数据库引擎]

Flutter帧率监控 | 由浅入深,详解获取帧率的那些事

重新学习MySQL数据库2:『浅入浅出』MySQL 和 InnoDB

重新学习Mysql数据库2:『浅入浅出』MySQL 和 InnoDB