什么是事务?
MySQL :: MySQL 8.0 参考手册 :: MySQL 词汇表
- TPS:"每秒事务数"的缩写。
事务
注意:MySQL是不支持嵌套事务的,在已经开启了一个事务的情况下,若再开启一个事务,会隐式的提交上一个事务!
在MySQL数据库中只有使用了InnoDB存储引擎的数据库或表才支持事务!
- 事务是可以提交或回滚的原子工作单元。单条或多条SQL语句都属于是一个工作单元。
- 当事务对数据库进行多次更改时,要么在提交事务时所有更改都成功,要么在回滚事务时所有更改都撤销。
事务是由 MySQL 的存储引擎来实现的,我们常见的 InnoDB 存储引擎它是支持事务的。
不过并不是所有的存储引擎都能支持事务,比如 MySQL 原生的 MyISAM 存储引擎就不支持事务,也正是这样,所以大多数 MySQL 的引擎都是用 InnoDB。
事务看起来感觉简单,但是要实现事务必须要遵守 ACID 4 个特性!
- 由InnoDB实现的数据库事务具体统称为ACID的属性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolctaion)和持久性(Durability)。
- 一致性也可以保证在集群架构下主从是一致的。
- 持久性是去保证一致性的前提,持久性:我去做任何数据的更改都会持久化到磁盘,不会因为断电而导致数据的丢失。
- RedoLog重做日志是去保证持久性的,即持久性通过RedoLog重做日志实现,因为如果你在内存里面更改了以后没有同步到磁盘,假如发生宕机,就会导致数据丢失,如果发生数据丢失,一致性就保证不了了。
两阶段提交
- 在XA规范下,作为分布式事务的一部分的操作(有时缩写为2PC)。
- 当多个数据库参与事务时,要么所有数据库提交更改,要么所有数据库回滚更改。
开启、提交、回滚事务
MySQL :: MySQL 8.0 Reference Manual :: 13.3.1 START TRANSACTION, COMMIT, and ROLLBACK Statements
只读事务和读写事务 & begin VS start transaction:
- begin跟start transaction它的区别在于:start transaction它可以加参数,就是我在启动的时候我可以去加一些参数,比如:
- 我们可以使用read only开启只读事务,开启只读事务模式之后,事务执行期间任何 insert 或者 update语句都是不允许的!
- 如果用begin去开启事务,默认就是读写事务,并且默认它不会去生成一个快照。
注意:执行「开始事务」命令,并不意味着启动了事务!
在 MySQL 有两种开启事务的命令,分别是:
- 第一种:begin/start transaction 命令;
- 第二种:start transaction with consistent snapshot 命令;
这两种开启事务的命令,事务的启动时机是不同的:
- 执行了 begin/start transaction 命令后,并不代表事务启动了,只有在执行这个命令后,执行了增删查改操作的 SQL 语句,才是事务真正启动的时机;
- 执行了 start transaction with consistent snapshot 命令,就会马上启动事务。
隐式自动提交
- 我们知道,一条或多条SQL语句,都是属于一个工作单元,那为什么我们平时写SQL语句的时候,不需要手动去写start transaction或者begin来开启事务呢?
- 这是因为在我们的MySQL里面,事务是默认自动提交的,也就是说,当执行一条DML语句时,MySQL会立即隐式的提交事务。
查询是否开启自动提交事务
关闭自动提交事务(一般不会关闭...)
不能回滚的语句(来自官网原语)
MySQL :: MySQL 8.0 Reference Manual :: 13.3.2 Statements That Cannot Be Rolled Back
- 有些语句无法回滚,一般来说,这些包括DDL - 数据定义语言(data definition language, DDL)语句,例如创建或删除数据库的语句。
SavePoint回滚点
- 事务是保证单条或者多条SQL语句要么同时执行成功,要么全部回滚。
- 其实,InnoDB是支持回滚点的操作的,何为回滚点,就是我可以回滚部分操作、提交部分操作。
查看事务
- 事务一定是可以并行的,因为MySQL服务端是允许多个客户端连接的,意味着MySQL会出现同时处理多个事务的情况,因此我们去操作语句的时候肯定会有很多的客户端。
怎样去查看当前所在的事务信息呢?
- 除了你的业务库以外,MySQL它是有系统库的,这个系统库里面有个很重要的叫information_schema,这里面会有很多统计信息,这里面有一个表叫INNODB_TRX,这个表可以去查看我当前没有提交的所有的事务信息,包括事物的id(trx_id)、事务的执行状态(trx_state)、事务开启的时间(trx_started)、trx_weight(事务的权重)、trx_isolation_level(当前事务的隔离级别)、trx_query(事务正在执行的SQL语句)、事务是否在等待锁等等。
为什么会有trx_weight权重?
- 事务的权重反映被更改的行数和被事务锁定的行数。由于事务跟事务之间有可能会死锁,形成死锁的时候我需要去回滚,为了解决死锁,InnoDB选择权重最小的事务作为"受害者"进行回滚。
事务id:
数据量比较大的表在做修改操作的时候可以分批执行。
如果不是一个只读的事务,那么每个事务都会有一个自增的事务ID(trx_id),即每个非只读事务都有个递增的事务ID;如果是一个只读的事务(select from),它没有事务ID。
所以,我们所有的数据操作都是由事务(ID)来完成的,每行数据都会有一个隐藏的字段,叫做事务ID:TRX_ID,代表这个数据修改的最后的事务ID。
InnoDB和ACID模型的底层实现原理 事务的原理 ⇒ MySQL的InnoDB引擎是如何保证事务的四大特性的?
- ACID模型是一组数据库设计原则,强调对业务数据和关键任务应用程序非常重要的可靠性方面。
- MySQL包含了一些组件,比如紧密遵循ACID模型的InnoDB存储引擎,这样在软件崩溃和硬件故障等异常情况下,数据不会被破坏,结果也不会被扭曲。
下面将讨论MySQL的特性,特别是InnoDB存储引擎如何与ACID模型的类别进行交互:
事务看起来感觉简单,但是要实现事务必须要遵守 4 个特性,分别如下:
1. Atomicity - 原子性
- ACID模型的原子性,主要涉及到的是InnoDB存储引擎的事务。
- 原子性是指事务是一个不可分割的工作单元,事务中的操作要么同时执行成功,要么全部回滚,即事务是保证单条或多条SQL语句要么同时执行成功,要么全部回滚。
- 相关的MySQL功能包括:自动提交设置、COMMIT语句、ROLLBACK语句。
- 原子性是依靠UndoLog回滚日志来实现的,每次对数据做修改、删除或插入的操作都会生成一条UndoLog日志来记录操作之前的数据状态,使用ROLLBACK的语句能够将所有执行成功的SQL语句产生的效果撤回。
原子性 ⇒ 如果让你来恢复数据的话,你会怎么来恢复?
- 我执行了一条insert语句,只需要再执行一条delete语句就可以把它恢复过去;
- 如果执行我执行了一条delete语句,我只需要再执行一条insert语句就可以把它恢复回去;
- 如果执行了一条update语句,我可以反向的执行一条update语句从而把它恢复回去。
WAL - Write Ahead Log:预写日志,顾名思义,写磁盘数据之前先写日志,为什么?
- WAL技术指的是,MySQL的写操作并不是立刻写到磁盘上的,而是先写日志,然后再合适的时间再写到磁盘上!
- 因为表数据在磁盘上面是随机分布的,因为与磁盘IO是随机读写的,而日志文件是顺序读写的,效率更高,所以先写日志,假如说异步写磁盘失败了,还可以根据日志把丢失的修改数据补回来,减少数据丢失的可能性,redo log就是预写日志的机制!
什么是事务的两阶段提交?
两阶段提交的问题
- 我们知道redolog是属于InnoDB的,而binlog是属于MySQL Server的,因此在MySQL的InnoDB存储引擎当中,在开启binlog的情况下,两个日志文件是有可能同时存在的,当两种日志文件同时存在时,是先写binlog呢还是先写redolog呢?’
- 其实,不管你是先写binlog还是先写redolog它都有问题。
假设我们执行一条SQL语句做update操作:
- 假设redolog写成功了,也就是在将 redo log 刷入磁盘之后,但是来得及写入binlog,系统崩了或者系统突然断电了。在MySQL重启后,可以根据redolog来进行数据恢复,但是,binlog由于没有写成功,所以它是没有记录下来这次变更的,这也就意味着,主从同步的时候,是缺少了一条SQL的,导致主从两台机器数据不一致!
- 如果是先写入binlog成功了,即在将 bin log 刷入到磁盘之后,但是没来得及写入redolog,系统崩了或MySQL突然宕机了,在MySQL重启后,崩溃恢复的时候由于redolog没有写成功,所以数据库记录的数据还是旧值,但是因为binlog已经写入成功了,所以在做主从同步的时候,就会把新值同步到从库或从节点,就导致了主从库之间的数据不一致!
- 因此才引入了两阶段提交,事务提交后,redo log 和 bin log都要持久化到磁盘,如果不引入两阶段提交的话,在 bin log 和 redo log没办法保证一致性的情况(即出现半成功的状态),就会导致主从库之间的数据不一致!这是因为 redo log 影响主库的数据,而bin log影响从库的数据,所以 redo log 和 bin log必须保持一致才能保证主从数据的一致性!
- 所以MySQL为了避免 redo log 和 bin log 这两份日志文件之间的逻辑不一致的问题,就引入了MySQL事务的两阶段提交,就是在更新的过程中,来整体的控制 redo log 和 bin log 的一致性写入的一种手段。
- 两阶段提交其实是分布式事务的一致性协议,它可以保证多个逻辑操作要不全部成功,要么全部失败,不会出现半成功的状态。
两阶段提交的过程
两阶段提交把单个事务的提交拆分成了2个阶段,就是将 redo log的写入拆分成了两个步骤,分别是 Prpare-准备阶段 和 Commit-提交阶段,中间再穿插写入 bin log。
- 注意:不要把Commit提交阶段和commit语句混淆了,commit语句执行的时候,会包含Commit提交阶段!
Prepare阶段(预提交) - 先写 redo log
- 这个阶段SQL已经成功执行并生成 redo log,同时将 redo log对应的事务状态设置为prepare。 => 注意:处于prepare状态的 redo log 是不能够直接使用的~!
BinLog持久化到磁盘(sync_binlog = 1的作用) - 再写 bin log
- binlog提交,通过write()将binlog内存日志数据写入到文件缓冲区;
- 再通过fsync()将binlog从文件缓冲区永久写入到磁盘;
Commit阶段 - 再写 redo log
- 调用执行引擎内部的提交事务接口,将 redo log 状态设置为 commit,说白了就是给之前写入的数据打上commit标识,事物两阶段提交完成。
- 只要 bin log 写磁盘成功,就算 redo log的状态还是prepare也没有关系,一样会被认为事务已经执行成功。‘
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- write()和fsync(刷盘)是与文件系统和磁盘IO相关的两个不同的操作。
- write()操作是将数据写入文件的缓冲区,也就意味着write操作完成后,并不一定立即将数据持久化导磁盘上,而是将数据暂时存储在内存中。
- fsync(刷盘)用于强制将文件的修改持久化到磁盘上,它通常与write()操作配合使用,以确保文件的修改在fsync()操作完成之后被写入磁盘。
两阶段提交是如何保证主从数据一致性的?
- 情况一:假设我在第一次写完 redo log 之后断电了,即一阶段提交之后,处于prepare状态的时候崩溃了,断电之后我在进行数据恢复或崩溃恢复的时候,我读的是 redo log,但是此时 redo log 是处于prepare状态,处于prepare状态的 redo log 是不能够直接使用的,所以我要根据相应的事务id去 bin log 里面查询,如果能查到匹配的记录,意味着这条数据是有效数据;如果在 bin log 里面没有找到与之匹配的记录,意味着这是一条无效数据,直接丢弃,回滚事务即可,这样主从是一致的,就都没有执行整个事务。
- 情况二:一阶段提交成功,写完 bin log 之后崩溃了,此时,redo log 处于prepare状态,bin log已写入,这时候检查 bin log 中的事务是否存在并且完整,如果存在且完整,则直接提交事务,更改 redo log为commit状态;如果不存在或者不完整,则回滚事务。
- 情况三:假设 redo log 处于commit状态的时候崩溃了,此时数据库重启后,先检查 bin log 中的事务是否存在并且完整,如果存在且完整,则直接提交事务;如果不存在或者不完整,则回滚事务。
- 由此可见,两阶段提交能够确保数据的一致性!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- redo log 可以在事务没提交之前持久化到磁盘,但是 bin log 必须在事务提交之后,才可以持久化到磁盘!
- redo log 是固定大小的,redo log 是一个循环写的过程,如果写满了,同时你很多数据还没有落盘,那么就会紧急的把之前没有落盘的数据进行落盘,腾出一段儿空间来来继续写!
如何判断binlog和redolog达成一致了?
- 当MySQL写完 redo log 并将它标记为prepare状态时,会在redolog中记录一个XID,它全局唯一的标识着这个事务,接着mysql就会对应binlog并且会直接将其刷新到磁盘中,binlog记录的row格式结束的位置上也有一个XID,只要这个XID和redolog中记录的XID是一致的,MySQL就会认为binlog和redolog逻辑上是一致的!
并发事务会引发什么问题?- 数据一致性问题
- 事务一定是可以并行的,因为MySQL服务端是允许多个客户端连接的,意味着MySQL会出现同时处理多个事务的情况,因此我们去操作语句的时候肯定会有很多的客户端。
- 既然事务是可以并行或并发的时候,那么就一定会有数据一致性问题,因为两个事务可能同时改相关的数据,这个时候我不知道应该听谁的,所以会有数据一致性问题,有可能导致你改的数据跟你要的数据是不一致的。
针对不同的隔离级别,并发事务时可能发生的现象也会不同。
- InnoDB使用不同的锁策略支持这里描述的每一种事务隔离级别。
- 用户可以通过默认的可重复读取级别来确保操作的高度一致性,在关键数据上的操作符合ACID要求非常重要。或者,您可以放宽已提交读甚至未提交读的一致性规则,在批量报告等情况下,精确的一致性和可重复的结果比最小化锁定的开销更重要。
- 用户可以更改单个会话或使用SET TRANSACTION语句的所有后续连接的隔离级别。要为所有连接设置服务器的默认隔离级别。
用户可以更改或设置Session会话或者全局Global的隔离级别:
- SET [Global | Session] - 作用域 TRANSACTION
- SESSION和GLOBAL关键字用来指定修改的事务的隔离级别的范围;
- SESSION:表示修改的事务隔离界别将应用于当前session(当前CMD窗口)内的所有事务;
- GLOBAL:表示修改的事务隔离级别将应用于所有session(全局)中的所有事务,且当前已经存在的session不受影响!
- 如果省略SESSION和GLOBAL,表示修改的事务隔离级别将应用于当前Session内的下一个还未开始的事务!
- 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
- 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
- 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。
MySQL InnoDB存储引擎的RR隔离级别根据不同的查询方式如何解决幻读问题?
MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了,详见这篇文章 (opens new window)),根据不同的查询方式,分别提出了两种解决幻读的方案:
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
- 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
- 快照读是MVCC实现的基础,而当前读是悲观锁实现的基础!
- MVCC全称是Multi-Version Concurrency Control - 多版本并发控制是指维护一个数据的多个版本,使得读写操作没有冲突,快照读为MySQL实现MVCC提供了一个非阻塞读的功能!是指在数据库中为了实现高并发的数据访问,对数据进行多版本处理,并通过事务的可见性来保证事务能看到自己应该看到的数据版本,实现对数据库的并发访问!
- MVCC它的主要作用就是在快照读的时候来决定我们读取的到底是哪一个历史记录版本!
- 多版本指的是版本链,并发指的是并发场景下多个线程对同一行数据进行读写,控制指的是用版本链控制事务读取的结果是哪个版本的数据。
- MVCC最大的好处是读不加锁,读写不冲突,在读多写少的系统应用中,读写不冲突是非常重要的,极大的提升系统的并发性能,这也是为什么现阶段几乎所有的关系型数据库都支持MVCC的原因,不过目前MVCC只在Read Commited和Repeatable Read两种隔离级别下工作。
- MVCC主要是为了提高并发的读写性能,不用加锁就能让多个事务并发读写!
MVCC实现原理
- MVCC的实现是由三部分组成的,分别是 数据库行记录中的三个隐藏或隐式字段 + undo log版本链 + ReadView!
- undo log中保存了历史快照,而ReadView用来判断具体哪一个快照是可见的~!
当前读 & 快照读
当前读:表示读取的数据都是最新的数据,即读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁!比如加锁的select或者对数据进行增删改都会进行当前读:、select...lock in share mode(共享锁),select...for update、update、insert、delete(排它锁),这些操作全部都会触发当前读。
- 快照读:读取的数据不一定是最新的数据,就是读取的是快照数据,有可能是历史版本的数据,普通的不加锁的select,就是单纯的select不加锁的语句就是快照读。不加锁,是非阻塞读!
- 快照读是MVCC实现的基础,而当前读是悲观锁实现的基础!
- 在MySQL中,只有Read Committed和Repeatable Read这两种事务隔离级别才会使用快照读!
- Read Committed:每次select,都生成一个快照读!
- Repeatable Read:开启事务后的第一个select语句才是快照读的地方,后面执行相同的select语句都是从快照中获取数据,可能不是当前的最新数据,这样也就保证了可重复读!
- Serializable - 串行化:快照读会退化为当前读!
什么时候会有快照读的场景?
- 可重复读啊!因为不可重复读读取到的不就是最新的数据吗?
数据库的并发场景有哪些?
- 读读:不存在任何问题,不需要并发控制。
- 读写:会有数据安全问题,可能会遇到脏读、幻读、不可重复读 ⇒ MVCC重点解决的是并发读写的问题
- 写写:会有数据安全问题,可能会遇到更新丢失问题!写写并发这种情况比较常用的就是通过加锁的方式实现!
在MySQL里面,有两种不同的解决方案,这两种解决方案可以配合使用:
快照读 - 非锁定一致性读取 - Consistent Nonlocking Reads - 不加锁(MVCC)
- 用在只读场景。
- 快照读就是使用快照信息显示基于某个时间点的查询结果~!像我们常用的普通的select语句不加锁的情况下就是快照读~!
- 快照读比较适合对于数据一致性要求不是特别高且追求极致性能的业务场景。
- 一致性读取意味着InnoDB使用多版本来为查询提供数据库在某个时间点的快照。查询会看到在该时间点之前提交的事务所做的更改,而不会看到后面或未提交的事务所做的更改。
所以关键在于快照!- Snapshot - 快照:在某个时间节点我会去记录某些数据提供给后面使用!快照即记录数据的历史版本,每行记录可能存在多个历史版本(多版本技术)。相当于就是一张照片,定格某一时刻的风景!
某个时间节点:RC和RR生成ReadView读示图(快照)的时机?
- 可以在start transaction开启事务的时候可以指定生成快照!
- 在MySQL中,只有在事务隔离级别为Read Committed(读已提交)和RepeatableRead(可重复读)下,InnoDB才会使用非锁定一致性读取或快照读。
- 对于RC和RR隔离级别的事务来说,它们是通过ReadView来实现的,它们的区别在于创建ReadView的时机不同:
- 如果隔离级别是读已提交Read Committed,则是在每次SQL语句执行前都会重新生成一个最新的ReadView,即每次select都会生成一个新的ReadView,因此每次slelect查询总是读取行记录的最新版本!这也就意味着,事务期间多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另一个事务修改了该记录,并提交了事务!
- 如果隔离级别是可重复读Repeatable Read,则是在[启动事务时] - 即第一次执行SQL语句时,第一次select查询时会生成一个ReadView,并且整个事务期间都在用这个ReadView,即后续所有的快照读都是用的同一个ReadView快照,这样就保证了在事务执行期间读到的数据都是事务启动前的数据,所以就不会发生不可重复读的问题了!只有在本事务中对数据进行更改才会更新快照~!
- 这两个隔离级别实现是通过 事务的ReadView里的字段 和 记录中的两个隐藏列 的比对,来控制并发事务访问同一个记录时的行为,这就叫MVCC(多版本并发控制)。
Snapshot快照到底去保存哪些数据呢?
- 保存的是一个ReadView的数据结构,ReadView说白了就是一个数据快照,是在某个时间节点去创建的一个Class结构。
我们需要了解两个知识:
- Read View 中四个字段作用;
- 聚簇索引记录中两个跟事务有关的隐藏列;
聚簇索引记录中跟事务相关的行隐藏字段或隐藏列,即行记录的三个隐藏字段:
对于InnoDB存储引擎的数据库表,当插入数据的时候,除了我们显式定义的字段之外,MySQL会生成一些隐藏字段,对用户不可见,它的聚簇索引记录中都包含下面几个隐藏列:
- DB_ROW_ID:隐藏主键,并不是每一张表都会生成,只有当数据库表没有主键或非空(的)唯一索引时,InnoDB才将会生成该隐藏字段,并且基于该递增字段来去建立主键,大小为6字节。
- DB_TRX_ID:翻译过来就是事务ID,更新这行数据的事务ID或最新修改事务ID,记录插入这条记录或者最后一次修改该记录的事务ID的值。事务ID是自增的!
- DB_ROLL_PTR:Roll-Pointer{回滚指针},被改动前的 undo log日志指针,指向该记录上一个历史版本状态的数据,通过它可以来找到该记录修改前的信息,用于配合undo log,指向上一个版本。
MySQL中的事务ID是系统生成的,用户不可控,同时事务ID是自增的!
undo log - 回滚日志
- 回滚日志,在insert、update、delete的时候产生的便于数据回滚的日志!
- 当insert的时候,产生的undo log日志只再回滚时需要,在事务提交后,可被立即删除!
- 而当update、delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会被立即删除。
为什么需要 undo log?
- 我们在执行一条"增删改"语句的时候,虽然没有输入begin开启事务和commit提交事务,但是MySQL会隐式开启事务来执行"增删改"语句,执行完会自动提交事务,这样就保证了执行完"增删改"语句后,我们可以及时在数据库表中看到"增删改"的结果了。
- 执行一条语句是否自动提交事务,是由 autocommit 参数决定的,默认开启,所以,执行一条update语句也是会使用事务的。
- 如果一个事务在执行过程中,在还没有提交事务之前,MySQL发生了崩溃,就需要依靠undo log回滚到事务之前的数据,在事务没提交之前,MySQL会记录更新前的数据到undo log回滚日志文件里面,当事务回滚时,就需要利用 undo log来进行回滚!
- undo log回滚日志,它保证了事务的ACID特性中的原子性(Atomicity)!
- undo log表示在进行insert、delete、update操作的时候生成的方便回滚的日志~!
undo log会无限膨胀吗?
- undo log不会无限增加,后台会有一个单独的线程叫做purge,会定期进行清理!
另外,undo log 还有一个作用,通过 ReadView + undo log 实现 MVCC(多版本并发控制)。
当我们在进行查询的时候,到底应该返回哪一个历史版本记录呢?
- 这个实际上不是由undo log的版本链来控制的,具体要返回哪一个版本是由ReadView来控制的!
- ReadView(读视图)是快照读SQL执行时MVCC提取数据的依据,记录并维护着系统当前活跃的事务(未提交的事务)id!
- ReadView称之为读视图,指的是事务在进行快照读操作的时候产生的读视图,ReadView中存储的并不是真实的数据,而是存储的是事务id的信息!
- ReadView是在某个事务里面去生成的! => 事务的ReadView
- READ COMMITTED:在事务中每一次执行快照读时生成ReadView!
- REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView!
MVCC案例引出:
案例一:很重要
- 我们都知道InnoDB默认的事务隔离级别是Repeatable Read - RR(可重复读),解决了在并发事务下不可重复读的问题,也就是我在我当前的这个事务窗口是读不到其他事务窗口update修改并已经commit提交的数据,保证了事务与事务之间的隔离性!
- 但是,通过刚才的案例,我们看到了在RR隔离级别下,关系隐式自动提交事务(set autocommit = 0)后,开启手动提交事务,并且开启了两个不同的窗口,在另一个窗口修改表中数据并提交,竟然在该窗口中select读到了另一个窗口修改并已经提交的数据,不是说在RR隔离级别下解决了不可重复读的问题吗???
来判断事务1第一次select之后能否读取到更新之后的数据?
能!
结论:DB_TRX_ID不在活跃事务列表m_ids中,则说明这个事务在ReadView生成之前就已经commit提交了,所以该版本的记录对当前事务是可见的!
- 事务1执行了一次select,会生成一个ReadView。
案例二:很重要
来判断事务1第二次select之后能否读取到更新之后的数据?
- 不能!
- 结论:DB_TRX_ID在活跃事务列表m_ids中,则代表在ReadView生成时刻,这个事务还是活跃状态,还没有commit提交,所以该版本的记录对当前事务不可见!
- 虽然事务1里面执行了两次select语句,但由于InnoDB默认隔离级别时RR,如果隔离级别是RR,则是在第一次select查询时会生成一个ReadView,并且整个事务期间都在用这个ReadView,因此虽然事务1执行了两次select语句,但是只生成一个ReadView!
- 因为可见性算法是固定死的,不会发生任何改变,唯一可能改变的就是ReadView的值!
为什么RR隔离级别可以解决不可重复读的问题?
- MVCC是MySQL底层的实现机制,这个机制是由MySQL的Server来主动来控制的,用户是不可干预的,但是作为用户是需要控制数据是否可见的,因此必须要给到用户一个选项或者配置来控制数据是否可见,那么最关键的点是在于ReadView的生成时刻,也就是说能否读取到修改之后的数据取决于ReadView是在什么时间生成的。
对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同:
- RC:读已提交隔离级别是每次进行快照读操作的时候都会生成ReadView。
- RR:可重复读隔离级别只有在事务第一次进行快照读的时候会生成ReadView,之后执行快照读操作的时候都会沿用第一次生成的ReadView视图。
- RR看不到已经修改的数据,它的前提是在你修改之前你已经执行过一次快照读了,如果没有进行过快照读!如果你之前从来没有执行过快照读,那么你依然可以看到更新之后的结果或者数据!
- RR隔离级别能够解决不可重复读的最关键点也在于ReadView没有变!RR隔离级别能够解决不可重复读的根本原因就在于沿用了第一次快照读产生的ReadView!
- 这两个隔离级别实现是通过「事务的 Read View 里的字段」和「记录中的两个隐藏列」的比对,来控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。
- 在可重复读隔离级别中,普通的 select 语句就是基于 MVCC 实现的快照读,也就是不会加锁的。而 select .. for update 语句就不是快照读了,而是当前读了,也就是每次读都是拿到最新版本的数据,但是它会对读到的记录加上 next-key lock 锁。
- Oracle默认隔离级别就是RC,MySQL默认隔离级别就是RR。
提示:与事务的隔离级别无关!!!
补充:
- 查询当前窗口的事务隔离界别:select @@transaction_isolation;
- 1或者ON代表开启,0或者OFF表示关闭!!!
当前读 - 锁定一致性读取 - Consistent Locking Reads - 加锁
幻读产生的根本原因就在于不可重复读的问题。为了解决不可重复读的问题,所以引入了幻读问题!
幻读案例引出:
此时问题抛出:为什么我select查询的时候是2条,而update修改的时候显示修改的记录数却是三条?本质点在哪里?
- 前面两次select都是快照读,读的都是历史数据;而我们的update是当前读,读的是最新数据,所以有了幻读的问题!
- 所以幻读产生的根本原因在于当前读和快照读一起使用,也就是只有当前读和快照读一起使用的时候,才有幻读的问题!如果只有快照读,那么永远不会发生幻读的问题!
- 幻读必须要通过加锁来解决!
以上就是本篇文章【深入解析InnoDB事务】的全部内容了,欢迎阅览 ! 文章地址:http://ww.kub2b.com/news/12950.html 栏目首页 相关文章 动态 同类文章 热门文章 网站地图 返回首页 企库往资讯移动站 http://ww.kub2b.com/mobile/ , 查看更多