【MySQL】事务管理
事务管理
- 一. 事务的概念
- 二. 事务的特征
- 三. 事务的版本支持
- 四. 事务的提交方式
- 五. 事务的常见操作
- 六. 事务的隔离级别
- 1. 查看与设置隔离级别
- 2. 读未提交 (Read Uncommitted)
- 3. 读提交 (Read Committed)
- 4. 可重复读 (Repeatable Read)
- 5. 串行化 (Serializable)
- 6. 隔离级别的总结
- 七. 事务的一致性
- 八. 数据库的并发场景
- 九. 多版本并发控制
- 1. 三个记录隐藏列字段
- 2. undo 日志
- 4. Read View
- 十. 读提交(RC) 与 可重复读(RR) 的本质区别
一. 事务的概念
- 事务由一条或多条SQL语句组成,这些语句在逻辑上存在相关性,共同完成一个任务,事务主要用于处理操作量大,复杂度高的数据。比如转账就涉及多条SQL语句,包括查询余额(select)、在当前账户上减去指定金额(update)、在指定账户上加上对应金额(update)等,将这多条SQL语句打包便构成了一个事务。
- MySQL同一时刻可能存在大量事务,如果不对这些事务加以控制,在执行时就可能会出现问题。比如单个事务内部的某些SQL语句执行失败,或是多个事务同时访问同一份数据导致数据不一致的问题。
二. 事务的特征
一个完整的事务,绝对不是简单的 SQL 集合,还需要满足如下四个属性,简称ACID:
- 原子性 (Atomicity): 事务中的所有操作要么全部完成,要么全部不完成。如果事务中的某个操作失败,整个事务都会回滚,撤销已经完成的操作,使数据库恢复到事务开始前的状态。
- 一致性 (Consistency): 事务必须确保数据库从一个一致性状态转换到另一个一致性状态。一致性意味着事务执行前后,数据库的完整性约束不能被破坏。
- 隔离性 (Isolation): 多个事务并发访问同一份数据时,事务之间是相互隔离的,一个事务的执行不能被其他事务干扰。隔离性确保了事务的独立性,防止了事务之间的相互影响导致数据的不一致问题。
- 持久性 (Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务的存在原因
- 事务被 MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。
- 当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?
- 因此事务本质上是为了应用层服务的,而不是伴随着数据库系统天生就有的。
备注:我们后面把 MySQL 中的一行信息,称为一行记录。
三. 事务的版本支持
通过 show engines 命令可以查看数据库引擎:
- Engine:表示存储引擎的名称。
- Support:表示服务器对存储引擎的支持级别,YES表示支持,NO表示不支持,DEFAULT表示数据库默认使用的存储引擎,DISABLED表示支持引擎但已将其禁用。
- Comment:表示存储引擎的简要说明。
- Transactions:表示存储引擎是否支持事务,可以看到InnoDB存储引擎支持事务,而MyISAM存储引擎不支持事务。
- XA:表示存储引擎是否支持XA事务。
- Savepoints:表示存储引擎是否支持保存点。
四. 事务的提交方式
事务的提交方式常见的有两种:自动提交、手动提交。
查看事务提交方式
show variables like 'autocommit';
用 SET 来改变 MySQL 的自动提交模式
# 设置为禁止自动提交
set autocommit = 0;
show variables like 'autocommit';
# 设置为自动提交
set autocommit = 1;
show variables like 'autocommit';
说明:
- autocommit 的值为 ON 打开自动提交。
- autocommit 的值为 OFF 关闭自动提交,表示手动提交。
五. 事务的常见操作
准备工作
将MySQL的隔离级别设置成读未提交,也就是把隔离级别设置的比较低,方便看到实验现象。
set global transaction isolation level read uncommitted;
需要注意的是,设置全局隔离级别后当前会话的隔离级别不会改变,只会影响后续与 MySQL 新建立的连接,因此需要重启终端才能看到会话的隔离级别被成功设置。
select @@transaction_isolation;
创建测试表
create table account(id int primary key, name varchar(50) not null, balance decimal(10,2) not null
)engine=InnoDB charset=utf8;
演示一:事务的常规操作
启动两个终端,左终端使用begin或start transaction命令启动一个事务,右终端查看银行用户表中的信息。
左终端中的事务向表中插入一条记录,由于我们将隔离级别设置成了读未提交,因此在左终端中的事务使用commit提交之前,在右终端中就能查看到事务向表中插入的记录。
左终端中的事务使用savepoint命令创建一个保存点,然后继续向表中插入一条记录,这时在右终端中也能看到新插入的这条记录。
左终端中的事务使用rollback命令回滚到保存点,这时右终端在查看表中数据时就看不到刚才插入的第二条记录了。
左终端中的事务使用rollback命令回滚到事务最开始,这时右终端在查看表中数据时就看不到任何记录了。
说明:
- 使用 begin 或 start transaction 命令,可以启动一个事务。
- 使用 savepoint 保存点 命令,可以在事务中创建指定名称的保存点。
- 使用 rollback to 保存点 命令,可以让事务回滚到指定保存点。
- 使用 rollback 命令,可以直接让事务回滚到最开始。
- 使用 commit 命令,可以提交事务,提交事务后就不能回滚了。
演示二:原子性
在左终端中启动一个事务,在右终端查看银行用户表中的信息。
左终端中的事务向表中插入一条记录,由于隔离级别是读未提交,因此在右终端中能够查询到插入的这条记录。
如果左终端中的事务在提交之前因为某些原因与MySQL断开连接,那么MySQL会自动让事务回滚到最开始,这时右终端中就看不到之前插入的记录了。
演示三:持久性
在左终端中启动一个事务,在右终端查看银行用户表中的信息。
左终端中的事务向表中插入一条记录,由于隔离级别是读未提交,因此在右终端中能够查询到插入的这条记录。
左终端中的事务在提交后与MySQL断开连接,这时右终端中仍然可以看到之前插入的记录,因为事务提交后数据就被持久化了。
演示四:begin会自动更改提交方式
通过show命令查看autocommit的值为ON,表示事务的提交方式是自动提交,此时银行用户表中有一条记录。
在左终端中启动一个事务并向表中新插入一条记录,由于隔离级别是读未提交,因此在右终端中能够查询到新插入的这条记录。
如果左终端中的事务在提交之前与MySQL断开连接,那么MySQL依旧会自动让事务回滚到最开始,这时右终端中就看不到之前新插入的记录了。
也就是说,使用begin或start transaction命令启动的事务,都必须要使用commit命令手动提交,数据才会被持久化,与是否设置autocommit无关。
演示五:单条SQL与事务的关系
- 实际全局变量autocommit是否被设置影响的是单条SQL语句,InnoDB中的每一条SQL都会默认被封装成事务。
- autocommit为ON,则单条SQL语句执行后会自动被提交,如果为OFF,则SQL语句执行后需要使用commit进行手动提交。
比如通过show命令查看autocommit的值为ON,表示事务的提交方式是自动提交,此时银行用户表中有一条记录。
在左终端中直接向表中新插入一条记录,由于隔离级别是读未提交,因此在右终端中肯定能够查询到新插入的这条记录。
但就算左终端在执行单条SQL后不使用commit进行提交,而直接与MySQL断开连接,这时右终端仍然可以看到之前新插入的记录了,因为单条SQL在执行后被自动提交持久化了。
相反,如果将autocommit设置为OFF,表示事务执行后需要手动提交,此时银行用户表中有两条记录。
在左终端中直接向表中新插入一条记录,由于隔离级别是读未提交,因此在右终端中肯定能够查询到新插入的这条记录。
但如果此时左终端在执行单条SQL后不使用commit进行提交,而直接与MySQL断开连接,那么这时右终端中就看不到之前新插入的记录了,因为这时单条SQL执行后需要使用commit手动提交后才会持久化,在commit之前与MySQL断开连接则会自动进行回滚操作。
也就是说,实际我们之前一直都在使用单SQL事务,只不过autocommit默认是打开的,因此单SQL事务执行后自动就被提交了。
六. 事务的隔离级别
- MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务的方式进行。
- 一个事务可能由多条SQL语句构成,也就意味着任何一个事务,都有执行前、执行中和执行后三个阶段,而所谓的原子性就是让用户层要么看到执行前,要么看到执行后,执行中如果出现问题,可以随时进行回滚,所以单个事务对用户表现出来的特性就是原子性。
- 但毕竟每个事务都有一个执行的过程,在多个事务各自执行自己的多条SQL时,仍然可能会出现互相影响的情况,比如多个事务同时访问同一张表,甚至是表中的同一条记录。
- 数据库为了保证事务执行过程中尽量不受干扰,于是出现了隔离性的概念,而数据库为了允许事务在执行过程中受到不同程度的干扰,于是出现了隔离级别的概念。
数据库事务的隔离级别有以下四种
- 读未提交 (Read Uncommitted): 在该隔离级别下,所有的事务都可以看到其他事务没有提交的执行结果,实际生产中不可能使用这种隔离级别,因为这种隔离级别相当于没有任何隔离性,会存在很多并发问题,如 脏读、不可重复读、幻读 等。
- 读提交 (Read Committed): 该隔离级别是大多数数据库的默认隔离级别,但它不是MySQL默认的隔离级别,它满足了隔离的简单定义:一个事务只能看到其他已经提交的事务所做的改变,但这种隔离级别存在 不可重复读、幻读 问题。
- 可重复读 (Repeatable Read): 这是MySQL默认的隔离级别,该隔离级别确保同一个事务在执行过程中,多次读取操作数据时会看到同样的数据,即解决了不可重复读的问题,但这种隔离级别下仍然存在 幻读 的问题。
- 串行化 (Serializable): 这是事务的最高隔离级别,该隔离级别通过强制事务排序,使之不可能相互冲突,从而解决了幻读问题。它在每个读的数据行上面加上共享锁,但是可能会导致超时和锁竞争问题,这种隔离级别太极端,实际生成中基本不使用。
说明:读写并发时,读存在隔离级别,隔离级别基本上都是通过加锁的方式实现的,不同的隔离级别对锁的使用是不同的,常见的有表锁、行锁、写锁、间隙锁(GAP)、Next-Key锁(GAP+行锁)等。
1. 查看与设置隔离级别
查看会话隔离级别
select @@session.transaction_isolation;# 或者
select @@transaction_isolation;
设置会话隔离级别
set session transaction isolation level read uncommitted;
select @@session.transaction_isolation;
说明:设置会话的隔离级别只会影响当前会话,新起的会话依旧采用全局隔离级。
查看全局隔离级别
select @@global.transaction_isolation;
设置全局隔离级别
set global transaction isolation level read uncommitted;
select @@global.transaction_isolation;
说明:设置全局隔离级别会影响后续的新会话,但当前会话的隔离级别没有发生变化,如果要让当前会话的隔离级别也改变,则需要重启会话。
2. 读未提交 (Read Uncommitted)
读未提交:一个事物读到了另一个事物未提交的数据。
启动两个终端,将隔离级别都设置为读未提交,并查看此时银行用户表中的数据。
在两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务就已经能够看到了。
- 读未提交是事务的最低隔离级别,几乎没有加锁,虽然效率高,但是问题比较多,所以严重不建议使用。
- 一个事务在执行过程中,读取到另一个执行中的事务所做的修改,但是该事务还没有进行提交,这种现象叫做脏读。
3. 读提交 (Read Committed)
读提交:一个事物读到了另一个事物提交的数据。
启动两个终端,将隔离级别都设置为读提交,并查看此时银行用户表中的数据。
在两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务无法看到。
只有当左终端中的事务提交后,右终端中的事务才能看到修改后的数据。
不可重复读:事务在执行过程中,多次 select 读取数据时,可能会读取到不同的数据。
4. 可重复读 (Repeatable Read)
可重复读:事务在执行过程中,多次 select 读取数据时,一定会读取到相同的数据。
启动两个终端,将隔离级别都设置为可重复读,并查看此时银行用户表中的数据。
在两个终端各自启动一个事务,左终端中的事务所作的插入、修改、删除在没有提交之前,右终端中的事务无法看到。
并且当左终端中的事务提交后,右终端中的事务仍然看不到插入、修改、删除后的数据。
只有当右终端中的事务提交后再查看表中的数据,这时才能看到修改后的数据。
- 幻读:一个事务在执行过程中,相同的 select 查询得到了新的数据,如同出现了幻觉。
- 存在一些数据库在可重复读隔离级别下,update 数据是满足可重复读的,但 insert 数据会存在幻读问题,因为隔离性是通过对数据加锁完成的,而新插入的数据原本是不存在的,因此一般的加锁无法屏蔽这类问题。
MySQL 通过Next-Key锁 (GAP+行锁) 来解决幻读问题的。
5. 串行化 (Serializable)
串行化:同一时间只有一个事务可以执行。
启动两个终端,将隔离级别都设置为串行化,并查看此时银行用户表中的数据。
在两个终端各自启动一个事务,如果这两个事务都对表进行的是读操作,那么这两个事务可以并发执行,不会被阻塞。
但如果这两个事务中有一个事务要对表进行写操作,那么这个事务就会立即被阻塞。
直到访问这张表的其他事务都提交后,这个被阻塞的事务才会被唤醒,然后才能对表进行修改操作。
- 串行化是事务的最高隔离级别,多个事务同时进行读操作时加的是共享锁,因此可以并发执行读操作,但一旦需要进行写操作,就会进行串行化,效率很低,几乎不会使用。
6. 隔离级别的总结
- 隔离级别越严格,安全性越高,但数据库的并发性能也就越低,在选择隔离级别时往往需要在两者之间找一个平衡点。
- 不可重复读的重点是修改和删除:同样的条件,你读取过的数据,再次读取出来发现值不一样了。
- 幻读的重点在于新增:同样的条件,第1次和第2次读出来的记录数不一样。
- MySQL 默认的隔离级别是可重复读,一般情况下不要修改。
说明:
- 表中只写出了各种隔离级别下进行读操作时是否需要加锁,串行化读时,加的是共享锁 (允许多个事务同时读取同一数据行,但会阻止其他事务对该数据行进行写操作)
- 无论哪种隔离级别,只要需要进行写操作就一定需要加锁。
七. 事务的一致性
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态,当数据库只包含事务成功提交的结果时,数据库就处于一致性状态。
- 事务在执行过程中如果发生错误,则需要自动回滚到事务最开始的状态,就像这个事务从来没有执行过一样,即一致性需要原子性来保证。
- 事务处理结束后,对数据的修改必须是永久的,即便系统故障也不能丢失,即一致性需要持久性来保证。
- 多个事务同时访问同一份数据时,必须保证这多个事务在并发执行时,不会因为由于交叉执行而导致数据的不一致,即一致性需要隔离性来保证。
- 此外,一致性与用户的业务逻辑强相关,如果用户本身的业务逻辑有问题,最终也会让数据库处于一种不一致的状态。
也就是说,一致性实际是数据库最终要达到的效果,一致性不仅需要原子性、持久性和隔离性来保证,还需要上层用户编写出正确的业务逻辑。
八. 数据库的并发场景
数据库并发场景有三种:
- 读-读并发:不存在任何问题,也不需要并发控制。
- 读-写并发:有线程安全问题,可能会存在事务隔离性问题,可能遇到脏读、不可重复读、幻读。
- 写-写并发:有线程安全问题,可能会存在两类更新丢失问题。
说明:
- 写-写并发场景下的第一类更新丢失又叫做回滚丢失,即一个事务的回滚把另一个已经提交的事务更新的数据覆盖了,第二类更新丢失又叫做覆盖丢失,即一个事务的提交把另一个已经提交的事务更新的数据覆盖了。
- 读-读并发不需要进行并发控制,写-写并发实际也就是对数据进行加锁,这里最值得讨论的是读-写并发,读-写并发是数据库当中最高频的场景,在解决读-写并发时不仅需要考虑线程安全问题,还需要考虑并发的性能问题。
九. 多版本并发控制
- 多版本并发控制 (MVCC) 是一种用来解决读写冲突的无锁并发控制。
- 主要依赖记录中的 三个记录隐藏列字段、undo日志 和 Read View 实现。
- 为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照,通过事务ID 判断事务的先后顺序。
- MVCC保证读写并发时,读操作不会阻塞写操作,写操作也不会阻塞读操作,提高了数据库并发读写的性能,同时还可以解决脏读、幻读和不可重复读等事务隔离性问题。
1. 三个记录隐藏列字段
DB_TRX_ID (事务ID)
:记录修改该行的事务的事务 IDDB_ROLL_PTR (回滚指针)
:指向该行的回滚记录 (undo log),回滚记录包含了将行恢复到之前状态所需的信息。DB_ROW_ID (行ID)
:为每一行分配的一个唯一标识符,当表没有定义聚簇索引时,这个值会被用作内部主键。
说明:
- 采用 InnoDB 存储引擎建立的每张表都会有一个主键,如果用户没有设置,InnoDB 就会自动以 DB_ROW_ID 产生一个聚簇索引。
- 此外,数据库表中的每条记录还有一个删除 flag 隐藏字段,用于表示该条记录是否被删除,便于进行数据回滚。
创建测试表
create table student(name varchar(20) not null, age int not null
);
insert student values ('张三', 28);
select * from student;
当向表中插入一条记录后,该记录不仅包含name和age字段,还包含三个隐藏字段,如下:
说明:
- 假设插入该记录的事务的事务ID为9,那么该记录的DB_TRX_ID字段填的就是9
- 因为这是插入的第一条记录,所以隐式主键DB_ROW_ID字段填的就是1
- 由于这条记录是新插入的,没有历史版本,所以回滚指针DB_ROLL_PTR的值设置为null
2. undo 日志
MySQL 的三大日志
- redo log:重做日志,用于MySQL崩溃后进行数据恢复,保证数据的持久性。
- bin log:逻辑日志,用于主从数据备份时进行数据同步,保证数据的一致性。
- undo log:回滚日志,用于对已经执行的操作进行回滚,保证事务的原子性。
说明:
- MySQL会为上述三大日志开辟对应的缓冲区,用于存储日志相关的信息,必要时会将缓冲区中的数据刷新到磁盘。
- MVCC的实现主要依赖三大日志中的undo log,记录的历史版本就是存储在undo log对应的缓冲区中的。
模拟 MVCC
现在有一个事务ID为10的事务,要将刚才插入学生表中的记录的学生姓名改为“李四”:
- 因为是要进行写操作,所以需要先给该记录加行锁。
- 修改前,先将该行记录拷贝到undo log中,此时undo log中就有了一行副本数据。
- 然后再将原始记录中的学生姓名改为“李四”,并将该记录的DB_TRX_ID改为10,回滚指针DB_ROLL_PTR设置成undo log中副本数据的地址,从而指向该记录的上一个版本。
- 最后当事务10提交后释放锁,这时最新的记录就是学生姓名为“李四”的那条记录。
修改后的示意图如下:
现在又有一个事务ID为11的事务,要将刚才学生表中的那条记录的学生年龄改为38:
- 因为是要进行写操作,所以需要先给该记录(最新的记录)加行锁。
- 修改前,先将该行记录拷贝到undo log中,此时undo log中就又有了一行副本数据。
- 然后再将原始记录中的学生年龄改为38,并将该记录的DB_TRX_ID改为11,回滚指针DB_ROLL_PTR设置成刚才拷贝到undo log中的副本数据的地址,从而指向该记录的上一个版本。
- 最后当事务11提交后释放锁,这时最新的记录就是学生年龄为38的那条记录。
修改后的示意图如下:
此时我们就有了一个基于链表记录的历史版本链。快照:undo log 中的一个个的历史版本。
说明:
- 所谓的回滚实际就是用 undo log 中的历史数据覆盖当前数据,而所谓的创建保存点就可以理解成是给某些版本做了标记,让我们可以直接用这些版本数据来覆盖当前数据。
- 这种技术实际就是基于版本的写时拷贝,当需要进行写操作时先将最新版本拷贝一份到 undo log 中,然后再进行写操作,和父子进程为了保证独立性而进行的写时拷贝是类似的。
insert和delete的记录如何维护版本链?
- 删除记录并不是真的把数据删除了,而是先将该记录拷贝一份放入 undo log 中,然后将该记录的删除flag隐藏字段设置为1,这样回滚后该记录的删除flag隐藏字段就又变回0了,相当于删除的数据又恢复了。
- 新插入的记录是没有历史版本的,但是一般为了回滚操作,新插入的记录也需要拷贝一份放入 undo log 中,只不过被拷贝到 undo log 中的记录的删除flag隐藏字段被设置为1,这样回滚后就相当于新插入的数据就被删除了。
说明:增加、删除和修改数据都是可以形成版本链的。
当前读 VS 快照读
- 当前读:读取最新的记录,就叫做当前读。
- 快照读:读取历史版本,就叫做快照读。
事务在进行增删查改的时候,并不是都需要进行加锁保护:
- 事务对数据进行增删改的时候,操作的都是最新记录,即当前读,需要进行加锁保护。
- 事务在进行select查询的时候,既可能是当前读也可能是快照读,如果是当前读,那也需要进行加锁保护,但如果是快照读,那就不需要加锁,因为历史版本不会被修改,也就是可以并发执行,提高了效率,这也就是MVCC的意义所在。
而select查询时应该进行当前读还是快照读,则是由隔离级别决定的,在读未提交和串行化隔离级别下,进行当前读,而在读提交和可重复读隔离级别下,进行快照读。
undo log中的版本链何时才会被清除?
- 在undo log中形成的版本链不仅仅是为了进行回滚操作,其他事务在执行过程中也可能读取版本链中的某个版本,也就是快照读。
- 因此,只有当某条记录的最新版本已经修改并提交,并且此时没有其他事务与该记录的历史版本有关了,这时该记录在undo log中的版本链才可以被清除。
说明:
- 对于新插入的记录来说,没有其他事务会访问它的历史版本,因此新插入的记录在提交后就可以将undo log中的版本链清除了。
- 因此版本链在undo log中可能会存在很长时间,尤其是有其他事务和这个版本链相关联的时候,但这也没有坏处,这说明它是一个热数据。
4. Read View
如何保证不同的事务,select 看到哪些内容呢?也就是如何实现隔离级别?由 Read View 决定!
- 当事务在进行快照读操作时会生成Read View (读视图),他在 MySQL 源码中就是一个类,本质是用来进行可见性判断的。在该事务执行快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃的事务ID。
- 当事务创建时,不会创建 Read View,只有事物对某个记录执行快照读的时候,对该记录创建一个Read View,根据这个Read View来判断,当前事务能够看到该记录的哪个版本的数据。
简化的 ReadView 类的源码如下:
class ReadView
{// ...
private:/** 高水位:大于等于这个ID的事务均不可见*/trx_id_t m_low_limit_id;/** 低水位:小于这个ID的事务均可见 */trx_id_t m_up_limit_id;/** 创建该 Read View 的事务ID*/trx_id_t m_creator_trx_id;/** 创建视图时的活跃事务id列表*/ids_t m_ids;// ...
};
部分成员说明:
m_low_limit_id
:记录 Read View 生成时刻,系统尚未分配的下一个事务IDm_up_limit_id
:记录 m_ids 列表中事务ID最小的IDm_creator_trx_id
:记录创建该 Read View 的事务的事务IDm_ids
:一张列表,记录 Read View 生成时刻,系统中活跃的事务ID
由于 事务ID (隐藏列DB_TRX_ID) 是单向增长的,因此根据 Read View 中的 m_up_limit_id 和 m_low_limit_id,可以将事务ID分为三个部分:
- 事务ID < m_up_limit_id 的事务:一定是生成 Read View 时已经提交的事务,因为 m_up_limit_id 是生成 Read View 时刻系统中活跃事务ID中的最小ID。已经提交的事务我们应该看到。
- 事务ID >= m_low_limit_id 的事务:一定是生成 Read View 时还没有启动的事务,因为m_low_limit_id是生成Read View时刻,系统尚未分配的下一个事务ID。还未启动的事务我们不应该看到。
- m_up_limit_id <= 事务ID < m_low_limit_id:在生成 Read View 时可能正处于活跃状态,也可能已经提交了。如果事务ID不在m_ids中说明事务已经提交了,我们应该看到,如果事务ID在m_ids中说明事务处于活跃状态,我们不应该看到。
示意图如下:
- 一个事务在进行读操作时,只应该看到 自己提交的事务 或 已经提交的事务 所作的修改,因此我们可以根据 Read View 来判断当前事务能否看到另一个事务所作的修改。
- 版本链中的每个版本的记录都有自己的 DB_TRX_ID,即创建或最近一次修改该记录的事务ID,因此可以依次遍历版本链中的各个版本,通过 Read View 来判断当前事务能否看到这个版本,如果不能则继续遍历下一个版本。
源码策略如下:
bool changes_visible(trx_id_t id, const table_name_t& name) const MY_ATTRIBUTE((warn_unused_result))
{ut_ad(id > 0);//1、事务id小于m_up_limit_id(已提交)或事务id为创建该Read View的事务的id,则可见if (id < m_up_limit_id || id == m_creator_trx_id) {return(true);}check_trx_id_sanity(id, name);//2、事务id大于等于m_low_limit_id(生成Read View时还没有启动的事务),则不可见if (id >= m_low_limit_id) {return(false);}//3、事务id位于m_up_limit_id和m_low_limit_id之间,并且活跃事务id列表为空(即不在活跃列表中),则可见else if (m_ids.empty()) {return(true);}const ids_t::value_type* p = m_ids.data();//4、事务id位于m_up_limit_id和m_low_limit_id之间,如果在活跃事务id列表中则不可见,如果不在则可见return (!std::binary_search(p, p + m_ids.size(), id));
}
说明: 使用该函数时将版本的DB_TRX_ID传给参数id,该函数的作用就是根据Read View,判断当前事务能否看到这个版本。
读提交的整体流程如下
假设当前有条记录:
事务操作:
- 事务4:修改name(张三) 变成name(李四)
- 当 事务2 对某行数据执行了 快照读 ,数据库为该行数据生成一个 Read View 读视图
// 事务2 的 Read View
m_ids; // 1,3
up_limit_id; // 1
low_limit_id; // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID
creator_trx_id // 2
只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务,此时版本链是:
我们的事务2在快照读该行记录的时候,就会拿该行记录的 DB_TRX_ID 去跟 up_limit_id,low_limit_id 和 m_ids 进行比较,判断当前事务2能看到该记录的版本。
//事务2的 Read View
m_ids; // 1,3
up_limit_id; // 1
low_limit_id; // 4 + 1 = 5,原因:ReadView生成时刻,系统尚未分配的下一个事务ID
creator_trx_id // 2//事务4提交的记录对应的事务ID
DB_TRX_ID=4//比较步骤
DB_TRX_ID(4)< up_limit_id(1) ? 不小于,下一步
DB_TRX_ID(4)>= low_limit_id(5) ? 不大于,下一步
m_ids.contains(DB_TRX_ID) ? 不包含,说明,事务4不在当前的活跃事务中结论:事务4的更改,应该看到
十. 读提交(RC) 与 可重复读(RR) 的本质区别
现象演示一
启动两个终端,将隔离级别都设置为可重复读,并查看此时银行用户表中的数据。
在两个终端各自启动一个事务,在左终端中的事务操作之前,先让右终端中的事务查看一下表中的信息。
左终端中的事务对表中的信息进行修改并提交,右终端中的事务看不到修改后的数据。
在右终端中使用select * from account lock in share mode
命令进行当前读,可以看到表中的数据确实是被修改了,只是右终端中的事务看不到而已。
现象演示二
但如果修改一下SQL的执行顺序,在两个终端各自启动一个事务后,直接让左终端中的事务对表中的信息进行修改并提交,然后再让右终端中的事务进行查看,这时右终端中的事务就直接看到了修改后的数据。
右终端中使用select * from account lock in share mode
命令进行当前读,可以看到刚才读取到的确实是最新的数据。
- 上面两次实验的唯一区别在于,右终端中的事务在左终端中的事务修改数据之前是否进行过快照读。
- 事务中快照读的结果是非常依赖该事务首次出现快照读 select 的地方,即某个事务中首次出现快照读时,创建了 Read View,决定该事务后续快照读结果的能力。
RR与RC的本质区别
- 正是因为Read View生成时机的不同,从而造成了RC和RR级别下快照读的结果的不同。
- 在RR级别下,事务第一次进行快照读时会创建一个Read View,将当前系统中活跃的事务记录下来,此后再进行快照读时就会直接使用这个Read View进行可见性判断,因此当前事务看不到第一次快照读之后其他事务所作的修改。
- 而在RC级别下,事务每次进行快照读时都会创建一个Read View,然后根据这个Read View进行可见性判断,因此每次快照读时都能读取到被提交了的最新的数据。
- RR级别下快照读只会创建一次Read View,所以RR级别是可重复读的,而RC级别下每次快照读都会创建新的Read View,所以RC级别是不可重复读的。
相关文章:
【MySQL】事务管理
事务管理 一. 事务的概念二. 事务的特征三. 事务的版本支持四. 事务的提交方式五. 事务的常见操作六. 事务的隔离级别1. 查看与设置隔离级别2. 读未提交 (Read Uncommitted)3. 读提交 (Read Committed)4. 可重复读 (Repeatable Read)5. 串行化 (Serializable)6. 隔离级别的总结…...
【点对点协议(PPP)全解析】从原理到工程实践
目录 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 二、实战演示环境配置要求核心配置实现案例1:基础PPP链路建立案例2:CHAP认证配置 运行结果验证 三、性能对比测试…...
环境搭建:开启 Django 开发之旅
一、环境搭建:开启 Django 开发之旅 (一)安装 Python 先确保电脑上装有 Python 3.6 及以上版本,Django 5.1 的话,至少得 Python 3.8 哦。 安装前,先查下有没有装过 Python ,终端(Wi…...
如何配置NGINX作为反向代理服务器来缓存后端服务的响应?
大家好,我是锋哥。今天分享关于【如何配置NGINX作为反向代理服务器来缓存后端服务的响应?】面试题。希望对大家有帮助; 如何配置NGINX作为反向代理服务器来缓存后端服务的响应? 1000道 互联网大厂Java工程师 精选面试题-Java资源…...
【Java IO流】File类基础详解
参考笔记:java File类基础 万字详解(通俗易懂)-CSDN博客 目录 1.前言 2. File类介绍 3. File类构造方法 4.File类常用的方法案例演示 4.1 创建文件/文件夹的方法 4.2 删除文件/文件夹的方法 4.3 判断文件/文件夹是否存在的方法 4.4 …...
《C#数据结构与算法》—201线性表
线性表的实现方式 顺序表 线性表的顺序存储是指在内存中用一块地址连续的空间依次存放线性表的数据元素,用这种方式存储的线性表叫顺序表。 特点:表中相邻的数据元素在内存中存储位置也相邻。 顺序表接口实现: 方法名参数返回值描述GetLen…...
MATLAB绘制局部放大图
今天,我将分享一段 MATLAB 代码,该代码生成了一个主副图结合的可视化展示,用于比较不同控制系统性能表现。 clc; clear; close all;% 生成时间向量 t 0:0.1:12;% 生成模拟数据 zero_feedback 0.5 * ones(size(t)); % 恒定…...
TS 常用类型
JS不会检查变量类型的变化 给变量规定特定的数据类型,错误赋值时会报错 优势:TS会标记出代码中的意外行为,尤其是typeerrors 具体实现:类型注解 JS和TS中数据类型的变化...
[Control-Chaos] Toxic Cascade(毒性級鏈)
信息 信息描述靶場名稱Toxic Cascade地址GitHub: Toxic Cascade難度中等人數推薦1人類型CTF、APT 攻擊模擬、故事解謎、化工工程與逆向工程描述Toxic Cascade 是一個結合 CTF、APT 攻擊模擬、故事解謎、化工工程與逆向工程的高度沉浸式靶場。該靶場具有獨特的情境背景與模擬真…...
纳米AI搜索体验:MCP工具的实际应用测试,撰写报告 / 爬虫小红书效果惊艳
1. 引言 近期测试了纳米AI搜索的MCP工具功能,重点体验了其智能体在报告生成和社交媒体数据分析方面的表现。平台整合了100多个MCP工具,通过本地化部署的方式,为用户提供了不同于云端方案的操作体验。本文将分享实际测试结果,包括智…...
React useMemo函数
第一个参数是回调函数,返回计算的结果,第二个参数是依赖项,该函数只监听count1变量的变化 import { useReducer, useState } from react; import ./App.css;// 定义一个Reducer函数 根据不同的action进行不同的状态修改 function reducer(st…...
第 1 篇:起点的选择:为何需要超越数组与链表?
大家好,欢迎来到“数据结构选型指南”系列!在软件开发中,数据是核心,而如何高效地组织和访问这些数据,则是程序性能的关键。选择合适的数据结构,就像为你的 Java 应用选择最优的“引擎零件”。今天…...
MySQL 索引不生效的情况
MySQL 索引不生效的 SQL 查询需要避免的情况 索引是提高 MySQL 查询性能的关键,但某些 SQL 写法会导致索引失效,从而影响查询效率。以下是需要避免的常见情况: 1. 使用 NOT、! 或 <> 操作符 -- 索引可能失效 SELECT * FROM users WH…...
【阿里云大模型高级工程师ACP学习笔记】2.9 大模型应用生产实践 (上篇)
特别说明:由于这一章节是2025年3月官方重点更新的部分,新增内容非常多,因此我不得不整理成上、下两篇,方便大家参考。 学习目标 备考阿里云大模型高级工程师ACP认证,旨在全面掌握大模型应用生产实践的专业知识,提升在该领域的实操技能与理论水平,为职业发展增添助力。具…...
STM32 ZIBEE DL-20 无线串口模块
一.配置方法 二.串口中断 u8 i; u16 buf[20],res; u8 receiving_flag 0; // 新增一个标志,用于标记是否开始接收数组 void USART1_IRQHandler(void) {if(USART_GetITStatus(USART1, USART_IT_RXNE) ! RESET) //接收中断{res USART_ReceiveData(USART1);if(receiv…...
【算法基础】选择排序算法 - JAVA
一、算法基础 1.1 什么是选择排序 选择排序是一种简单直观的排序算法,它的工作原理是:首先在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置,然后再从剩余未排序元素中继续寻找最小…...
FastAPI 与数据库交互示例
目录 安装必要的包完整代码示例运行应用使用说明API 端点说明代码解析 下面将创建一个简单的 FastAPI 应用程序,演示如何与 SQLite 数据库进行交互。这个例子包括创建、读取、更新和删除(CRUD)操作。 安装必要的包 首先,需要安装…...
(六——下)RestAPI 毛子(Http resilience/Refit/游标分页)
文章目录 项目地址一、Refit1.1 安装需要的包1.2 创建接口IGitHubApi1.3 创建RefitGitHubService1. 实现接口2. 注册服务 1.4 修改使用方法 二、Http resilience2.1 安装所需要的包2.2 创建resilience pipeline简单版2.3 创建全局的resilience处理1. 创建清理全局ResilienceHan…...
Rust 学习笔记:关于枚举与模式匹配的练习题
Rust 学习笔记:关于枚举与模式匹配的练习题 Rust 学习笔记:关于枚举与模式匹配的练习题以下程序能否通过编译?若能,输出是什么?考虑这两种表示结果类型的方式,若计算成功,则包含值 T;…...
父子组件双向绑定
v-model 语法糖实现 vue中我们在input中可以直接使用v-model来完成双向绑定,这个时候 v-model 通常会帮我们完成两件事: v-bind:value的数据绑定@input的事件监听如果我们现在封装了一个组件,其他地方在使用这个组件时,是否也可以使用v-model来同时完成这两个功能呢? 当我…...
系统思考与第一性原理
最近一直有客户提到“第一性原理”,希望借此穿透纷繁复杂的现象,看清事情的本质。我第一反应是:这与系统思考中的冰山模型不谋而合。 冰山模型中提到:我们看到的只是表面事件,事件背后有趋势,趋势背后有结…...
基于Redis实现-UV统计
基于Redis实现-UV统计 本文将使用HyperLogLog来实现UV统计。 首先我们搞懂两个概念: UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录一次…...
【iOS】类与对象底层探索
类与对象底层探索 Clang探索对象本质objc_setProperty源码探索cls与类的关联原理isa的类型isa_t原理探索 类&类的结构什么是元类NSObject到底有几个isa走位&继承关系图objc_class&objc_object 类结构分析计算cache类中的内存大小获取bits属性列表(prope…...
2025年- H18-Lc126-54.螺旋矩阵(矩阵)---java版
1.题目描述 2.思路* 思路1: 补充2: directions[1][0] // 表示“下”这个方向的行增量(1) directions[1][1] // 表示“下”这个方向的列增量(0) int[][] directions {{0, 1}, {1, 0}, {0, -1}, {-…...
Paddle Serving|部署一个自己的OCR识别服务器
前言 之前使用C部署了自己的OCR识别服务器,Socket网络传输部分是自己写的,回过头来一看,自己犯傻了,PaddleOCR本来就有自己的OCR服务器项目,叫PaddleServing,这里记录一下部署过程。 1 下载依赖环境 1.1 …...
yolov5 本地训练
YOLOv5 | Kaggle 直接gitclone他的源码用Vscode看(也可以直接把jupyter下下来) 他要1.8,我的是2.7,他这个代码可能有点年头了 两年前了 他的环境 我的环境 我就是不懂为什么清华源的torch windows默认下出来是cpu版本 . 在终端…...
同城跑腿小程序帮取帮送接单抢单预约取件智能派单同城配送全开源运营版源码优创
一、源码描述 这是一套同城跑腿小程序,基于FastadminUniapp框架,全开源无加密,可私有化部署,包含用户端、骑手端和运营端(后端),支持帮取/帮送模式,支持一键接单/抢单,主…...
基于SpringBoot的药房药品销售管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...
机器学习中的学习率及其衰减方法全面解析
摘要: 本文深入解析机器学习中的学习率及其衰减方法,涵盖学习率的作用、常用衰减参数及七种主流衰减策略(分段常数、指数、自然指数、多项式、余弦、线性余弦、噪声线性余弦)。通过公式推导与图示对比,揭示不同衰减方式…...
硬件性能与能效比竞赛:解码 PC 硬件的 “速度与激情”
引言:当性能遇见能效,一场永不停歇的算力革命 在数字内容爆炸式增长的时代,无论是 4K/8K 游戏的极致画质追求,还是 AI 大模型的本地化部署需求,亦或是内容创作者对实时渲染的效率渴求,都在推动 PC 硬件走向…...
大模型在终末期肾脏病风险预测与临床方案制定中的应用研究
目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 1.3 研究方法与数据来源 二、终末期肾脏病概述 2.1 定义与诊断标准 2.2 发病机制与影响因素 2.3 现状与危害 三、大模型技术原理及应用现状 3.1 大模型基本原理 3.2 在医疗领域应用案例 3.3 在终末期肾脏病…...
【C++11】智能指针
📝前言: 这篇文章我们来讲讲C11——智能指针: 🎬个人简介:努力学习ing 📋个人专栏:C学习笔记 🎀CSDN主页 愚润求学 🌄其他专栏:C语言入门基础,pyt…...
华为云Astro轻应用利用自定义连接器调用第三方接口实际操作
样图 说明 华为云Astro轻应用通过自定义连接器调用第三方接口具有多方面的作用,主要体现在以下几点: 扩展功能与集成能力 调用第三方服务:通过配置自定义连接器,Astro轻应用可以调用第三方提供的Rest协议接口,实现第三方提供的业务功能,扩展应用的能力。 集成外部系统:…...
【中间件】brpc_基础_butex.h
butex.h 学习笔记 源码 1 概述 butex.h 提供了一种用户态同步原语 butex(类似 Linux 的 futex),专为 bthread 设计,用于高效协调线程的阻塞与唤醒。其核心是通过原子操作结合等待队列管理,减少内核态切换开销&#…...
数字智慧方案5876丨智慧交通枢纽智能化系统建设方案(56页PPT)(文末有下载方式)
篇幅所限,本文只提供部分资料内容,完整资料请看下面链接 https://download.csdn.net/download/2301_78256053/89575493 资料解读:智慧交通枢纽智能化系统建设方案 详细资料请看本解读文章的最后内容。 随着城市化进程的加速,交…...
深度学习笔记40_中文文本分类-Pytorch实现
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 | 接辅导、项目定制 一、我的环境 1.语言环境:Python 3.8 2.编译器:Pycharm 3.深度学习环境: torch1.12.1cu113torchvision…...
python设置word字体的方法
在Python中,可以使用python-docx库来设置Word文档的字体样式,以下为具体方法和示例代码: 一、设置段落中字体样式 使用python-docx库时,Word文档中的文本通常被组织成段落(Paragraph对象),而一…...
golang常用库之-标准库text/template
文章目录 golang常用库之-标准库text/template背景什么是text/templatetext/template库的使用 golang常用库之-标准库text/template 背景 在许多编程场景中,我们经常需要把数据按照某种格式进行输出,比如生成HTML页面,或者生成配置文件。这…...
【JAVA】如何快速阅读一个基于maven构建的springboot项目
一、摘要 在JAVA项目开发过程中,现在比较流行的是springboot机构,特别是在后端开发的项目中,springboot应用的非常普遍。springboot很好将大型的、复杂的项目进行分解,以模块或者服务的表现形式组成项目。那么当我们接手一个陌生的…...
Fedora升级Google Chrome出现GPG check FAILED问题解决办法
https://dl.google.com/linux/linux_signing_key.pub 的 GPG 公钥(0x7FAC5991)已安装 https://dl.google.com/linux/linux_signing_key.pub 的 GPG 公钥(0xD38B4796)已安装 仓库 "google-chrome" 的 GPG 公钥已安装,但是不适用于此软件包。 请检查此仓库的…...
深入解析MapReduce:大数据处理的经典范式
引言 在大数据时代,如何高效处理海量数据成为技术核心挑战之一。Hadoop生态中的MapReduce框架应运而生,以其“分而治之”的思想解决了大规模数据的并行计算问题。本文将从原理、核心组件到实战案例,带你全面理解这一经典计算模型。 一、MapR…...
JVM性能调优的基础知识 | JVM内部优化与运行时优化
目录 JVM内部的优化逻辑 JVM的执行引擎 解释执行器 即时编译器 JVM采用哪种方式? 即时编译器类型 JVM的分层编译5大级别: 分层编译级别: 热点代码: 如何找到热点代码? java两大计数器: OSR 编译…...
云计算-容器云-部署jumpserver 版本2
应用部署:堡垒机部署 # 使用提供的软件包配置Yum源,通过地址将jumpserver.tar.gz软件包下载至Jumpserver节点的/root目录下 [rootjumpserver ~]# tar -zxvf jumpserver.tar.gz -C /opt/ [rootjumpserver ~]# cp /opt/local.repo /etc/yum.repos.d/ [roo…...
MSP430G2553驱动0.96英寸OLED(硬件iic)
1.前言 最近需要用MSP430单片机做一个大作业,需要用到OLED模块,在这里记录一下 本篇文章主要讲解MSP430硬件iic的配置和OLED函数的调用,不会详细讲解OLED显示原理(其实就是江科大的OLED模块如何移植到msp430上).OLED显示原理以及底层函数讲解请参考其他…...
同质化的旅游内核
湘西凤凰古城、北京非常有文艺氛围的方家胡同都在被改造翻新为现代的其他城市范式式的样式。 什么意思呢?很多古城的老房子,从外面看,很古老、很漂亮,但是进去以后,完全不是那么回事,整座房子已经被完全掏…...
2025年五一数学建模A题【支路车流量推测】原创论文讲解(含完整python代码)
大家好呀,从发布赛题一直到现在,总算完成了2025年五一数学建模A题【支路车流量推测】完整的成品论文。 本论文可以保证原创,保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 A题论文共104页&a…...
文章六:《循环神经网络(RNN)与自然语言处理》
文章6:循环神经网络(RNN)与自然语言处理——让AI学会"说人话" 引言:你的手机为什么能秒懂你? 当你说"我想看科幻片"时,AI助手能立刻推荐《星际穿越》,这背后是RNN在"…...
Redis总结及设置营业状态案例
Redis简介: rRedis服务开启与停止: 服务开启: 在Redis配置文件中输入cmd进入命令行输入redis-server redis-cli.exe -h -p:连接到redis服务 设置密码:在redis.windows.conf中找到requirepass 密码 服务停止: 在服务开启的界面按ctrlc Redis数据类…...
中科大:LLM几何推理数据生成
📖标题:Enhancing the Geometric Problem-Solving Ability of Multimodal LLMs via Symbolic-Neural Integration 🌐来源:arXiv, 2504.12773 🌟摘要 🔸多模态大语言模型(MLLM)的最…...
AimRT从入门到精通 - 04RPC客户端和服务器
一、ROS中的service通信机制 服务通信也是ROS中一种极其常用的通信模式,服务通信是基于请求响应模式的,是一种应答机制。也即:一个节点A向另一个节点B发送请求,B接收处理请求并产生响应结果返回给A。比如如下场景: 机器…...