MySQL 事务

目录

事务四大特性

[保证事务的四大特性(InnoDB 为例)](#保证事务的四大特性(InnoDB 为例))

并发处理事务存在的问题?怎样解决

事务隔离级别

不可重复读和幻读区别

行锁、表锁和间隙锁

MVCC

[RR幻读问题 + MVCC 有幻读问题吗?](#RR幻读问题 + MVCC 有幻读问题吗?)

彻底解决幻读问题


首先我们来举个例子:1 给 2 转账 500 元

sql 复制代码
1)update account set balance = balance - 500 where id = 1;
2) update account set balance = balance + 500 where id = 2;

假设,在执行转账过程中,执行完1之后,数据库崩溃了,此时这个转账就僵硬了!1的钱扣了,但是2的钱没到账!这是不合理的,所谓事务事务就是为了解决上述这类问题:当第一个sql执行完之后,数据库崩溃,当下次数据库重新启动完成之后,就会自动地把上次修改一半的数据给进行还原------把 1 用户 -500 再加回来就行了


事务:把多个 sql 语句打包成一个整体,要么全部执行成功,要不一个都不执行,而不会出现执行一半这样的中间状态(不是真正不执行,而是如果执行一半出错了,出错之后把数据还原成为执行之前的状态)

事务四大特性

  • 原子性【最核心的特征】:事务中的操作要么全部执行成功,要不全部失败回滚,不能执行其中的一部分;如果把转账的两个操作作为一个事务,当第一个sql执行完之后,数据库崩溃,下次数据库重新启动完成之后,就会自动的把上次修改一半的数据给进行还原(把1号用户-500再加回来);进行回滚的时候,这里需要额外的部分记录事务中的操作步骤,数据库里有一个专门记录事务的日志,根据日志进行操作
  • 一致性:事务执行前后,数据库的完整性没有被破环,数据总是从一个一致的状态转移到另一个一致状态。例如A给B转账,无论事务成功与否,最终 A 和 B 的总金额应该保持不变
  • 隔离性:事物之间是相互隔离的,每一个事务对其他事务的操作是透明的,一个事务的中间结果对其他事务是不可见的。隔离性可以防止并发执行的事务之间产生脏读、不可重复读和幻读等问题。
  • 持久性:事务修改的内容是写在硬盘上的,持久存在的,重启也不会丢失

保证事务的四大特性(InnoDB 为例)

  • 原子性通过**undo log(回滚事务)**保证:InnoDB 使用日志来记录事务的操作,包括事务开始、修改数据、事务提交等。如果事务执行失败或者回滚,InnoDB 可以使用日志来撤销已经执行的操作,保证事务的原子性
  • 持久性通过 **redo log(重做日志)**保证:在事务提交之前,InnoDB 会将事务修改数据写入事务日志中,再写入磁盘中,即使再系统崩溃或者断电的情况下,InnoDB 可以通过重放事务日志来恢复数据,确保事务的持久性
  • 隔离性通过 MVCC(多版本并发控制)和锁的机制来保证
  • 一致性是通过各种约束,如主键、外键、唯一性约束等,加上事务的持久性、原子性和隔离性来保证

并发处理事务存在的问题?怎样解决

并发:服务器同时处理多个客户端的请求

  • 脏读问题:事务A正在对数据进行修改数据的过程中,还没有提交之前,另外一个事务B,也对同一个数据进行了读取,此时B的操作就称为"脏读",读到的数据也称为"脏数据"(脏的意思就是"无效",很可能A回头就把数据改了)

例如:我在写代码,但是写的过程中,有一个人从我背后走过看了一眼我的屏幕,并且看到了我写的内容,然后他就走了;但是很可能他走之后我的代码进行了修改,最后我代码展示出来,他发现和他看到的代码不一样,这就是脏读问题

  • 解决方法(写加锁):MySQL 引入 "写加锁"操作:降低并发程度(降低了效率),提高隔离性(提高了数据准确性)

例如:我和同学商量好,我写代码的过程中,你别来看,等我改完提交代码至码云上之后,你再通过我的码云来看;分析:写的时候不能看(给写操作加锁),写完了才能看------意味着我的"写操作"和同学的"读操作"不能并发了(不能同时执行)

  • 不可重复读:事务1已经提交数据,此时事务2开始读取数据,在读的过程中事务3又提交了新的数据,此时意味着同一个事务2多次读取数据,读取结果是不相同的(预期是一个事务中,多次读取结果是一样的),这个过程叫做"不可重复读"

例如:我写代码给同学看,约定好我写的时候不许看,等我提交之后,再通过码云来看(约定好写加锁),写好之后,提交了版本1,此时就有同学开始读这个代码;但是我又打开代码继续修改代码,然后又提交了版本2.这个时候就会有一个问题,这个同学读的过程中,读到的是版本1的代码,读着读着我提交了版本2,此时这个同学读的代码,刷的一下变了,这个问题就叫做"不可重复读"

  • 解决问题(读加锁):进一步降低了事务的并发处理能力(处理效率也降低),提高了事务的隔离性(数据的准确性提高了)

例如:同学发现这个问题,知道了在他读的过程中,我又改了代码,于是来找我约定:同学读代码的时候,我不能修改!!!(刚才约定的是我修改的时候,提交之前同学不要读, 是在给写加锁;现在约定的是同学读的时候,我不能修改,就是给读加锁)

  • 幻读:在读加锁和写加锁的情况下,一个事务两次读取同一个数据,发现读取的数据值是一样的,但是结果不一样,这样就称为"幻读"

例如:当前已经约定了读加锁和写加锁,解决了不可重复读和脏读问题;这个时候由于约定了读加锁,同学读的时候我不能修改代码,但是这个时候我已经没事做了,于是想了一个办法,同学读一个Student.java文件,则我再创建一个Student2.java文件,就先用这个文件写代码,这样大多数情况没问题,少数情况发现多了一个Student2.java文件

  • 解决问题(串行化):使用"串行化"的方式解决幻读,彻底放弃并发处理事务,一个一个的串行处理事务------并发程度是最低的(效率最慢),隔离性是最高的(准确性也是最高的)

例如:相当于同学要求,在他们读代码的时候,我不能碰电脑,强制停下来

事务隔离级别

事务的隔离级别是指多个并发事务之间的隔离程度,有四个级别:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)

  • 读未提交(Read Uncommitted):最低的隔离级别,事务中的修改,即使未提交也可以被其他事务读取到;优点:并发性能最好,读取到的数据最新;缺点:存在"脏读问题",读取到未提交的数据,可能导致数据不一致
  • 读已提交(Read Committed):保证事务读取到的数据是已经提交的,其他事务提交的数据对该事务可见;优点:避免了脏读问题;缺点:存在"不可重复读问题",同一个事务,不同时间读取到的数据可能不一样
  • 可重复读(Repeatable Read):保证事务中,多次读取到同一条记录时,读取到的数据时一致的,MySQL 默认的事务隔离级别;优点:避免了不可重复读;缺点:存在"幻读问题",即在一个事务中,两次查询同一个范围的记录,但第二次查询却发现了新的记录。
  • 串行化(Serializable):最高的隔离级别,将所有的事务串行执行,保证了数据的完全隔离;优点:避免了幻读的问题;缺点:并发性能最差,可能导致大量的锁等待和死锁。

通常情况下,可重复读是一个比较好的选择,能够较好地平衡数据一致性和并发性能。

不可重复读和幻读区别

  • 不可重复读主要涉及到修改数据;幻读主要涉及到插入或删除数据
  • 不可重复读指的是在同一个事务中,多次读取同一行数据,但是每次读取的结果都不同,这是因为在这个事务读取数据的期间,其他事务修改了这些数据所导致的
  • 幻读指的是在同一个事务中,多次执行相同的查询,但是每次查询返回的行数却不同,这是因为在这个事务读取数据的期间,其他事务插入了新的数据所导致的
  • 解决方案不同:不可重复读的解决方案通常是使用行锁 或者表锁 来解决,而幻读的解决方案通常是使用间隙锁来解决。

行锁、表锁和间隙锁

行锁、表锁和间隙锁是 InnoDB 中实现事务隔离级别的基本锁类型。

  • 行锁:对表中的某一行数据进行锁定,当有事务修改行数据时,其他事务无法同时修改行数据,从而保证了数据的一致性。行锁的粒度最细,并发性能的影响最小
  • 表锁:指对整张表进行锁定,当有事务对表中任意一行数据进行修改时,其他事务无法修改整张表中的任意一行数据,从而保证数据的一致性。表锁的粒度最大,对并发性能的影响最大
  • 间隙锁:对一个索引范围中的"空隙"进行锁定,防止其他事务在这个范围内插入新数据。间隙锁用于解决幻读问题

MVCC

MVCC(Multi-Version Concurrency Control,多版本并发控制) 是一种并发控制机制,用于在数据库系统中处理并发读写操作时保证数据的一致性和隔离性(主要是用来解决幻读问题的)。MVCC 通过在每个数据行上保存多个版本的数据来实现并发读取和写入的一致性。

  • MVCC核心思想:将每个事务的读操作和写操作解耦,通过保存数据的历史版本来实现并发控制。每个事务在开始时会创建一个读视图,用于确定在事务开始时可见的数据版本。读视图包含一个事务开始时的系统版本号,用于与数据行的版本号进行比较,以确定数据行是否对事务可见
  • 在 MVCC 中,当一个事务执行写操作时,会产生一个新的数据版本,并将旧的数据版本保存在回滚日志中,这样,其他事务在读取数据时仍然可以访问到旧版本的数据,从而避免幻读问题

MVCC 实现原理:简单来说 MVCC 是通过以下 3 大组件实现

  • 隐藏字段:每个执行的 SQL 命令都有几个隐藏的字段,其中有一个事务 ID 字段,很重要
  • undo log(回滚日志):里面记录了 SQL 命令执行的历史数据。
  • Read View(读视图):包含快照读(一个快照,保存了数据库某个时刻的数据)和一些重要的属性。

MVCC 工作流程:

  • 读操作:当一个事务执行 select 语句时,会根据读视图的版本号和数据行的版本号进行比较,只读取在事务开始之前已经提交的数据行。这样,即使在其他事务进行插入或者删除数据,事务仍然可以读取到一致的数据
  • 写操作:当一个事务执行 insert、update、delete 语句时,会产生一个新的版本,并将旧的版本数据保存在回滚日志中。这样,其他事务在读取数据时仍然可以访问到旧版本的数据,从而避免了幻读问题

总之:MVCC 机制在数据库系统中通过解耦合读操作和写操作,提高并发性和数据一致性,是的多个事务可以同时读取和修改数据库,而不会互相干扰

RR幻读问题 + MVCC 有幻读问题吗?

RR 代表 Repeatable Read(可重复读),是数据库事务隔离级别中的一种,它的特性是保证同一个事务中,多次读取同一条记录时,读取到的数据都是一致的。它也是 MySQL 默认的事务隔离级别

RR 隔离级别中存在两种读操作:

  • 快照读:数据库中的一种读取方式,它基于事务开始时的一个一致性快照来读取数据。快照读可以提供事务开始时的数据视图,即使在事务执行期间其他事务对数据进行修改也不会影响快照读取到的数据
  • 当前读:数据库中的一种读取方式,它读取到的是最新提交的数据,而不是基于事务开始时的一致性快照

因此,在 RR 隔离级别中 MVCC 通过快照读来解决大部分的幻读问题,但如果 RR 隔离级别存在当前读(使用 select ... for update 实现),那么此时也会发生幻读问题

彻底解决幻读问题

要想彻底解决幻读问题,有两个方法:

  • 使用 "串行化" 隔离级别:彻底放弃并发处理事务,一个一个的串行处理事务------并发程度是最低的(效率最慢),隔离性是最高的(准确性也是最高的)官方推荐
  • RR + 锁(临建锁):使用 RR 隔离级别,但是在事务开启之后立即加锁
  • 临建锁是行锁和间隙锁的组合,可以理解为一种特殊的间隙锁,它等于行锁+间隙锁,除了锁住记录本身,还会锁住索引之间的间隙,即锁定一段左开右闭的索引区间

事务一开启就加锁,之后其他事务在操作此表的相关数据时,就只能等待锁释放

相关推荐
指尖上跳动的旋律2 分钟前
shell脚本定义特殊字符导致执行mysql文件错误的问题
数据库·mysql
苹果醋34 小时前
2020重新出发,MySql基础,MySql表数据操作
java·运维·spring boot·mysql·nginx
先睡4 小时前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
呼啦啦啦啦啦啦啦啦6 小时前
【MySQL篇】事务的认识以及四大特性
数据库·mysql
溟洵8 小时前
Linux下学【MySQL】表中插入和查询的进阶操作(配实操图和SQL语句通俗易懂)
linux·运维·数据库·后端·sql·mysql
苹果醋313 小时前
React源码02 - 基础知识 React API 一览
java·运维·spring boot·mysql·nginx
别致的影分身14 小时前
使用C语言连接MySQL
数据库·mysql
过过过呀Glik14 小时前
在 Ubuntu 上安装 MySQL 的详细指南
mysql·ubuntu
Sunyanhui118 小时前
牛客网 SQL36查找后排序
数据库·sql·mysql
老王笔记18 小时前
MHA binlog server
数据库·mysql