mysql/mariadb知识点总结 之 事务总结

目录

事务相关概念

1、事务概述

2、redo log概述

3、undo log概述

4、log group概述

5、总结

事务控制语句

1、事务日志参数

2、事务控制语句

事务隔离级别

1、事务隔离级别概述

2、隔离级别:可重读

3、隔离级别:串行化

4、隔离级别:读已提交

5、隔离级别:读未提交

总结


avatar

事务相关概念

1、事务概述

  事务可以理解为是一组sql语句的集合。事务可以只包含一条sql语句,也可以包含多条复杂的sql语句,事务中的所有sql语句被当做一个操作单元,换句话说,事务中的sql语句要么都执行成功,要么全部执行失败。事务内的sql语句被当做一个整体,被当做一个原子进行操作。

  mysql中,innodb存储引擎是支持事务的,而且innodb存储引擎的事务完全符合ACID的特性,ACID是如下四大特性的首字母缩写:

A(atomicity):原子性

C(consistency):一致性

I(isolation):隔离性

D(durability):持久性

原子性:整个事务中的所有操作要么全部执行成功,要么全部执行失败后混滚到最初状态。

一致性:数据库总是从一个一致性状态转为另一个一致性状态。

隔离性:一个事务在提交之前所做出的的操作是否能为其他事务可见,由于不同的场景需求不同,所以针对隔离性来说,有不同的隔离级别。

持久性:事务一旦提交,事务所做出的修改将会永久保存,此时即使数据库崩溃,修改的数据也不会丢失。

上述特性是ACID的理论基础,而innodb存储引擎的事务又符合ACID的特性,那么,我们来举个栗子,以方便我们理解。

举例:

  首先我们来描述一个场景,在学习事务时一般都会使用这个场景作为举例,这个场景就是”转账”:

“天朝银行”有很多用户,目前,A用户账户上的余额为8000元,B用户账上的余额为5000元,现在A用户要向B用户转账1000元。

那么,当转账结束以后,A用户账户上的余额应该为7000元,B账户上的余额应该为6000元。

那么上述过程在数据库中应该转换为如下操作:

操作1:修改A用户账户对应的余额记录,8000-1000

操作2:修改B用户账户对应的余额记录,5000+1000

  上述操作好像没毛病,但是假设,如果数据库刚刚完成操作1,好巧不巧,这个时候停电了,过了两分钟,又来电了,当我们再次查看数据库时,发现A用户余额为7000,比停电之前少了1000,但是发现B用户的账户余额仍然为5000,与停电之前一样,出现这种情况是因为数据库只完成了操作1,而没来得及完成操作2,那么,1000块大洋凭空消失了,所以,我们应该防止这样的悲剧发生,没错,解决方法就是使用事务。

  我们之前说过,事务中的所有sql语句都被当做一个整体,要么全部执行成功,要么在其中某些操作执行失败后回滚,回滚到最初的状态,就好像什么都没有发生过一样。那么利用事务的这个特性,就可以解决之前的问题,我们可以把转账的sql语句写入到事务中。

  利用事务完成上述操作,即使数据库刚刚将A用户账户余额减去1000时停电了,由于事务的特性,当再次使用数据库时,也不会出现A用户余额变为7000,B用户余额仍然为5000的情况,为什么呢?事务是怎样实现这样的功能的呢?你一定想到了这些问题,答案就是,事务是通过”事务日志”来实现这种功能的。

2、redo log概述

“事务日志”可以细分为 redo log 和 undo log,我们一个一个聊。

  先说redo log,mysql会将事务中的sql语句涉及到的所有数据操作先记录到redo log中,然后再将操作从redo log中同步到对应数据文件中(此处假设事物操作的数据量并非巨大)。

  换句话说,在事务执行提交成功以前,在修改对应的数据文件中的记录之前,一定要保证对应的所有修改操作已经记录到了redo log中。假设事务中的sql语句涉及到60条记录的修改,那么在修改这60条记录之前,要将这60条修改操作记录到redo log中,当这60条操作都记录到redo log中以后,再从redo log中一条一条同步到数据文件的对应记录中。

  所以,即使数据文件中的数据被修改到一半时被打断(比如停电),那么也能依靠redo log中的日志将剩余的部分操作再次同步到对应的数据文件中。

使用redo log,能够实现ACID中的A,也就是原子性,即事务中的所有sql被当做一个执行单元。

redo log其实由两部分组成:redo log buffer(重做日志缓冲) 和 redo log file(重做日志文件)

redo log buffer存在于内存之中,是易失的,redo log file是持久的,存在于磁盘上。

avatar

  重做日志先被写入到redo log buffer中,虽然内存的速度极快,但是无法满足持久性的需求,因为内存中的数据是易失的,所以为了满足持久性,需要将redo log buffer中的日志写入到redo log file中,相当于从内存中同步到磁盘上,所以磁盘的性能会影响事务的性能,由于redo log file是磁盘上一段连续的空间,所以写速度还是比较快的,比离散的写操作要快很多,当操作记录被记录到redo log file中以后,再从redo log file中将操作同步到数据文件中。

  虽然,我们应该实时将redo log buffer中的数据写入到redo log file中以保证数据的安全性,但是这样会极大的降低性能,我们可以通过设置innodb_flush_log_at_trx_commit参数来修改从redo log buffer写入redo log file的策略,但是如果这样做,则会丧失持久性,有可能会丢失部分数据,具体使用怎样的刷写策略,还需要根据实际情况自己权衡。

  查看innodb_flush_log_at_trx_commit参数如下:

  redo log是物理日志,之所以说它是物理日志,是因为redo log 中记录的是数据库对页的操作,而不是逻辑上的增删改查,重做日志具有幂等性。

3、undo log概述

  刚才我们大致的描述了什么是redo log ,现在来聊聊什么是undo log,我们可以把undo log理解成数据被修改前的备份。如果说事务进行了一半,有一条sql没有执行成功,那么数据库可以根据undo log进行撤销,将所有修改过的数据从逻辑上恢复到修改之前的样子。

  注意:是逻辑上还原成原来的样子,比如,之前insert了1000条数据 ,那么就delete它们,如果delete了2000条,就insert它们,如果update了500条数据,就再次根据undo log去update它们。所以,undo log是逻辑日志,与redo log记录的页操作物理日志不同。

4、log group概述

  log group为重做日志组,一个重做日志组(log group)中有多个重做日志文件(redo log file),当日志组中的第一个log file被写满,则会开始将redo log写入日志组中的下一个重做日志文件中,以此类推,当日志组中的所有redo log file都被写满,则将redo log再写入第一个redo log file 中,覆盖原来的redo log,以便新的redo log 被写入。

 如果重做日志所在的设备崩溃了,那么redo log将有可能丢失,这样就无法保证redo log在任何时候都是可用的,所以,log group还支持日志组镜像,为了保险起见,我们应该将 log group放在有冗余能力的设备上,比如 raid1。

5、总结

  redo log存储于重做日志文件中,undo log则不同,undo存放在数据库内部特定的特殊段中,这个段被称为undo段(undo segment),undo段位于共享表空间中。

  mysql中,innodb存储引擎是支持事务的,myisam存储引擎是不支持事务的,不管是redo log或者undo log,都是innodb的产物,或者说是innodb存储引擎层面的产物,而在mysql中,还有另外一种重要的日志,二进制日志,也就是平常所说的binlog,它是建立mysql主从复制环境时所必须的日志,但是binlog并不是innodb存储引擎层面的产物,而是整个mysql数据库层面的产物,换句话说,binlog不止针对于innodb,mysql数据库中的任何存储引擎对于数据库的更改都会产生二进制日志(binlog)。

  innodb的redo log记录的是物理格式的日志,记录了对页的操作,而binlog记录的是逻辑日志,记录的是对应的SQL。

  redo log与binlog写入磁盘的时机也不同,innodb的redo log在事务进行时会不断的写入redo log file,binlog只在事务提交完成后进行一次磁盘写入。

  其实,不管是redo log 还是 undo log,都可以理解成恢复数据库的手段。

事务控制语句

1、事务日志参数

当使用innodb存储引擎时,我们可以通过如下语句,查看与日志相关的配置参数。

show global variables like ‘%innodb%log%’;

  innodb_log_file_size:表示每个redo log file的大小,单位为字节,上图中的设置表示每个重做日志文件的大小为5M

  innodb_log_files_in_group:表示每个重做日志组中有几个redo log file

  innodb_log_group_home_dir:表示重做日志组文件所在路径,此处的相对路径表示数据所在目录,默认情况下为/var/lib/mysql,此目录中的ib_logfile0与ib_logfile1即为日志组中的两个重做日志,可以看到,这两个日志文件的大小为5M,也对应了innodb_log_file_size的值

  innodb_mirrored_log_groups:表示一共有几组日志组,上图中的1表示一共只有一组重做日志,即只有1组镜像日志组,就是当前日志组本身。如果此值为1,表示没有冗余的日志组,如果想要有冗余的镜像日志组,此值至少要设置为2。注意,如果重做日志所在的硬件设备并没有冗余能力,同时用户对数据安全性要求较高,那么往往需要将此值设置为大于等于2的值。

  innodb_flush_log_at_trx_commit:表示当事务提交以后,是否立即将redo log从内存(log buffer)刷新到redo log file中。具体参数解析如下:

(1)如果此值设置为1(默认值),表示事务提交时必须将redo log从log buffer中刷写到redo log file(磁盘)中,过程为:事务提交–log buffer–os buffer–log file,此值为1时完全满足ACID的要求。

(2)如果此值设置为0,事务提交时并不会将redo log从log buffer刷写到redo log file,但是会在每秒钟自动刷新一次,也就是说每一秒钟都自动将内存中的redo log刷写到redo log file(磁盘)中。

  注意:当事务提交时,redo log存在于log buffer中,每秒钟 redo log从log buffer中经过os buffer,刷新到log file中一次,当此值设置为0时,如果mysql数据库崩溃,最多会丢失1秒钟的redo log。

(3)如果此值设置为2,表示在事务提交时,只会将redo log写入到文件系统内存(os buffer)中,但是不会立即写入到redo log file(磁盘)中,而是每秒钟从文件系统缓存中将数据刷写至redo log file(磁盘)中一次。

  注意:当事务提交时,redo log存在于log buffer和os buffer中,每秒钟,log从os buffer中刷写到log file中一次。此值为2时,如果只是mysql数据库宕机,但是操作系统没有宕机,则数据不会丢失;如果此时操作系统宕机,重启数据库后,则会丢失未从文件系统内存刷写到redo log file中的那部分事务(约1秒钟的数据),因为只有mysql宕机而操作系统没有宕机时,并不会丢失数据,所以可靠性比此值设置为0时要高一些。

  理论上来说,此值设置为1,安全性最高,性能最低,设置为0,性能最高,安全性最低,设置为2,性能较高,安全性较低,此值设置为1,能够满足ACID的特性,设置为0或2,将会失去ACID的特性。但是需要注意,很多操作系统或者硬盘设备会欺骗mysqld进程,让mysqld进程认为刷写操作已经完成,但是实际上并没有,在这种情况下,即使innodb_flush_log_at_trx_commit的值设置为1,也不能保证事务的可用性,具体预防方法可以查看官方文档,地址如下:

https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit

  上述关于innodb_flush_log_at_trx_commit参数的总结参考了 “官网文档” 与 “MySQL技术内幕:InnoDB存储引擎”

  如果我们想要根据自己的需要,设置重做日志的相关参数,只要修改上述变量即可。或者将上述设置写入配置文件中。

2、事务控制语句

  在mysql中,默认情况下,我们每执行一条sql语句,mysql都会把这条sql当做一个单语句事务进行提交,而且默认是自动提交的,我们可以使用如下语句查看mysql是否开启了自动提交功能,如下两条语句分别查看全局与当前会话是否开启了自动提交功能。

show global variables like ‘autocommit%’;
show session variables like ‘autocommit%’;

  如上图所示,默认情况下,autocommit是开启的,表示事务都是自动提交到,执行sql语句以后也会立即自动执行commit操作。

  如果想要手动的控制提交操作,则需要显示的开启一个事务,或者禁用自动提交功能(set autocommit=0),进行手动提交,上述两种方法都行,我们一个一个聊。

首先,我们来了解一些事务的控制语句,了解了这些语句,就自然显示的控制事务了:

(1)start transaction 或者 begin :表示显示的开始一个事务,虽然begin和start transaction都表示显式的开启一个事务,但是在存储过程中,mysql会将begin识别为begin···end,所以,在存储过程中,只能使用start transaction来表示开始一个事务。

(2)commit 或者 commit work :表示提交事务,也就是说从begin到commit之间的所有sql语句对数据库所作出的修改将会被真正的执行,成为永久性的操作。

(3)rollback 或者 rollback work :表示回滚事务,回滚事务会撤销所有未提交的修改并结束当前事务。注意,使用rollback回滚事务以后,当前事务会结束,后面的操作不算在当前事务以内。

(4)savepoint 标识符 :表示创建一个事务的保存点,以便我们回滚到当前保存点,而不是回滚整个事务。就好比我们的游戏存档一样,如果你在当前位置设置了保存点,那么当你game over的时候,可以从这个保存点继续,而不是从游戏的开始处继续,一个事务中可以创建多个保存点。

(5)rollback to savepoint 标识符 :表示根据标识符回滚到指定的保存点,使用rollback to savepoint只会撤销对应保存点之后的操作,而且并不会结束当前事务,回滚到指定的保存点以后的操作仍然属于当前事务,与rollback不同。

(6)release savepoint 标识符 :表示删除一个保存点。

此处使用t1表做示例,先从最简单的事务开始,示例如下:

  使用begin时事务开始,开始事务以后,我们插入了两条数据,然后执行了commit操作,提交事务以后,事务完成,所作出的修改被持久化。

  上述示例就是最简单的事务开始,提交事务,事务中的sql语句被当做一个整体,要么全部执行成功,要么执行某一条语句失败,进行回滚。

那么,我们来看看回滚操作的示例,如下:

  事务开始之前,t1表中有5条数据,事务开始以后,我们删除了t1id为4和5的两条记录,但是,我们并没有执行commit操作,所以这些删除操作并没有真正的持久化到数据库中,此时,我们执行了rollback操作,所以,所有未提交的操作都被撤销了,同时当前事务结束。

  上述示例中已经使用到了回滚,但是,并没有使用到保存点,那么我们给出一个带有保存点的示例如下:

上图中事务执行的过程如下:

(1)事务开始前有5条数据

(2)开始事务

(3)删除了id号为3记录,创建了保存点,del3

(4)插入了一条新的记录,创建了保存点,add6

(5)执行了回滚操作,指定回滚到保存点del3,回滚以后,id号为3的记录仍然存在,因为在del3保存点中,还未执行删除id号为3的记录的操作。

(6)执行commit,提交事务,所有修改以提交时的为准,并且持久化。

  通过事务控制语句,即可显示手动的对事务进行控制。我们也可以禁用autocommit功能,从而进行手动的提交操作,此处只将当前会话的autocommit功能关闭,示例如下:

set @@session.autocommit=0

  当前会话的自动提交功能关闭以后,不进行手动commit操作,对应sql也是不会被真正提交的。

  因为关闭了自动提交功能,所以,每个sql语句并不会自动被当做一个单语句事务,所以每个sql语句并不会被自动提交。如果想要将之前的修改持久化,需要手动执行commit操作。

事务隔离级别

1、事务隔离级别概述

  mysql中,innodb所提供的事务符合ACID的要求,而事务通过事务日志中的redo log和undo log满足了原子性、一致性、持久性,事务还会通过锁机制满足隔离性,在innodb存储引擎中,有不同的隔离级别,它们有着不同的隔离性。

  什么是事务的隔离级别?首先,打开两个终端,同时连接到当前数据库,如下图所示,我们对两个回话进行编号,并且以颜色区分,1号会话使用黄色进行标识,2号会话使用红色进行标识。

  使用show processlist语句,可以看到,已经有两个线程链接到当前数据库。

  两个会话使用相同的数据库,同时两个会话各自开启一个事务。

  下图中,我们先在会话1的事务1中执行了更新操作,然后在事务1中执行了查询操作,最后又在会话2中的事务2中执行了查询操作,按照操作顺序,为各个操作进行了顺序编号。

  从上图可以看到,在事务1中显示的数据已经发生了改变,第5条数据对应的字符串已经变为ee,事务2中显示的数据未发生改变。如果将会话1中的事务提交,事务2中的数据会发生什么变化。

  那么,在事务提交前后具体操作如下:

  然后我们执行上图中的第4步,将事务1中的修改操作进行提交,在事务2中再次查看t1表中的数据(第5步),经过查看发现,t1表中的第5条数据对应的字符串仍然没有发生改变。在会话2没有提交之前,从t1表中查询出的数据一直都是不变的,直到会话2提交以后,再次查询t1表的数据,才发现t1表的第5条数据对应的字符串已经发生了改变。

  出现这种现象,是因为mysql默认的隔离级别造成的,而不同的隔离级别会体现出不同的隔离效果,所以,事务的隔离级别,决定了各个事务之间的隔离性。但是具体有几个隔离级别?每个隔离级别下有哪些特性?

  此处,列出了innodb中事务的所有隔离级别,事务的隔离级别一共有如下4种:

READ-UNCOMMITTED : 此隔离级别表示 “读未提交”。

READ-COMMITTED : 此隔离级别表示为 “读已提交” 或者 “读提交”。

REPEATABLE-READ : 此隔离级别表示 “可重复读” 或者 “可重读”。

SERIALIZABLE : 此隔离级别则表示”串行化”。

  mysql默认设置的隔离级别为REPEATABLE-READ,即 “可重读”。使用如下语句可以查看当前设置的隔离级别

show variables like ‘tx_isolation’;

如下图所示,默认设置的隔离级别为可重读:

 如果需要修改my.cnf配置文件,则可通过如下参数配置mysql的事务隔离级别,注意,不是使用tx_isolation,而是使用transaction_isolation。

  如果想临时在当前会话中修改事务隔离级别,语句如下:

//设置read uncommitted级别:
set session transaction isolation level read uncommitted;
//设置read committed级别:
set session transaction isolation level read committed;
//设置repeatable read级别:
set session transaction isolation level repeatable read;
//设置serializable级别:
set session transaction isolation level serializable;

  如果想永久保存修改的隔离级别,需要在配置文件中修改,然后重启数据库就会生效,这里以修改事务隔离级别 REPEATABLE-READ 为例:

  transaction_isolation=REPEATABLE-READ

2、隔离级别:可重读

  我们先来总结一下可重读隔离级别的特性,仍然以刚才文章开头的示例为例,下图中,在会话1与会话2中同时开启两个事务,在事务1的事务中修改了t1表的数据以后(将第5条数据的t1str的值修改为‘ee’),事务2中查看到的数据仍然是事务1修改之前的数据,即使事务1提交了,在事务2没有提交之前,事务2中查看到的数据都是相同的。

  比如t1表中的第5条数据,不管事务1是否提交,在事务2没有提交之前,这条数据对于事务2来说一直都是没有发生改变的,这条数据在事务2中是可以重复的被读到,所以,这种隔离级别被称为”可重读”。

  但是,你可能会有个问题,之前说过,事务的隔离性是由锁来实现的,那么,当上图中的事务1中执行更新语句时,事务1中应该对数据增加了写锁,但是在事务2中,仍然可以进行查询操作,即进行读操作,可是写锁是排他锁,在事务1中已经添加了写锁的情况下,为什么事务2还可以读取呢?

  这是因为innodb采用了”一致性非锁定读”的机制提高了数据库并发性。一致性非锁定读表示在如果当前行为被施加了排他锁,那么当需要读取行数据时,则不会等待行上的锁的释放,而是会去读取一个快照数据,如下图所示:

avatar

  上图展示了innodb中一致性非锁定读的过程。之所以称其为非锁定读,是因为它不需要等待被访问的行上的排他锁的释放。而上图中的快照的实现是由事务日志所对应的undo段来完成,其实快照就是该行所对应的之前的版本的数据,即历史数据,一行记录可能有不止一个快照数据。并不是所有隔离级别都使用了一致性非锁定读,在”可重读”和”读提交”的隔离级别下,innodb存储引擎使用了一致性非锁定读,但是在这两个隔离级别中,对于快照数据的定义也不相同,在”可重读”隔离级别下,快照数据是指当前事务开始时数据的样子,所以,在刚才的示例中,事务2中t1表对应的第5条记录的t1str的值一直都是e,因为在事务2开始的时候,其值就是e,这也是其可重读的特性,但是在”读提交”的隔离级别下,由于对于快照的定义不同,所以显示的现象也不同,这在做”读提交”隔离级别的实验时我们自然就会明白。

在可重读的隔离级别下,可能会出现”幻读”的问题,那么什么是幻读,我们一起来看一下。

当前的隔离级别仍然是”可重读”,现在来看一个幻读的示例,根据序号顺序查看下图中的操作即可。

  从上图可以看出,从上图中的第5步开始,数据其实就已经发生了改变,到第7步时,事务2还是无法看到数据的改变,但是当事务2更新数据以后,发现莫名其妙的多出了一条数据。在同一个事务中,执行两次同样的sql,第二次的sql会返回之前不存在的行,或者之前出现的数据不见了,这种现象被称之为”幻读”。

  注意:上例中第8步执行的update语句并没有指定任何条件,相当于更新表中的所有行的对应字段,如果你指定了条件,并且没有更新到”隐藏”的行,那么可能无法看到幻读现象。

3、隔离级别:串行化

  所以,经过上述实验我们可以发现,事务处于REPEATABLE-READ :”可重复读” 级别时,会出现幻读的情况,而在之前,我们已经提到过,不同的隔离级别,所引入的问题会有所不同,隔离性也有所不同,那么,有没有一种隔离级别,能够解决幻读的问题呢?SERIALIZABLE : “串行化”隔离级别不会出现幻读的问题。

  首先,我们将两个会话中的事务的隔离级别都设置为SERIALIZABLE : “串行化”。

  然后分别在两个会话中开启了事务1与事务2,在事务1中插入了一条数据,此时,在事务2中查询表t1的数据,第4步好像被”卡”住了,多等一会儿,发现并未执行成功,并且报出锁请求超时的错误,如下图:

  解释:从报错信息可以发现,事务2中的锁请求超时了。因为事务的隔离性是由锁来实现的,在使用串行化的隔离级别时,由于事务1先对t1表施加了写锁,所以当事务2对t1表请求读锁时,会被阻塞,那么,出现请求锁超时的情况,也就算是比较正常了,所以,此时,我们是无法在事务2中读到t1表的数据的。

  如果在事务2请求的过程中提交事务1,会发生什么情况?

  趁着事务2中的查询语句被阻塞的时候,将事务1进行提交,如下图所示:

  当在事务2中执行查询语句时,查询被阻塞,此时将事务1提交,当事务1被提交后的一瞬间,事务2中就会执行查询语句。当事务1中的写锁释放时,事务2才读出了数据。

  从上述实验上来看,当事务处于串行化隔离级别时,是不可能出现幻读的情况的,因为如果另一个事务中对表添加了写锁,那么在当前事务中是无法读到数据的,必须等到另一个事务提交,并且释放了对表的写锁,当前事务才能进行申请读锁,使用串行化的隔离级别不会出现幻读的情况,但是,聪明如你一定发现了,当事务的隔离级别设置为串行化时,数据库失去了并发的能力,所以,我们很少将隔离级别设置为串行化,因为这种隔离性过于严格了。

4、隔离级别:读已提交

  在两个会话中同时开启两个事务,在事务1中修改t1表中的第二条数据,如下图中的1、2步所示,此时,事务1并未提交,所以如第3步所示,在事务2中并无法看到事务1中的修改,而当事务1提交以后,事务2中即可看到事务1中的修改,换句话说,就是事务2能够读到事务1提交后的更改,这种隔离级别被称为”读提交”。

“不可重读”问题:

  在读已提交的隔离级别下,除了会出现幻读的情况,还会出现不可重读的情况。”不可重读”表示”不一定可重读”,不要理解为”一定不可重读”。什么叫”不一定可重读”呢?

  上图中的第3步中,获取到的第4条记录对应的t1str字段值为”d”:,而在同一个事务中,第5步所查出第二条记录对应的t1str字段却变成了”test”,所以,当我们想要再次重复读到刚才的”d”,就变成了”不可重读”。

“幻读”问题:

  在”读提交”的隔离级别下,也会出现”幻读”的问题,示例如下:

  在上述示例中,事务1向t1表中插入了一行数据,在事务1提交以后,事务2中即可看到,但是事务2还没有提交,在事务2中执行两次相同的查询语句,莫名其妙的多出了一行,出现了”幻读”的情况。

  其实,”不可重读”与”幻读”的现象都非常相似,都是在同一个事务中,并没有操作某些数据,可是这些数据却莫名的被改变了,或者突然多出了某些数据,又或者突然少了某些数据,这些状况好像都能用”幻象”这个词去理解,所以我一开始总是分不清到底什么是幻读,而且,mysql官方文档中也把不可重读归为幻读,只是大家为了更加的细化它们的区别,把他们分成了”不可重读”与”幻读”,如果我们实在无法分清他们,我们可以这样理解,”幻读”的重点在于莫名其妙的增加了或减少了某些数据,”不可重读”的重点在于莫名的情况下,数据被修改更新了。

  总结:在”读提交”隔离级别下,会出现”不可重读”,”幻读”的问题,比”可重读”隔离级别的问题更多,但是它的并发能力比”可重读”更强,我们似乎发现了一个规律,隔离级别的隔离性越低,并发能力就越强,存在的问题就越多。

5、隔离级别:读未提交

  将两个会话的事务隔离级别同时设置为读未提交,然后在两个会话中各自开启一个事务,然后在事务1中插入一条数据,并且删除一条数据,如下图中的第3步与第5步所示:

  第3、5步执行完毕以后,事务1并未提交,此时执行步骤4和6,在事务2中查看t1表中的数据,是可以看到事务1中所做出的修改。可以发现,在读未提交这个隔离级别下,即使别的事务所做的修改并未提交,我们也能看到其修改的数据。

  总结:当前事务能够看到别的事务中未提交的数据,我们称这种现象为”脏读”。上例中,事务1并未提交,但是其所作出的修改已经能在事务2中查看到,由于事务1中的修改有可能被回滚,或者数据有可能继续被修改,所以事务2中看到数据是飘忽不定的,并不是最终的数据,并不是提交后的数据,是”脏”的,但是事务2中仍然能看到这些数据,所以,这种现象被称之为脏读。当事务的隔离级别处于”读未提交”时,其并发性能是最强的,但是其隔离性与安全性是最差的。

  当事务处于”读未提交”这种隔离级别时,会出现”脏读”的情况,同时,也会出现”不可重读”,”幻读”的问题。

总结

脏读、幻读、不可重读的区别:

脏读:当前事务可以查看到别的事务未提交的数据(侧重点在于别的事务未提交)。

幻读:幻读的表象与不可重读的表象都让人”懵逼”,很容易搞混,但是如果非要细分的话,幻读的侧重点在于新增和删除。表示在同一事务中,使用相同的查询语句,第二次查询时,莫名的多出了一些之前不存在数据,或者莫名的消失了一些数据。

不可重读:不可重读的侧重点在于更新修改数据。表示在同一事务中,查询相同的数据范围时,同一个数据资源莫名的改变了。

  关于各个事务隔离级别的表象与问题,我们来总结一下:

  首先,我们明白了一个道理,事务的隔离级别越高,隔离性越强,所拥有的问题越少,并发能力越弱,所以,我们可以用如下表格进行总结:

avatar

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>