文章目录
😊 @ 作者:Lion J
`💖 @ 主页:
https://blog.csdn.net/weixin_69252724`🎉 @ 主题: MySQL__事务)
⏱️ @ 创作时间:2024年04月26日
————————————————
## 什么是事务呢?
事务就是由单独单元的一个或多个sql语句组成,在这个单元中,每个sql语句都是相互依赖的。而整个单独单元是作为一个不可分割的整体存在,类似于化学当中的原子(一种不可分割的最小单位)。
往通俗的讲就是,事务就是一个整体,里面的内容要么都执行成功,要么都不成功。不可能存在部分执行成功而部分执行不成功的情况。
就是说如果单元中某条sql语句一旦执行失败或者产生错误,那么整个单元将会回滚(返回最初状态)。所有受到影响的数据将返回到事务开始之前的状态,但是如果单元中的所有sql语句都执行成功的话,那么该事务也就被顺利执行。
我们的数据都是通过各种不同技术的存储引擎来引导存储的,不同的存储引擎,都有各自的特点。在mysql中,常见的存储引擎有innodb、myisam等。其中innodb支持事务(transaction),而myisam.等不支持事务。
可以通过show engines;语句来查看mysql支持的存储引擎
show engines;
比如在生活中举个例子:
事务最经典也经常被拿出来说例子就是转账了。
假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作,这两个操作必须都成功或者都失败。
- 将小明的余额减少 1000 元
- 将小红的余额增加 1000 元。
事务会把这两个操作就可以看成逻辑上的一个整体,这个整体包含的操作要么都成功,要么都要失败。这样就不会出现小明余额减少而小红的余额却并没有增加的情况。
那数据库事务有什么作用呢??
简单来说,数据库事务可以保证多个对数据库的操作(也就是 SQL 语句)构成一个逻辑上的整体。构成这个逻辑上的整体的这些数据库操作遵循:要么全部执行成功,要么全部不执行 。开启一个事务(对于mysql8来说)
BEGIN 开始一个事务
update... insert... delete ...
ROLLBACK 事务回滚
COMMIT 事务确认
对于关系型数据库来说(mysql, sql server, oracle)事务来说都有,ACID特性
●原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
●一致性(Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
●隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
●持久性(Durability):一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
注意!!:
只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是需要满足的条件,C 是最终的目的!
并发事务会带来哪些问题呢?
在单体应用程序中(因为我对微服务项目不够深入__🤣),多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对同一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。
●脏读:
一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务没有提交。这时另外一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据,这也就是脏读的由来。
顾名思义就是读取到了需要丢弃的数据
例如:事务 1 读取某表中的数据 A=20,事务 1 修改 A=A-1,事务 2 读取到 A = 19,事务 1 回滚导致对 A 的修改并未提交到数据库, A 的值还是 20但是此时,事务2读取到的A仍然是A=19
●丢失修改:
在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 先修改 A=A-1,事务 2 后来也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。
●不可重复读:
指在一个事务内多次读同一数据。
在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
●幻读:
幻读与不可重复读类似。
不可重复读的重点是内容修改或者记录减少比如多次读取一条记录发现其中某些记录的值被修改;
**幻读的重点在于记录新增比如多次执行同一条查询语句\
时,发现查到的记录增加了。**
它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
例如:事务 2 读取某个范围的数据,事务 1 在这个范围插入了新的数据,事务 2 再次读取这个范围的数据发现相比于第一次读取的结果多了新的数据。
并发事务的控制有什么?
MySQL 中并发事务的控制方式无非就两种:
锁 和 MVCC。
- 锁可以看作是悲观控制的模式
- 多版本并发控制(MVCC,Multiversion concurrency control)可以看作是乐观控制的模式。
锁
锁控制方式下会通过锁来显示控制共享资源而不是通过调度手段,MySQL 中主要是通过 读写锁 来实现并发控制。
●共享锁:又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
●排他锁:又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条记录加任何类型的锁(锁不兼容)
读写锁可以做到读读并行,但是无法做到写读、写写并行。另外,根据根据锁粒度的不同,又被分为 表级锁和 行级锁 。
InnoDB引擎 不光支持表级锁,还支持行级锁,默认为行级锁。行级锁的粒度更小,仅对相关的记录上锁即可(对一行或者多行记录加锁),所以对于并发写入操作来说, InnoDB 的性能更高。
不论是表级锁还是行级锁,都存在共享锁(Share Lock)和排他锁(Exclusive Lock)这两类。
**MVCC
MVCC 是一种多版本并发控制机制,用于在多个并发事务同时读写数据库时保持数据的一致性和隔离性。它是通过在每个数据行上维护多个版本的数据来实现的。当一个事务要对数据库中的数据进行修改时,MVCC 会为该事务创建一个数据快照,而不是直接修改实际的数据行
MVCC 在 MySQL 中实现所依赖的手段主要是: 隐藏字段、read view、undo log。
- undo log : undo log 用于记录某行数据的多个版本的数据。
- read view 和 隐藏字段 : 用来判断当前版本数据的可见性。
1、读操作(SELECT):
当一个事务执行读操作时,它会使用快照读取。快照读取是基于事务开始时数据库中的状态创建的,因此事务不会读取其他事务尚未提交的修改。
具体工作情况如下:
●对于读取操作,事务会查找符合条件的数据行,并选择符合其事务开始时间的数据版本进行读取。
●如果某个数据行有多个版本,事务会选择不晚于其开始时间的最新版本,确保事务只读取在它开始之前已经存在的数据。
●事务读取的是快照数据,因此其他并发事务对数据行的修改不会影响当前事务的读取操作。
2、写操作(INSERT、UPDATE、DELETE):
当一个事务执行写操作时,它会生成一个新的数据版本,并将修改后的数据写入数据库。
具体工作情况如下:
●对于写操作,事务会为要修改的数据行创建一个新的版本,并将修改后的数据写入新版本。
●新版本的数据会带有当前事务的版本号,以便其他事务能够正确读取相应版本的数据。
●原始版本的数据仍然存在,供其他事务使用快照读取,这保证了其他事务不受当前事务的写操作影响。
3、事务提交和回滚:
●当一个事务提交时,它所做的修改将成为数据库的最新版本,并且对其他事务可见。
●当一个事务回滚时,它所做的修改将被撤销,对其他事务不可见。
4、版本的回收:
为了防止数据库中的版本无限增长,MVCC 会定期进行版本的回收。回收机制会删除已经不再需要的旧版本数据,从而释放空间。
MVCC 通过创建数据的多个版本和使用快照读取来实现并发控制。读操作使用旧版本数据的快照,写操作创建新版本,并确保原始版本仍然可用。这样,不同的事务可以在一定程度上并发执行,而不会相互干扰,从而提高了数据库的并发性能和数据一致性。一致性非锁定读和锁定读
一致性非锁定读 和 锁定读
一致性非锁定读与锁定读的区别主要针对的是读操作在加锁方式上的差别。这里先看下锁的分类吧。
一致性非锁定读
一致性非锁定读是InnoDB存储引擎通过多版本控制(multi versioning)的方式来读取当前执行时间数据库中的数据。如果被读的数据行被加了排他锁,在读取这行数据的时候并不会等待锁释放,而是读取该行的一个快照数据。 之所以称为非锁定读,因为不需要等待被访问行的X锁的释放。快照数据是指修改行之前的数据版本,该实现通过undo段来完成。
●非锁定读的方式极大提高了数据库的并发性。在InnoDB存储引擎中,这是默认的读取方式。
快照数据其实就是当前行数据的一个历史版本,每行记录可能有多个版本。这种技术成为多行版本技术。由此带来的并发控制,成为多版本并发控制(Multi Version Concurrency Control,MVCC)。
●在事务的隔离级别,READ COMMITED和REPEATABLE READ(InnoDB存储引擎默认的事物隔离级别)下,对快照数据的定义不同。
注意:
- 在READ COMMITTED事务隔离级别下,对于快照数据,非锁定读总是读取被锁定行的最新一份快照数据。而在
- REPEATABLE READ事务隔离级别下,对于快照数据,非锁定读总是读取事务开始时的数据版本。
也就是说在READ COMMITTED事务隔离级别下,非锁定读读取到的数据是最新的快照版本数据,也就是可以读到另一个事务已经提交了的快照数据。而在REPEATABLE READ下,只会读到事务开始前的数据。 这也就是为什么在read commited 级别下面, 会出现不可重复读的问题
锁定读
在默认情况下,InnoDB存储引擎对数据采用的是一致性非锁定读。但是有些情况下为了保证数据逻辑的一致性,需要对SELECT的操作加锁。InnoDB存储引擎对于SELECT语句支持两种一致性的锁定读(locking read)操作。
在锁定读下,读取的是数据的最新版本,这种读也被称为 当前读(current read)。
SELECT …… for update (同时会造成幻读问题)
- SELECT …… lock in share mode
其中,SELECT …… for update对读取的记录加一个X锁(排它锁),其他事务不能对已锁定的行加任何锁。
而SELECT …… lock in share mode 是对读取的记录加一个S锁(共享锁),其他事物可以向被锁定的行加S锁,但是如果加X锁,则会被阻塞。
此外,SELECT …… FOR UPDATE, SELECT …… LOCK IN SHARE MODE必须在一个事物中,当事物提交了,锁也就释放了。因此在使用上诉两句SELECT锁定语句时,务必加上BEGIN,START TRANSACTION 或者SET AUTOCOMMIT=0
在一致性非锁定读下,即使读取的记录已被其它事务加上 X 锁,这时记录也是可以被读取的,即读取的快照数据。
上面说了,在 Repeatable Read 下 MVCC 防止了部分幻读,这边的 “部分” 是指在 一致性非锁定读 情况下,只能读取到第一次查询之前所插入的数据(根据 Read View 判断数据可见性,Read View 在第一次查询时生成)。但是!如果是 锁定读,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。 InnoDB 在实现Repeatable Read 时,如果执行的是当前读,则会对读取的记录使用 Next-key Lock ,来防止其它事务在间隙间插入数据
MVCC的实现?
MVCC 的实现依赖于:隐藏字段、Read View、undo log。在内部实现中,InnoDB 通过数据行的 DB_TRX_ID 和 Read View 来判断数据的可见性,如不可见,则通过数据行的 DB_ROLL_PTR 找到 undo log 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 Read View 之前已经提交的修改和该事务本身做的修改
隐藏字段
在内部,InnoDB 存储引擎为每行数据添加了三个 隐藏字段:
●DB_TRX_ID(6字节):表示最后一次插入或更新该行的事务 id此外,delete 操作在内部被视为更新,只不过会在记录头Record header 中的 deleted_flag 字段将其标记为已删除
●DB_ROLL_PTR(7字节) 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空
●DB_ROW_ID(6字节):如果没有设置主键且该表没有唯一非空索引时,InnoDB 会使用该 id 来生成聚簇索引ReadView
undo-log
undo-log日志中,主要有两个作用
●当事务回滚时用于将数据恢复到修改前的样子
●另一个作用是 MVCC ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现非锁定读
在 InnoDB 存储引擎中 undo log 分为两种:insert undo log 和 update undo log:
- insert undo log:
指在 insert 操作中产生的 undo log。因为 insert 操作的记录只对事务本身可见,对其他事务不可见,故该 undo log 可以在事务提交后直接删除。不需要进行 purge 操作insert 时的数据初始状态:
- update undo log:update 或 delete 操作中产生的 undo log。该 undo log可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入 undo log 链表,等待 purge线程 进行最后的删除
如图
在 可重复读 与 串行化 的级别下MVCC的差异?
在事务隔离级别 读已提交 和 可重复读(InnoDB 存储引擎的默认事务隔离级别)下,InnoDB 存储引擎使用 MVCC(非锁定一致性读),但它们生成 Read View 的时机却不同在 读已提交 隔离级别下的 每次select 查询前都生成一个Read View (m_ids 列表)在 RR 隔离级别下只在事务开始后 第一次select 数据前生成一个Read View(m_ids 列表)