MySQL事务(transaction)以及事务隔离级别(isolation level)

版权声明:原创文章,转载请注明出处! https://blog.csdn.net/L_15156024189/article/details/87006324

本文使用的MySQL版本:Server version: 5.7.25 MySQL Community Server (GPL)。

事务

       事务是可以提交或者回滚的原子工作单元,可以理解为由一组SQL语句组成的逻辑处理单元。开始事务语句使用begin或者start transaction,结束事务使用commit和rollback。


ACID原则

        ACID分别是atomicity,consistency,isolation和durability的缩写,表示原子性、一致性、隔离性和持久性。它是数据库系统需要的四个基本原则。ACID和数据库中事务的概念息息相关,MySQL的事务特性遵守ACID原则。

原子性(atomicity)

        一个事务对数据库做的多个更改,要么在事务被提交时所有更改都成功,要么在事务被回滚时所有更改都撤销。例如A账户余额为300,B账户余额为200,现从A账户转出100到B账户,那么这个转账就看成一个事务,由两个DML语句构成,一个是将A账户存款更改为200,另一个是将B账户更改为300,两个账户的更改要么都成功,要么都失败,也就是说,不会出现A账户已经被更改为200,而B账户没有被更改。这就是事务的原子性。

一致性(consistency)

        在每次提交或回滚之后,和事务运行过程中,数据库始终保持一致状态。例如:跨多个表更新相关数据时,查询到的数据要么是新值,要么是旧值,不会看到新值和旧值混合情况。

隔离性(isolation)

        事务在运行过程中相互保护或者隔离。它们不会相互干扰或者看到对方未提交的数据(这个由隔离别决定)。事务之间的隔离性是通过锁机制实现的。有经验的用户可以调整隔离级别,在可以确保事务之间不会相互干扰的情况下,减少事务之间的保护来提高数据库性能和并发性。

持久性(durability)

        事务的结果是持久的:一旦提交操作成功,事务所做的更改就不会受到电源故障、系统崩溃、竞态条件或许多非数据库应用程序容易受到的其他潜在危险的影响。持久性通常包括写入磁盘存储,并具有一定的冗余,以防止写入操作期间出现电源故障或软件崩溃。(在InnoDB中,双写缓冲区有助于持久性。)


读现象(read phenomena)

        当一个事务读取另一个事务修改的数据时,可能会出现脏读(dirty reads),不可重复读(non-repeatable reads)和幻读(phantom reads )现象。因为读现象是和事务的隔离级别息息相关的,而InnoDB中事务的隔离级别有四个,由高到低分别是:串行读(SERIALIZABLE)、可重复读(REPEATABLE-READ)、读提交(READ-COMMITTED)和读未提交(READ-UNCOMMITTED)。下面先分别解释读现象:

脏读(dirty reads)

        脏读是一种读取不可靠数据的现象,是指当前事务读取了另一个事务修改的数据,但是另一个事务对这些数据做了回滚,或者在提交前再次更新,而这些数据可能被当前事务用于后续的处理中。它违背了数据库的ACID原则,是非常风险的。

        只有在读未提交(READ-UNCOMMITTED)的隔离级别下才有可能出现脏读。

        在这里我们需要提一下,与脏读相反的是一致读(consistent read),在一致读中,MySQL的InnoDB引擎保证一个事务不会读取到被另一个事务更新的数据,即使另一个事务在此期间提交了数据。它使用快照信息根据时间点显示查询结果,而不考虑同时运行的其他事务执行的更改。如果查询的数据已被另一个事务更改,则可以根据撤消日志(undo log)的内容重建原始数据。这种技术通过强制事务等待其他事务完成来避免一些锁(locking)问题,锁会降低并发性。对于可重复读(REPEATABLE READ)隔离级别,快照基于第一次读取操作执行的时间;在读提交(READ-COMMITTED)隔离级别下,将快照重置为每次一致读操作的时间。总而言之,一致读是通过基于时间点的快照实现的,在可重复读(REPEATABLE-READ)隔离级别中,快照的时间点就是第一次读取操作执行的时间,这样每次从该快照中读取数据就能保证一致性。在读提交(READ-COMMITTED)隔离级别下,时间点是一致读操作的时间。

        一致读在读提交(READ-COMMITTED)和可重复读(REPEATABLE-READ)隔离级别下工作。

不可重复读(non-repeatable reads)

       在一个事务中,前后相同的两次查询,结果应当是一样的,但是查询返回的结果确不同。也就是说,同一份数据在同一个事务中不能被重复读。出现这种情况的可能原因是,当一个事务执行某个查询后,数据被另一个事务更新,当前事务再次执行该查询会得到更新后的数据。它违背了数据库设计的ACID原则。

        不可重复读现象可以通过设置串行读(SERIALIZABLE)和重复读(REPEATABLE-READ)隔离级别来防止,但可能会出现在读未提交(READ-UNCOMMITTED)隔离级别中。官方提到不可重复读现象可能会出现在一致读(consistent read)中(注意:一致读不是MySQL的隔离级别)。

幻读(phantom reads )

        某一行出现在了一次查询的结果集中,但是没有出现在之前该查询的结果集中。例如,在一个事务中,一个查询先后执行两次,在两次查询之间,另一个事务插入了一行并提交 或者更新了某一行以满足该查询where子句。这种现象就是一种幻读(这只是一种,幻读还有很多情况)。它比不可重复读现象更难防范。因为锁定第一次查询的结果集中所有的行,照样可能会出现幻读。

        幻读可以通过设置串行隔离级别来防止,但是可能会出现在重复读(REPEATABLE-READ)、一致读和读未提交(READ-UNCOMMITTED)隔离级别下。

不可重复读和幻读的区别:

        不可重复读和幻读容易混淆,不可重复读侧重于更新操作,幻读侧重于行的删除或者增加操作。行锁可以解决不可重复读现象,但是解决不了幻读现象,因为锁住了一些行,无法再对这些行做修改,所以每次读取结果是一样的,也就是可重复读取,但是当增加了满足查询的where子句的行,两次查询会出现不同的结果,这是幻读。

对于事务的各种隔离级别,可能出现的读现象如下表:

事务隔离级别 脏读 不可重复读 幻读
读未提交(READ-UNCOMMITTED)
读提交(READ-COMMITTED) ×
重复读(REPEATABLE-READ) × ×
串行化(SERIALIZABLE) × × ×

简单做个总结:如果采用串行化隔离级别,不用担心出现任何读现象,但是这是以降低性能和并发性为代价的。


隔离级别(isolation level)

串行化(SERIALIZABLE)

        串行化隔离级别是最高的隔离级别,它使用了最保守的锁策略。它阻止任何其他事务插入或更改此事务读取的数据,直到该事务完成。简单的来说,就是一个事务一个事务的来执行,显然性能会很低。在这种隔离级别下,一个事务中的相同查询可以反复执行,每次查询结果是一样的。从当前事务开始执行,任何更改另一个事务提交的数据的尝试都会导致当前事务等待(阻塞)。这是SQL标准指定的默认隔离级别(注意不是MySQL)。在实践中,这种严格程度是很少需要的。

重复读(REPEATABLE-READ)

        这是MySQL的InnoDB引擎默认的隔离级别,它阻止查询的任何行被其他事务更改。因此,阻塞不可重复读,而不是幻读。也就是说在可重复读中,可能会出现幻读。重复读使用一种中等严格的锁定策略,以便事务中的所有查询都能看到来自相同快照(即事务启动时的数据)的数据。

        当拥有该级别的事务执行 UPDATE ... WHEREDELETE ... WHERESELECT ... FOR UPDATE和LOCK IN SHARE MODE操作时,其他事务可能需要等待。

读提交(READ-COMMITTED)

        事务无法看到来自其他事务的未提交数据,但可以看到当前事务启动后另一个事务提交的数据。当拥有这种级别的事务执行 UPDATE ... WHERE or DELETE ... WHERE操作时,其他事务可能需要等待。但是该事务可以执行 SELECT ... FOR UPDATE, and LOCK IN SHARE MODE操作,其他事务不需要等待。

读未提交(READ-UNCOMMITTED)

        它是最低的隔离级别。它会读取到其他事务修改尚未提交的数据,使用此隔离级别就需要非常小心,认识到这种级别下的查询结果可能不一致或不可复制,这取决于其他事务同时在做什么。通常,具有此隔离级别的事务只执行查询,而不执行插入、更新或删除操作。

        在实际环境中,应当根据是否允许出现脏读(dirty reads),不可重复读(non-repeatable reads)和幻读(phantom reads )现象而选择相应的隔离级别。例如在大数据中,少量的数据不一致不会影响到最后的决策,这种情况下可以使用较低的隔离级别以提交性能和并发性。


实践

查询隔离级别

select @@global.tx_isolation,@@tx_isolation;

查询语句中,前者global.tx_isolation是全局事务隔离级别,后者tx_isolation是当前会话事务级别,查询结果如图:

无论是全局还是当前会话,默认的隔离级别都是REPEATABLE-READ。

修改隔离级别

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}

执行如下语句修改隔离级别为READ-COMMITTED:

set global transaction isolation level read committed; 
set session transaction isolation level read committed;

再次查询隔离级别,看是否修改成功如图:

已经修改成功。下面重新打开一个会话,再次查看事务隔离级别,看看全局事务隔离级别与当前会话隔离级别的区别,如图:

发现全局事务隔离级别已经被修改,而当前事务隔离级别仍然是默认值,也就是说全局事务隔离级别作用所有的会话,而当前会话事务级别只对当前会话有效。

四种隔离级别例子

(1)读未提交

如图:

图中是两个会话,左边是会话A,右边是会话B,橙黄色数字表示命令执行的顺序。在读未提交隔离级别下,会话A中的事务可以看到会话B的事务中更改但还未提交的数据,当会话B对修改的数据进行回滚或者再次更改时,那么会话A已经读取的数据就变成了脏数据,这种现象就是前面说的脏读。会话A的同一个事务的同一个查询语句的两次执行结果确不一样,这就是不可重复读现象。如果在会话B中增加或者删除一条记录,会话A中立即能看到这条记录,这就是其中一种幻读。

(2)读提交

如图:

将会话A和会话B的事务隔离级别设置为读提交后,如果会话B对数据进行了修改但未提交,会话A是看不到修改后的数据的,也就不会再出现脏读现象,但是当会话B提交修改后,会话A中的事务就能看到修改后的数据。会话A中第一次和第三次查询结果不一致就是不可重复读现象。当会话B删除某一行并提交,会话A看到少了一行(但是会话A并未做删除操作,会话A觉得这是一种幻觉)。也就是说,读提交隔离级别解决了脏读现象,但还是会出现不可重复读和幻读现象。

(3)重复读

如图:

从图中可以看到,无论会话B中的事务是否提交了插入数据的操作,会话A中的事务查询的结果都是一样的,也就是数据是可重复读取的,不会再出现脏读和不可重复读的现象了。在会话A的事务中插入一条数据,如图:

从图中可以看到插入的数据的id是6(id是主键,自动增长),见鬼了(幻影)?对于会话A来说,它不知道有其他事务再操作,它觉得这条记录的id应该是5而不是6,这就是一种幻读。也就是说在可重复读隔离级别下,还是会出现幻读现象。

(4)串行化

如图:

猜你喜欢

转载自blog.csdn.net/L_15156024189/article/details/87006324