一、什么是事务
事务一般是指要做的或所做的事情,在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元。
比如在现实生活中,经常进行转账操作,分为两个步骤:转入与转出,只有当两部分都完成才认为转账成功。如果其中任意操作异常没有执行成功,则会导致两账户的金额不同步,造成错误,为了避免上述错误,数据库引入事务。
二、事务执行流程

①查询操作先从Buffer Pool中查询数据,若存在则直接输出,不存在则读取磁盘中的数据并放入Buffer Pool。
②在操作任何数据之前,会先将数据的旧值写入undo log日志文件中,以便执行事务过程中出现异常后好回滚到事务执行之前的数据。
③然后再操作Buffer Pool(内存)中的数据并设置成脏页,同时将操作的数据写入到Redo Log Buffer。
④将Redo Log Buffer中的数据写入到os cache系统内核缓存中。
⑤系统内核调用fsync()将os buffer中的数据写入到redolog文件中,并设置状态为prepare。
⑥redolog文件写入完成后,由server层将执行的操作性修改,包括数据库结构和表数据的变更,但不包括select语句,写入到binlog文件中。
⑦binlog写入成功后,设置状态为commit。
⑧最后完成事务提交。
脏页什么时候会被刷入磁盘
一般来说,当事务提交后,MySQL首先更新的是 Buffer Pool 中数据所在的页,然后将该页设置为脏页,但是磁盘中还是原数据。因此,脏页需要被刷入磁盘,以保证缓存和磁盘数据一致,但是若每次修改数据都刷入磁盘,则性能会很差,因此一般都会在一定时机进行批量刷盘。数据刷盘的时机有以下几种:
①当 redo log 日志满了的情况下,会主动触发脏页刷新到磁盘。
②Buffer Pool 空间不足时,会先将脏页刷新到磁盘。
③MySQL 认为空闲时,后台线程回定期将适量的脏页刷入到磁盘。
④MySQL 正常关闭之前,会把所有的脏页刷入到磁盘。
三、事务特性
事务4 个特性:原子性、一致性、隔离性、持久性。
①原子性(atomicity)
原子性是指事务中的所有操作要么全部成功,要么全部失败,不会出现部分成功部分失败的情况。如果事务在执行过程中发生了错误,那么事务会回滚(rollback),即撤销已经执行的操作,恢复到事务开始之前的状态。MySQL通过使用回滚日志(undo log)来实现原子性,回滚日志记录了事务中对数据的修改,如果事务失败,就可以根据回滚日志恢复数据。
②一致性(consistency)
一致性是指事务在执行前后必须保持数据的一致性,即不会破坏数据的完整性和业务逻辑。例如,在转账事务中,转出账户和转入账户的总金额应该保持不变。MySQL通过使用锁(lock)来实现一致性,锁可以防止多个事务同时修改同一条数据,造成数据冲突或丢失。
③隔离性(isolation)
隔离性是指多个事务之间相互隔离,不会互相影响。例如,在转账事务中,转出账户的余额应该只受到本事务的影响,而不受到其他事务的影响。MySQL通过使用隔离级别(isolation level)来实现隔离性,隔离级别定义了一个事务在读取数据时能够看到其他事务对数据所做的修改的程度。MySQL支持四种隔离级别,分别是未提交读(read uncommitted)、已提交读(read committed)、可重复读(repeatable read)和可串行化(serializable),它们从低到高提供了不同程度的隔离性和并发性。
④持久性(durability)
持久性是指事务一旦提交(commit),那么对数据所做的修改就会永久保存在数据库中,即使发生系统崩溃或断电等异常情况,也不会丢失数据。MySQL通过使用重做日志(redo log)来实现持久性,重做日志记录了事务对数据所做的修改,如果系统崩溃或断电,就可以根据重做日志恢复数据。
四、事务隔离级别
①事务隔离级别重置
MySQL InnoDB存储引擎默认的事务隔离级别是可重复读(REPEATABLE READ)
MySQL 5.7 SELECT @@tx_isolation;
MySQL 8.0 SELECT @@transaction_isolation;
-- 设置当前会话 或者 全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
{READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
②事务隔离级别介绍
以下面表为例简单介绍MySQL各个事务隔离级别执行情况
CREATE TABLE `test1` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '姓名',
`age` tinyint NOT NULL DEFAULT '0' COMMENT '年龄',
`card_no` varchar(30) NOT NULL COMMENT '身份证号',
`base_info` text COMMENT '基础信息',
PRIMARY KEY (`id`),
UNIQUE KEY `test1_card_no_IDX` (`card_no`) USING BTREE,
KEY `test1_age_IDX` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COMMENT='测试1';

- 读未提交 (READ UNCOMMITTED)
最低级别,允许一个事务读取另一个事务尚未提交的数据。这可能导致脏读、不可重复读和幻读的问题。


|
事务A |
事务B | ||||||||||
|
begin; |
begin; | ||||||||||
|
update test1 set name='特朗普是懂王' where id=1; |
| ||||||||||
|
commit; |
| ||||||||||
|
|
update test1 set name=’拜登走路要摔倒’where id=1; | ||||||||||
|
select * from test1 where id =1;
|
| ||||||||||
|
|
commit; |
- 读已提交 (READ COMMITTED)
大多数据库管理系统的默认隔离级别(如oracle),该级别下的事务只能读取其他事务已经提交的内容,可避免脏读,但不能避免 不可重复读、幻读。不可重复读是指事务内重复读取别的线程已经提交的数据,但两次读取的结果不一致,原因是查询过程中其他事务做了更新操作。幻读是指在一个事务内两次查询中数据条数不一致,原因是查询过程中其他事务做了添加操作。不可重复读与幻读严格上说不是错误,但其情况不符合实际需求。


|
事务A |
事务B | ||||||||||||||||||||
|
begin; |
begin; | ||||||||||||||||||||
|
select * from test1 where id =2;
|
select * from test1 where id =2;
| ||||||||||||||||||||
|
update test1 set name='这俩都不是啥好货' where id=2; |
| ||||||||||||||||||||
|
|
select * from test1 where id =1;
| ||||||||||||||||||||
|
commit; |
| ||||||||||||||||||||
|
|
select * from test1 where id =1;
| ||||||||||||||||||||
|
|
commit; |
- 可重复读 (REPEATABLE READ)
MySQL 的默认事务隔离级别,在一个事务中即使其他事务对该数据进行了修改,多次读取同一数据会得到相同的结果,这可以避免脏读和不可重复读,但可能出现幻读问题。
幻读问题网上很多的解释是这样的同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻读,这是错误的
幻读并不是说两次读取获取的结果集不同,幻读是指某一次select操作得到的结果所表征的数据状态无法支持后续的业务操作,具体例子:select某记录是否存在,不存在,准备插入此记录,但执行insert时发现此记录已存在,无法插入,此时就发生了幻读。
还一种理解思路,就是因为先采用了"快照读",然后又用了"当前读",发现结果不同。比如先select是快照读,然后update、insert等,这时会用到当前读,发现操作出现未预料结果。


|
事务A |
事务B | ||||||||||||||||||||
|
begin; |
begin; | ||||||||||||||||||||
|
select * from test1 where id >2;
|
select * from test1 where id >2;
| ||||||||||||||||||||
|
update test1 set name='这俩都不是啥好货' where id=2; |
| ||||||||||||||||||||
|
commit; |
| ||||||||||||||||||||
|
|
select * from test1 where id >2;
| ||||||||||||||||||||
|
INSERT INTO `test1` (`id`, `name`, `age`, `card_no`, `base_info`) VALUES ('4', '特朗普也不是啥好货', '51', '11032', NULL); |
| ||||||||||||||||||||
|
commit; |
| ||||||||||||||||||||
|
|
select * from test1 where id >2;
| ||||||||||||||||||||
|
|
INSERT INTO `test1` (`id`, `name`, `age`, `card_no`, `base_info`) VALUES ('4', '特朗普也不是啥好货', '51', '11032', NULL); | ||||||||||||||||||||
|
|
ERROR 1062 (23000): Duplicate entry '4' for key 'test1.PRIMARY' |
- 可串行化 (SERIALIZABLE)
事务的最高隔离级别,会对事务强制排序,使其不发生冲突,解决脏读、幻读、不可重复读问题。


|
事务A |
事务B | ||||||||||||||||||||
|
begin; |
begin; | ||||||||||||||||||||
|
select * from test1 where id >7;
|
select * from test1 where id >7;
| ||||||||||||||||||||
|
INSERT INTO `test1` (`id`, `name`, `age`, `card_no`, `base_info`) VALUES ('9', '李力', '12', '02422', NULL); |
| ||||||||||||||||||||
|
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction |
|
③MySQL隔离级别的实现
- MVCC
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。它的实现原理主要是依赖记录中的 4个隐式字段、undo log、purge线程 、Read View 来实现的。
Ⅰ、隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID等字段。
DB_TRX_ID:占用空间6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID。
DB_ROLL_PTR:占用空间7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)。
DB_ROW_ID:占用空间6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引。
删除字段flag:既记录被更新或删除并不代表真的删除,而是删除flag变了。DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本。

Ⅱ、 undo log
undo log主要分为两种:insert undo log、update undo log。
insert undo log:代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃。
update undo log:事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除。
Ⅲ、purge线程:
从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的Deleted_Bit,并不真正将过时的记录删除。
为了节省磁盘空间,InnoDB有专门的purge线程来清理Deleted_Bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个Read View(这个Read View相当于系统中最老活跃事务的Read View);如果某个记录的Deleted_Bit为true,并且DB_TRX_ID相对于purge线程的Read View可见,那么这条记录一定是可以被安全清除的。
Ⅳ、 Read View
Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,并且分配一个递增的事务ID,同时维护一个当前活跃事务id列表,把这些属性与DB_TRX_ID作比较,判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
Read View中的全局属性:
m_ids:表示在生成ReadView时当前系统中活跃的读写事务的 事务id列表 。
min_trx_id:活跃的事务中最小的事务 ID。
max_trx_id:Read View生成时刻系统尚未分配的下一个事务ID。
creator_trx_id:当前事务ID。
具体的比较规则如下:
如果被访问版本的DB_TRX_ID属性值与Read View中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
如果被访问版本的DB_TRX_ID属性值小于Read View中的min_trx_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
如果被访问版本的DB_TRX_ID属性值大于或等于Read View中的max_trx_id值,表明生成该版本的事务在当前事务生成Read View后才开启,所以该版本不可以被当前事务访问。
如果被访问版本的DB_TRX_ID属性值在Read View的min_trx_id和max_trx_id之间(min_trx_id < trx_id < max_trx_id),那就需要判断一下DB_TRX_ID属性值是不是在m_ids列表中,如果在,说明创建Read View时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建Read View时生成该版本的事务已经被提交,该版本可以被访问。
所以总结一下,对于使用Read Uncommitted隔离级别的事务来说,只需要读取版本链上最新版本的记录即可;对于使用Serializable隔离级别的事务来说,InnoDB使用加锁的方式来访问记录。而Read Committed和Repeatable Read隔离级别来说,都需要读取已经提交的事务所修改的记录,也就是说如果版本链中某个版本的修改没有提交,那么该版本的记录时不能被读取的。所以需要确定在Read Committed和Repeatable Read隔离级别下,版本链中哪个版本是能被当前事务读取的。于是Read View的概念被提出以解决这个问题。
- Next-key Lock
组合锁就是间隙锁(Gap Lock)+行锁(Record Lock)。
间隙锁(Gap Lock):间隙锁锁定的是索引BTree+叶子节点的next指针;间隙锁主要用于解决可重复读事务隔离级别中的幻读问题。
行锁(Record Lock):就是通过索引锁住当前记录。
快照读:在可重复读事务隔离级别下,快照读读到的是数据的当前版本或历史版本。所以快照读无需加锁也可以防止幻读。
当前读:对于这些操作(select…lock in share mode、select…for update、update、delete、insert)读取的是记录的最新版本,所以就需要通过加锁(行锁、间隙锁、表锁)的方式,使得被当前读读过的数据不能被新增修改或者删除,换句话说再来一次当前读要返回相同的数据。
间隙锁加锁规则:
Ⅰ、加锁的单位是next-key-lock,是前开后闭区间;
Ⅱ、只有访问到的对象才会加锁;
Ⅲ、对于唯一索引的等值查询来说,next-key-lock会退化为行锁;
Ⅳ、索引的等值查询来说,向右遍历时,右边界不满足等值条件时,next-key-lock会退化为间隙锁;
Ⅴ、对于唯一索引的范围查询来说,会访问到第一个不满足条件的记录为止。
五、分布式事务
①XA协议
- 基本介绍
MySQL XA 协议是指MySQL服务器支持的分布式事务协议。XA 协议由Tuxedo首先提出,并成为实现两阶段提交(2PC,Two-Phase Commit)分布式事务的标准方法,XA主要规定了RM与TM之间的交互。MySQL 从5.0.3开始支持XA分布式事务,且只有InnoDB存储引擎支持,在MySQL中XA用于分布式事务中的准备(prepare)和提交(commit)阶段。

AP(Application Program):应用程序,定义事务边界(定义事务开始和结束)并访问事务边界内的资源。
RM(Resource Manger)资源管理器: 管理共享资源并提供外部访问接口。供外部程序来访问数据库等共享资源。此外,RM还具有事务的回滚能力。
TM(Transaction Manager)事务管理器:TM是分布式事务的协调者,TM与每个RM进行通信,负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等。
- XA二阶段协议
第一阶段TM要求所有的RM准备提交对应的事务分支,询问RM是否有能力保证成功的提交事务分支,RM根据自己的情况,如果判断自己进行的工作可以被提交,那就就对工作内容进行持久化,并给TM回执OK;否者给TM的回执NO。RM在发送了否定答复并回滚了已经的工作后,就可以丢弃这个事务分支信息了。
第二阶段TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare回执NO的话,则TM通知所有RM回滚自己的事务分支。
- XA二阶段协议提交流程图

Ⅰ、应用程序AP向事务管理器TM发起事务请求。
Ⅱ、TM调用xa_open()建立同资源管理器的会话。
Ⅲ、TM调用xa_start()标记一个事务分支的开头。
Ⅳ、AP访问资源管理器RM并定义操作,比如插入记录操作。
Ⅴ、TM调用xa_end()标记事务分支的结束。
Ⅵ、M调用xa_prepare()通知RM做好事务分支的提交准备工作。其实就是二阶段提交的提交请求阶段。
Ⅶ、TM调用xa_commit()通知RM提交事务分支,也就是二阶段提交的提交执行阶段。
Ⅷ、TM调用xa_close管理与RM的会话。
Ⅸ、xa_start:负责开启或者恢复一个事务分支,并且管理XID到调用线程。
Ⅹ、xa_end:负责取消当前线程与事务分支的关系。
XI、Shiyixa_prepare:负责询问RM 是否准备好了提交事务分支 xa_commit:通知RM提交事务分支。
XII、xa_rollback:通知RM回滚事务分支
②3pc
- 阶段一
事务询问,协调者向所有参与者发送CanCommit请求;
参与者反馈,参与者接收到来自协调者的CanCommit请求之后,会根据自身情况反馈Yes或者No,Yes则进入预备状态。
- 阶段二
协调者根据各个参与者的反馈情况确定是否进行PreCommit操作,包括两种情况执行事务预提交,包括协调者发送预提交请求;参与者事务预提交,参与者响应协调者。
协调者发送中断请求。
- 阶段三
执行提交,进入这一阶段,事务会从预提交状态转换为提交状态,并向所有参与者发送DoCommit请求、事务提交、反馈提交结果、完成事务。
中断事务,发送中断请求,事务回滚;利用二阶段记录的undo日志进行回滚;反馈事务回滚结果。
本文深入探讨了MySQL事务的概念、执行流程、四大特性(原子性、一致性、隔离性、持久性)以及事务隔离级别。详细介绍了InnoDB存储引擎的MVCC机制、Read View的工作原理和快照读的实现。此外,还讨论了分布式事务中的XA协议和两阶段提交(2PC)流程。
1959

被折叠的 条评论
为什么被折叠?



