Login
网站首页 > 文章中心 > 其它

MySQL 中的事务理解

作者:小编 更新时间:2023-09-06 23:32:48 浏览量:358人看过

前言

原子性

MySQL 中的事务理解-图1

一致性

持久性

并发事务存在的问题

脏读

幻读

不可重复读

隔离性

事务的隔离级别

事务隔离是如何实现

可重复读 和 读提交

串行化

MySQL 中的事务理解-图2

读未提交

可重复读解决了幻读吗

总结

参考

MySQL 中的事务

前言

MySQL 中的事务操作,要么修改都成功,要么就什么也不做,这就是事务的目的.事务有四大特性 ACID,原子性,一致性,隔离性,持久性.

A(Atomic),原子性:指的是整个数据库事务操作是不可分割的工作单元,要么全部执行,要么都不执行;

C(Consistent),一致性:指的是事务将数据库从一种状态转换成下一种一致性状态,在事务开始之前和事务结束之后,数据库的完整性约束没有被破坏;

数据的完整性: 实体完整性、列完整性(如字段的类型、大小、长度要符合要求)、外键约束等;

业务的一致性:例如在银行转账时,不管事务成功还是失败,双方钱的总额不变.

如果事务执行过程中,每个操作失败了,系统可以撤销事务,系统可以撤销事务,返回系统初始化的状态.

I(isolation): 隔离性还有其它称呼,如并发控制,可串行化,锁等,事务的隔离性要求每个读写事务对象对其他事务操作对象能够相互隔离,即事务提交之前对其它事务都不可见;

D(durability), 持久性: 指的是一旦数据提交,对数据库中数据的改变就是永久的.即使发生宕机,数据库也能恢复.

原子性

原子性:指的是整个数据库事务操作是不可分割的工作单元,要么全部执行,要么都不执行.

在对数据库进行修改的时候,就会记录 undo log ,这样当事务执行失败的时候,就能使用这些 undo log 恢复到修改之前的样子.

InnoDB 通过 undo log 进行事务的回滚,实际上做的是和之前相反的工作,对于每个 INSERT ,InnoDB 会生成一个 DELETE ;对于 DELETE 操作,InnoDB 会生成一个 INSERT...通过反向的操作来实现事务数据的回滚操作.实现事务的原子性.

一致性

MySQL 中的事务理解-图3

一致性:指的是事务将数据库从一种状态转换成下一种一致性状态,在事务开始之前和事务结束之后,数据库的完整性约束没有被破坏.

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

持久性

持久性: 指的是一旦数据提交,对数据库中数据的改变就是永久的.即使发生宕机,数据库也能恢复.

redo log 用来从保证事务的持久性.

redo log 简单点讲就是 MySQL 异常宕机后,将没来得及提交的事物数据重做出来.

redo log 包括两部分:一个是内存中的日志缓冲( redo log buffer ),另一个是磁盘上的日志文件( redo log file ).

MySQL 每执行一条 DML 语句,先将记录写入 redo log buffer,后续某个时间点再一次性将多个操作记录写到 redo log file .这种 先写日志,再写磁盘 的技术就是 MySQL 里经常说到的 WAL(Write-Ahead Logging) 技术.

有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe.

并发事务存在的问题

事务并发可能会出现下面几种问题,【脏读,不可重复读,幻读】 等情况.

脏读:读到其他事务未提交的数据;

不可重复读:前后读取的数据不一致;

幻读:前后读取的记录数量不一致.

The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a "phantom" row.

幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,比如这种修改涉及到表中的"全部数据行".同时,第二个事务也修改这个表中的数据,这种修改是向表中插入"一行新数据".那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样.

简单的讲就是,幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行.

不可重复读,是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据.

在一个事务内,多次读同一个数据.在这个事务还没有结束时,另一个事务也访问该同一数据并修改数据.那么,在第一个事务的两次读数据之间.由于另一个事务的修改,那么第一个事务两次读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,所以呢称为不可重复读,即原始读取不可重复.

幻读和不可重复读的的区别

不可重复读和幻读都是读的过程中数据前后不一致,只是前者侧重于修改,后者侧重于增删.

隔离性

MySQL 中标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable ).

读未提交:一个事务还没提交时,它的变更就能被别的事务看到,读取未提交的数据也叫做脏读;

读提交:一个事务提交之后,它的变更才能被其他的事务看到;

可重复读:MySQL 中默认的事务隔离级别,一个事务执行的过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的,在此隔离级别下,未提交的变更对其它事务也是不可见的,此隔离级别基本上避免了幻读;

串行化:这是事务的最高级别,顾名思义就是对于同一行记录,"写"会加"写锁","读"会加"读锁".当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行.

串行化,不是所有的事务都串行执行,没冲突的事务是可以并发执行的.

下面来详细的介绍下 读提交 和 可重复读

准备下数据表

create table user
(
id int auto_increment primary key,
username varchar(64) not null,
age int not null
);
insert into user values(2, "小张", 1);

来分析下下面的栗子

MySQL 中的事务理解

读提交

可重复读

在了解了四种隔离级别,下面来聊聊这几种隔离级别是如何实现的.

首先来介绍一个非常重要的概念 Read View.

Read View 是一个数据库的内部快照,用于 InnoDB 中 MVCC 机制.

可重复读 和 读提交 主要是通过 MVCC 来实现,MVCC 的实现主要用到了 undo log 日志版本链和 Read View.

undo log 日志版本链

undo log 是一种逻辑日志,当一个事务对记录做了变更操作就会产生 undo log,里面记录的是数据的逻辑变更.

对于使用 InnoDB 存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列.

trx_id:每次对某条聚簇索引记录进行改动时,都会把对应的事务 id 赋值给 trx_id 隐藏列;

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

MySQL 中的事务理解

每次事务更新的时候,undo log 就会用 trx_id 记录下当前事务的事务 ID,同时记录下当前更新的数据,通过 roll_pointer 指向上个更新的旧版本数据,这样就形成了一个历史的版本链.

Read View

undo log 版本链会将历史事务进行快照保存,并且根据事务的版本大小,通过指针串联起来,对于 可重复读 和 读提交 这两种事务隔离级别,只需要在 undo log 中选择合适的事务版本进行数据读取,就能实现对应的读取隔离效果.

判断 undo log 版本链中,那个事务版本对当前事务可见,InnoDB 中通过 Read View来解决,作用是事务执行期间用来定义"我能看到什么数据".

Read View 中有四个重要的字段

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.

MySQL 中的事务理解

Read View 可以在理解为一个数据的快照,可重复读隔离级别会在每次启动的事务的时候生成一个 Read View 记录下当前事务启动瞬间,当前所有活跃的事务 ID.

创建该 Read View 的事务的事务 ID,会在 Read View 中根据事务 ID 大小,判断当前事务落在了那个区域,然后判断当前事务 ID 对应的数据快照是否可读.

已提交的事务:对于当前事务来讲,因为都是已经提交或者是当前事务生成的,这部分数据都是可见的;

未开始事务:Read View 中应该给下一个事务的 ID,这部分的数据是不可见;

未提交事务集合:这种有下面两种情况;

①.、如果当前事务 ID 在未提交事务集合中,表示这个版本是由还没提交的事务生成的,不可见;

总结下来就是

①.、首先创建当前 Read View 时的事务 ID 会判断当前事务落在了 Read View 中那个区域中;

可重复读 和 读提交 事务隔离级别的区别就在于创建 Read View 的时机不同,可重复读事务隔离级别会在每次启动事务的时候创建 Read View,读提交 会在每次查询的时候创建 Read View.

因为可重复读事务隔离级别在事务开始创建了 Read View,就能保证事务中的看到的数据一致了,而读提交事务隔离级别在每次查询的时候,创建 Read View,就能在每次查询的时候读到已经提交的事务数据.

下面来个栗子具体分析下可重复读隔离级别的读取过程

MySQL 中的事务理解

栗如,上面的三个事务,在可重复读隔离级别中,事务的查询结果

这里具体的分析下,假定现在有下面几种场景

这样三个事务的 Read View 分别是

MySQL 中的事务理解

这时候事务1 的查询,事务1 中的事务版本号是100.在事务1 Read View 中的未提交事务集合中,所以数据不可见,需要根据版本链寻找上一个版本.

总结下就是,一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

①.、版本未提交,不可见;

数据更新

更新数据都是先读后写的,读使用的是当前读,每次读取的内容都是最新的.

除了update语句外,select语句如果加锁,也是当前读.例如,加上 读锁(S锁,共享锁) lock in share mode 或 写锁(X锁,排他锁)for update.

select age from user where id=2 lock in share mode;

select age from user where id=2 for update;

MySQL 中的事务理解

什么是两阶段锁协议:在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放.这个就是两阶段锁协议.

事务更新数据的时候,只能用当前读.如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待.

而读提交的逻辑和可重复读的逻辑类似,它们最主要的区别是:

在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;

在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图.

读写都需要加锁,读的时候加读锁,写的时候加写锁.

读取最新的数据,读不用加锁,不用遍历版本链,直接读取最新的数据,不管这条记录是不是已提交.不过这种会导致脏读.

对写仍需要锁定,策略和读已提交类似,避免脏写.

可重复读解决了幻读吗

MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:

针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题.

针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题.

可重复读是如何解决幻读的呢?

读操作,可重复读隔离级别会使用 MVCC 针对当前事务生成事务快照,通过对比事务版本,就能保证事务中的快照读.

读写操作,这种就需要借助于 Next-Key,MySQL 把行锁和间隙锁合并在一起,解决了并发写和幻读的问题,这个锁叫做 Next-Key 锁.

Next-Key 算法中,对于索引的扫描,不仅仅是锁住扫描到的索引,而且还锁着这些索引覆盖的范围.所以呢对于范围内的插入都是不允许的,这样就能避免幻读的发生.

create table user
(
id int auto_increment primary key,
username varchar(64) not null,
age int not null
);
insert into user values(2, "小张", 1);
insert into user values(4, "小明", 1);
insert into user values(6, "小红", 1);
insert into user values(8, "小白", 1);

MySQL 中的事务理解

事务 1 中的前后两次查询,就不会出现幻读的情况了.

MySQL 中的事务理解

不过可重读真的就完全避免了幻读吗?下面来看两种异常的情况.

场景1

MySQL 中的事务理解

MySQL 中的事务理解

上面两种场景的最终原因,就是后面的查询间接或直接的使用了当前读,造成了数据的不一致,所以只需要在最开始的查询加上 for updated,就能避免幻读的出现了.

总结

①.、MySQL 中的事务隔离级别分别是

参考

以上就是土嘎嘎小编为大家整理的MySQL 中的事务理解相关主题介绍,如果您觉得小编更新的文章只要能对粉丝们有用,就是我们最大的鼓励和动力,不要忘记讲本站分享给您身边的朋友哦!!

版权声明:倡导尊重与保护知识产权。未经许可,任何人不得复制、转载、或以其他方式使用本站《原创》内容,违者将追究其法律责任。本站文章内容,部分图片来源于网络,如有侵权,请联系我们修改或者删除处理。

编辑推荐

热门文章