mysql 事务
本文来源:
https://www.modb.pro/db/42303
1、什么是事务?
事务就是 一组原子性的SQL,或者说一个独立的工作单元。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。
控制事务的语法:
1、BEGIN 或 START TRANSACTION 显式开启一个事务; 2、COMMIT 。提交事务,并使得对数据库进行的所有操作是永久性的; 3、ROLLBACK 。回滚事务,并撤销正在进行的所有未提交的修改;
4、SAVEPOINT identifier 在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;
5、RELEASE SAVEPOINT identifier 删除一个事务的保存点;
6、ROLLBACK TO identifier 把事务回滚到标记点;
7、SET TRANSACTION 用来设置事务的隔离级别。
InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE
2、举个例子说说你理解的事务
把转账的操作简化抽成一个事务,包含如下步骤:
-
查询CMBC账户的余额是否大于10万
-
从CMBC账户余额中减去10万
-
在ICBC账户余额中增加10万
1、START TRANSACTION;
2、SELECT balance FROM CMBC WHERE username='you';
3、UPDATE CMBC SET balance = balance - 100000.00 WHERE username = 'you';
4、UPDATE ICBC SET balance = balance + 100000.00 WHERE username = 'you';、
5、COMMIT;
3、什么是ACID特性?
原子性(atomicity)
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。
一致性(consistency)
数据库总是从一个一致性的状态转换到另外一个一致性的状态。在前面的例子中,一致性确保了,即使在执行第三、四条语句之间时系统崩溃,CMBC账户中也不会损失100万,不然lemon要哭死因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。
隔离性(isolation)
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。在前面的例子中,
当执行完第三条语句、第四条语句还未开始时,此时如果有其他人准备给你的CMBC账户存钱,那他看到的CMBC账户里还是有10万的。
持久性(durability)
一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。
4、什么是脏读、不可重复读、幻读
脏读
在事务A 修改数据之后,提交数据之前,这个时候另一个事务 B 来读取数据,如果不加控制,事务 B 读取到 A 修改过的数据,之后 A 又对数据
修改在提交,则 B 读取到的数据是脏数据。这个过程称为脏读。
不可重复读
一个事务内在读取某些数据后的某个时间,再次读取以前读过的数据,缺发现读出的数据已经发生了变更,或者某些记录被删除了。
幻读
事务A在按查询条件读取某个范围的记录时,事务B又在该范围内插入了新的满足条件的记录,当事务A再次按条件查询记录时,会产生新的满足条件的记录(幻行 Phantom Row)
5、不可重复读与幻读有什么区别?
- 不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的「数据不一样」。(因为中间有其他事务提交了修改)
- 幻读的重点在于新增或者删除:在同一事务中,同样的条件,第一次和第二次读出来的「记录数不一样」。(因为中间有其他事务提交了插入/删除)
6、MySQL事务的四个隔离级别分别是?解决了什么问题
SQL实现了四个标准的隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。
低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
各个隔离级别可以不同程度的解决脏读、不可重复读、幻读。隔离级别各有所长,没有完美的解决方案,要结合具体的业务场景谈具体实施。
7、MySQL存储引擎类型有哪些?
最常用的存储引擎是InnoDB引擎和MyISAM存储引擎,InnoDB是MySQL的默认事务引擎。
8、InnoDB存储引擎的特点和应用场景?
InnoDB是MySQL的默认「事务引擎」,被设置用来处理大量短期(short-lived)事务,短期事务大部分情况是正常提交的,很少会回滚。
9、InnoDB与MyISAM对比
10、mvvc 的原理
我们看上面这个数据库日常操作的例子。
1、session 1 修改一条记录,没有提交;以此同时,session 2 来查询这条记录是多少呢?
2、session 1 提交之后 session 2 查询出来的又应该是多少呢?
由于MySQL支持多种隔离级别,这个问题是需要看session2的事务隔离级别的,情况如下:
1、隔离级别为 READ-UNCOMMITTED 的情况下:
session 1 commit 前后 session 2 去查看都会看到的修改结果是 a = 20
2、隔离级别为 READ-COMMITTED 情况下:
session 1 commit 前查看的数据是 a = 10, commit 之后看到的是 a = 20
3、隔离级别为 REPEATABLE-READ, SERIALIZABLE 情况下:
session 1 commit前后 session 2 去查看都会看到的是修改后的结果 a = 10
问题:
其实不管隔离级别,我们也抛开数据库中的ACID,我们思考一个问题:众所周知,InnoDB的数据都是存储在B-tree里面的,修改后的数据到底要不要存储在实际的B-tree叶子节点?session2是怎么做到查询出来的结果还是10,而不是20呢?
MVCC实现原理
原理很简单,数据库就是通过 undo 和 mvcc 来实现的。
通过 DB_ROLL_PT 回溯查找数据的历史版本
1、首先 InnoDB 每一行数据还有一个 DB_ROLL_PT 的回滚指针,用于指向该行修改前的上一个历史版本。
2、当插入的是一条新数据时候,记录上对应的回滚指针为 NULL
更新记录时候,原纪录将被放到 undo 表空间中,并通过 DB_ROLL_PT 指向该记录。
https://blog.csdn.net/qq_56263094/article/details/125317049
基本概念
快照读
简单的 select (不加锁) 就是快照读,快照读取可能是最新的,也有可能是历史数据
Read Committed : 每个 select 都会产生一个快照读
Repeatable Read : 开启事物后第一个 select 才是快照读的地方
Serializable : 快照读会退化为当前读(加锁)
MVCC
全称Multi-Version Concurrency Control,多版本并发控制,指维护一个数据的多个版本,使得读写操作没有冲突,也就是不会产生读写锁冲突,而快照读为MySQL实现 MVCC提供了一个非阻塞读功能,这给我们高并发性能提升不少
而来介绍一下MVCC的原理。
隐藏字段
举个例子当我们创建了这张表,在查看表结构的时候,只看得到这三个显示的字段,但是实际上除了这三个字段意外,InnoDB还会自动的给我们添加三个隐藏字段,如下:
而上面前俩个字段是肯定添加的,而第三个要看是否自己设置了主键,如果没有指定主键,则会添加该字段
undolog
回滚日志,在insert,update,delete的时候产生便于数据回滚的日志
1、在insert的时候,产生的undo log 日志旨在回滚的时候需要,在事务提交后,可被立即删除
2、而update,delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除
版本链
DB_TRX_ID : 代表最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID,是自增的
DB_ROLL_PTR : 指向上一个版本,由于这条数据是才插入的,没有被更新过,所以该字段值为null。
然后,有四个并发事务同时在访问这张表【重点】
第一步
当事务2开始修改数据时候,会产生 undo log 日志,记录数据变更之前的样子,并且会记录本次操作的事务 id
第二步
当事务3执行第一条修改语句时,也会记录记录undo log日志,记录数据变更之前的样子,然后更新版本记录,并且记录本次操作事务id
第三步
当事务4执行第一条修改语句时,也会记录undo log日志,记录数据变更之前的样子; 然后更新记录,并且记录本次操作的事务ID
最终我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录
Readview
Readview(读视图)是快照读SQL执行的时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id
Redaview中包含了四个核心字段:
看图有点抽象,我觉得可以这样理解
m_ids:当前活跃的事务ID集合 ==>当前并发过程中还未提交的事务ID集合
min_trx_id:最小活跃事务ID ==>在还未提交的事务ID集合中最小那个
max_trx_id:预分配事务ID,当前最大事务ID+1 ==>也就是说下一个事务ID应该是当前还未提交的事务id集合中最大那个ID+1
creator_trx_id:ReadView创建者的事务ID ==>这个好理解 就是哪个事务创建的这个ReadView那就是那个事务的ID
而在readview中就规定了版本链数据的访问规则:
trx_id 代表当前undolog版本链对应事务ID
1、trx_id 可以理解为每一条匹配的 undo log 版本链的事务 ID,也就是哪个事务 ID 产生的这条记录
2、从版本链尾部到头部开始匹配
3、如果 trx_id == creator_trx_id 那就说明改记录是自己所在的事务更改的,是一家人。那肯定可以访问该版本
4、如果 trx_id < min_trx_id,也就是说访问的这个事务已经提交了,因为目前还未结束的事务ID 都比它大。
5、如果trx_id > max_trx_id,那就说明已经有事务在生成ReadView之后开启了,我们肯定不能访问该版本,防止产生重复读的问题
6、如果min_trx_id <= trx_id <=max_trx_id,如果trx_id不在m_ids集合里面,那就说明该事务已经提交了,提交了我们肯定可以访问,如果没有提交,那就不能访问
不同的隔离级别,生成ReadView的时机不同
READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。
原理分析
1、READ COMMITTED 隔离级别
READ COMMITTED 隔离级别下,在事务中每一次执行快照读时候生成 ReadView,如下图
在分析事务5 中,两次快照读取数据,是如何获取数据的呢?
在事务5中,查询了两次id为30的记录,由于隔离级别为Read Committed,所以每一次进行快照读都会生成一个ReadView,那么两次生成的ReadView如下:
这里有图也可以好好理解一下ReadView每一个字段所代表的具体含义
那么俩次快照读在获取数据的时候,就需要根据所生成的ReadView和ReadView的版本链访问规则,到undo log版本练中匹配数据,最终决定此次快照读使用哪一条记录的数据
A.先来看第一次快照读具体的读取过程
下图是版本链和访问规则
在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配:
1、 先匹配这条记录
这条记录对应的 trx_id 为4,也就是将4带入右侧的匹配规则中
trx_id (4) == creator_trx_id (5) 不满足
Read Committed : 每个 select 都会产生一个快照读
Repeatable Read:开启事务后第一个select才是快照读的地方
Serializable:快照读会退化为当前读(加锁)
https://www.bilibili.com/video/BV1YJ411J7vb/?spm_id_from=333.337.search-card.all.click&vd_source=3b043fbb3f7302824c9ad5570dccd79a
https://www.bilibili.com/video/BV1te4y1o7KN/?spm_id_from=333.999.0.0&vd_source=3b043fbb3f7302824c9ad5570dccd79a