MySQL-事务

目录

事务有什么特性?

事务的隔离性如何保证?

事务的持久性如何保证?

事务的原子性如何保证?

[MySQL事务和Redis 事务有什么区别?](#MySQL事务和Redis 事务有什么区别?)

MySQL事务隔离级别有哪些?分别解决哪些问题?(先说问题再说隔离级别)

MySQL默认的隔离级别是什么?怎么实现的?

事务的各个隔离级别都是如何实现的?

读未提交是如何实现的?

读已提交&可重复读是如何实现的?

串行化是如何实现的?

介绍一下MVCC

MVCC的如何判断行记录对某一个事务是否可见?

[如果两个 A B 事务并发修改一个变量,那么 A 读到的值是什么,怎么分析。(根据隔离级别进行分析)](#如果两个 A B 事务并发修改一个变量,那么 A 读到的值是什么,怎么分析。(根据隔离级别进行分析))

[读已提交和可重复读隔离级别实现 MVCC的区别?](#读已提交和可重复读隔离级别实现 MVCC的区别?)

为什么互联网公司用读已提交隔离级别?

可重复读隔离级别是如何解决不可重复读的?

可重复读隔离级别是怎么解决幻读的?

可重复读隔离级别解决了什么问题?有没有完全解决幻读?

可重复读隔离级别为什么不能完全避免幻读?什么情况下出现幻读?

可重复读隔离级别,MVCC完全解决了不可重复读问题吗?

修改隔离级别


事务有什么特性?

MySQL事务有 ACID 四大特性,分别是原子性、一致性、隔离性、持久性。

  1. 原子性的意思是事务中的所有操作要么全部完成,要么全部不完成,不会结束在中间某个环节,原子性是由 undo log 日志保证的;
  2. 一致性的意思是事务执行前后,数据库的状态必须保持一致性,一致性是由通过持久性+原子性+隔离性这三个共同保证的:
  3. 隔离性的意思是多个并发事务之间需要相互隔离,即一个事务的执行不能被其他事务干扰 。隔离性是由MVCC 和锁机制保证的
  4. 持久性的意思是保证事务完成后对数据的修改就是永久的。即使发生系统崩溃,修改的数据也不会丢失。持久性是由 redo log 日志保证的;

事务的隔离性如何保证?

事务的隔离性是由MVCC 和锁保证的

可重复读隔离级别下的快照读(普通select),通过 MVCC 来保证事务隔离

当前读 (update、select ... for update) 是通过行级锁来保证事务隔离性的。

事务的持久性如何保证?

事务的持久性是由 redo log保证的,因为 MySQL通过 WAL (先写日志再写数据)机制,在修改数据的时候,会将本次对数据页的修改以 redo log 的形式记录下来,这个时候更新就算完成了。

Buffer Pool 的脏页会通过后台线程刷盘,即使在脏页还没刷盘的时候发生了数据库重启,由于修改操作都记录到了 redo log ,之前已提交的记录都不会丢失 **,**重启后就通过 redo log,恢复脏页数据,从而保证了事务的持久性。

事务的原子性如何保证?

事务的原子性是通过 undo log 实现的,在事务还没提交前,历史数据会记录在 undo log中,如果事务执行过程中,出现了错误或者用户执行ROLLBACK 语句,MySQL可以利用undo log 中的历史数据,将数据恢复到事务开始之前的状态,从而保证了事务的原子性。

MySQL 事务和Redis 事务有什么区别?

Redis事务没保证原子性和持久性

  1. MySQL事务能够实现ACID四大特性,而Redis事务没保证原子性和持久性
  2. Redis 事务没有回滚功能,没办法实现跟MySQL事务一样的原子性,就是没办法保证事务执行期间,要不全部失败,要不全部成功。Redis 事务执行过程中,如果中途有命令执行出错了,不会停止和回滚,而是继续执行,那么就可能出现半成功的状态
  3. Redis 不管是 AOF 模式,还是 RDB 快照,都没办法保证数据不丢失,所以 Redis 事务不具有持久性。

MySQL 事务隔离级别 有哪些?分别解决哪些问题? (先说问题再说隔离级别)

MySQL共有四个隔离级别如下:

  1. 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到; 出现" 脏读 "、" 不可重复读 "和" 幻读 "的问题
  2. 读已提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到; 可以避免" 脏读 "现象。但 不可重复读 幻读 问题仍然存在。
  3. 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别 ; 避免了" 脏读 "和" 不可重复读 ",但可能会出现 幻读
  4. 串行化(serializable ),会对记录加上读写锁,通过强制事务串行执行来避免并发问题,可以解决" 脏读 "、" 不可重复读 "和" 幻读 "问题。但会导致大量的超时和锁竞争问题。

隔离水平由低到高 1-4

脏读 不可重复读 幻读 的意思:

  1. 脏读是指一个事务读取了另一个事务还未提交的数据,如果另一个事务回滚,则读取的数据是无效的。
  2. 不可重复读是指在同一事务中执行相同的查询时,返回的结果集不同。这是由于在事务过程中,另一个事务修改了数据并提交。
  3. 幻读是指在同一事务中执行相同的查询时,返回的结果集中出现了之前没有的数据行。这是因为在事务过程中,另一个事务插入了新的数据并提交。

MySQL默认隔离级别是可重复读,除此之外,我还知道 MySQL还支持读未提交、读已提交、串行化 这三个隔离级别。

我了解到事务并发问题有 脏读 不可重复读 幻读,不同的隔离级别,解决的问题也各不同的。

  1. 读未提交一个问题都没有解决
  2. 读已提交避免了脏读问题,但是还存在不可重复读和幻读这两个问题
  3. 可重复读避免了脏读和不可重复读的问题,不过对于幻读问题是很大程度上避免了,没有完全避免。
  4. 串行化是所有问题都可以避免,但是事务的并发性能是最差的。

MySQL 默认的隔离级别是什么?怎么实现的?

MySQL默认的隔离级别是可重复读

select 查询是通过 MVCC 实现的,在 MVCC 实现中,每条记录都会保存多个版本,每个版本都有一个版本号,事务在读取数据时,会根据事务开始时的版本号来读取数据,从而保证了事务的隔离性。

可重复读隔离级别是在开启事务后,执行一条 select 语句的时候,会生成一个Read View,后续事务查询数据的时候都在复用 Read View,所以保证了事务期间多次读到的数据都是一致的。

事务的各个隔离级别都是如何实现的?

读未提交是如何实现的?

不提供任何锁机制来保护读取的数据,允许读取未提交的数据(即脏读)。

读已提交&可重复读是如何实现的?

读已提交和可重复读通过 MVCC 机制中的 ReadView 来实现。

  • READ COMMITTED:每次读取数据前都生成一个 ReadView,保证每次读操作都是最新的数据。
  • REPEATABLE READ:只在第一次读操作时生成一个 ReadView,后续读操作都使用这个 ReadView,保证事务内读取的数据是一致的。
串行化是如何实现的?

事务在读操作 时,必须先加表级 共享锁 ,直到事务结束才释放;事务在写操作 时,必须先加表级排他锁,直到事务结束才释放。

串行化隔离级别的特点是:

  1. 所有读操作都需要加锁:在串行化隔离级别下,读操作不仅仅是简单的读取数据,还会使用锁来防止其他事务同时修改或插入数据。这种锁通常是表级共享锁(S lock)。
  2. 所有写操作都需要加锁:写操作在串行化隔离级别下,需要使用表级排他锁(X lock)。这意味着当一个事务在写数据时,其他事务无法对该表进行读或写操作。

介绍一下MVCC

  1. MVCC 是多版本并发控制 ,是通过记录历史版本数据,解决读写并发冲突问题,避免了读数据时加锁,提高了事务的并发性能
  2. 在传统的锁机制中,如果一个事务正在写数据,那么其他事务必须等待写事务完成才能读数据,MVCC 允许读操作访问数据的一个旧版本快照,同时写操作创建一个新的版本,这样读写操作就可以并行进行,不必等待对方完成。
  3. MySQL将历史数据存储在 undo log 中,结构逻辑上类似一个链表,MySQL数据行上有两个隐藏列,一个是事务trx_ID,一个就是指向 undo log 的指针。
  4. 事务开启后,执行第一条select(读)语句的时候会创建 ReadView ,ReadView 记录了当前未提交的事务,通过与历史数据的事务ID比较,就可以根据可见性规则进行判断,判断这条记录是否可见,如果可见就直接将这个数据返回给客户端,如果不可见就继续往undo log 版本链查找第一个可见的数据。

trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里;

在创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:

roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。

MVCC的如何判断行记录对某一个事务是否可见?

ReadView 有四个重要的字段:

m_ids:指的是在创建 Read View 时,当前数据库中[活跃事务]的事务id 列表,注意是一个列表,"活跃事务"指的就是,启动了但还没提交的事务

min_trx_id:指的是在创建 Read View 时,当前数据库中 活跃事务中事务id 最小的事务,也就是 m_ids的最小值。

max_trx_id:这个并不是m_ids 的最大值,而是创建Read View 时当前数据库中应该给下一个事务的id 值,也就是全局事务中最大的事务id 值 +1

creator_trx id:指的是创建该 Read View 的事务的事务id。


我们每一条记录都有两个隐藏列,一个是事务 id,一个是指向历史数据 undo log 的指针,然后 Read View 有四个字段,分别是创建 Read View 的事务 id、活跃事务 id 列表、活跃事务 id 列表中最小的 id、下一个事务的 id。

主要有这几种判断规则:

  • 如果记录的事务 id 小于活跃事务 id 列表中最小的 id,就说明该记录是在创建Read View 前就生成好了,所以该记录是当前事务是可见的。
  • 如果记录的事务id 大于下一个事务的 id,就说明该记录是在创建 Read View 后才生成的,所以该记录是当前事务是不可见的。
  • 如果记录隐藏列的事务 id 在最小的 id 和下一个事务的id之间,这时候就需要看记录的事务id是否在活跃事务id列表中:
    • 如果记录的事务id 在活跃事务 id 列表中,说明修改该记录的事务还没提交,所以该记录是不可见的。
    • 如果记录的事务id不在活跃事务id 列表中,说明修改该记录的事务已经提交了那么该记录就是可见。

活跃id事务是启动未提交的列表,事务提交就不在该列表内了

如果两个 A B 事务并发修改一个变量,那么 A 读到的值是什么,怎么分析。(根据隔离级别进行分析)

当两个事务 A 和 B 并发修改同一个变量时,A 事务读取到的值取决于多个因素,包括事务的隔离级别、事务的开始时间和提交时间等。

  • 读未提交:在这个级别下,事务可以看到其他事务尚未提交的更改。如果 B 更改了一个变量但尚未提交,A 可以读到这个更改的值。
  • 读提交:A 只能看到 B 提交后的更改。如果 B 还没提交,A 将看到更改前的值。
  • 可重复读:在事务开始后,A 总是读取到变量的相同值,即使 B 在这期间提交了更改。这是通过 MVCC 机制实现的。
  • 可串行化:A 和 B 的操作是串行执行的,如果 A 先执行,那么 A 读到的值就是 B 提交前的值;如果 B 先执行,那么 A 读到的值就是 B 提交后的值。

读已提交和可重复读隔离级别实现 MVCC的区别?

读已提交和可重复读隔离级别都是由MVCC实现的,它们的区别在于创建 Read View 的时机不同。

  1. 读已提交隔离级别在事务开启后, 每次执行 select 都会生成一个新的 Read View,所以每次 select 都能看到其他事务最近提交的数据
  2. 可重复读隔离级别在事务开启后,执行第一条 select 时生成一个 Read View,然后整个事务期间都在复用这个 Read View,所以一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的。

为什么互联网公司用读已提交隔离级别?

  1. 读已提交的并发性能更好,因为读已提交没有间隙锁,只有记录锁,发生死锁的概率比较低。而可重复读是会有记录锁和间隙锁,所以读已提交隔离级别发生死锁的概率比较小
  2. 然后互联网业务对于幻读和不可重复读的问题都是能接受的,所以为了降低死锁的概率,提高事务的并发性能,都会选择使用读已提交隔离级别。

可重复读 隔离级别是如何解决 不可重复读 的?

分两种查询来回答

  • 快照读,靠MVCC解决不可重复读
  • 当前读,靠行级锁中的记录锁解决不可重复读

针对快照读的话,是通过 MVCC 机制来解决的,在可重复读隔离级别下,第一次select查询的时候,会生成 readview,在第二次执行select查询的时候,会复用这个readview,这样前后两次查询的记录都是一样的,不会读到其他事务更新的操作,这样就不会发生不可重复读的问题了。

针对当前读的话,是靠行级锁中的记录锁来实现的 ,在可重复读隔离级别下,第一次select for update 语句查询的时候,会对记录加next-key 锁,这个锁包含记录锁,这时候如果其他事务更新了加了锁的记录,都会被阻塞住,这样就不会发生不可重复读的问题

可重复读 隔离级别是怎么解决 幻读 的?

分两种查询来回答

快照读,靠MVCC解决幻读

当前读,靠行级锁中的间隙锁解决幻读

回答

针对快照读的话,是通过 MVCC 机制来解决的,在可重复读隔离级别下,第一次select查询的时候,会生成 readview,在第二次执行select查询的时候,会复用这个readview,这样前后两次查询的结果集都是一样的,不会读到其他事务新插入的记录,这样就不会发生幻读的问题了。

针对当前读的话,是靠行级锁中的间隙锁 来实现的,在可重复读隔离级别下,第一次select for update 语句查询的时候,会对记录加next-key 锁,这个锁包含间隙锁,这时候如果其他事务往这个间隙插入新记录的话,都会被阻塞住,这样就不会发生幻读的问题了。

可重复读隔离级别解决了什么问题?有没有完全解决幻读?

可重复读隔离级别解决了脏读、不可重复读问题,幻读也很大程度上避免了,但是我觉得并没有完全解决幻读,在一些特殊的场景,还是会发生幻读的问题。

可重复读隔离级别为什么不能完全避免幻读?什么情况下出现幻读?

在可重复读隔离级别场景下,当先快照读再当前读的场景下可能会出现幻读的问题。

比如说这个场景:

事务A 通过快照读的方式查询id =5的记录,此时数据库没有这条记录,然后事务 B 向这张表中新插入了一条 id =5的记录并提交了事务。

接着,事务A对id = 5这条记录进行了更新(当前读)操作,在这个时刻,这条新记录隐藏列中的事务id就变成了事务A 的事务 id,这时候事务 A 再使用 select 语去查询这条记录时就可以看到这条记录了,这里事务 A前后两次查询的结果集合数不一样了,于是就发生了幻读

我还知道另外一个场景,事务A 通过快照读的方式查询d 大于 100的记录,假设这时候有1条记录,然后事务B 插入了 id = 200的记录并提交了事务,接着事务A 通过当前读的方式查询 id 大于 100的记录,这时候就会得到 2条记录,事务 A 前后两次查询的结果集合数不一样了,就发生了幻读

我觉得上面这两种发生幻读的场景,也是可以避免的,就是尽量在开启事务之后,马上执行 select ... for update 语句,因为它会对记录加临键锁 (next-key 锁),这样就可以避免其他事务插入一条新记录,就避免了幻读的问题

可重复读隔离级别,MVCC完全解决了不可重复读问题吗?

如果前后两次查询都是快照读 ,就是普通的 select 的话,那就不会产生不可重复读的问题的。但是如果第一次查询是快照读,第二次查询是当前读,那么就可能会发生不可重复读的问题。

修改隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL [隔离级别];

自己整理,借鉴很多博主,感谢他们

相关推荐
漠北银虎1 小时前
【学习分享】应用架构之持久化数据状态管理
数据库·学习·架构
柴...1 小时前
MySql
数据库·mysql
乐乐吃不饱2 小时前
android之SQLiteDatabase
数据库·oracle
小孔_H2 小时前
MySQL 系统学习系列 - SQL 语句 DML 语句的使用《MySQL系列篇-02》
sql·学习·mysql
wbo1123 小时前
mac上编译redis ,报错fstat64
数据库·redis·macos
zcccsss3 小时前
Redis一些问题
java·数据库·redis·缓存
CoderJia程序员甲4 小时前
MySQL字符串比较忽略尾随空格
数据库·mysql
牛燕飞4 小时前
海山数据库(He3DB)源码详解:He3DB-XLogWrite函数
数据库·oracle·性能优化
kali-Myon4 小时前
ctfshow-web入门-sql注入(web231-web236)update 注入
数据库·sql·学习·mysql·web安全·web·sql注入
妍妍的宝贝5 小时前
mysql高可用之组复制 (MGR)
linux·运维·服务器·数据库·mysql