MySQL中事务和事务的隔离级别

本文主要是帮助理解相关知识,没有具体的操作和代码。

事务

事务就是一组操作,这组操作要么全部成功,要么全部失败。

最经典的例子就是银行转账:

张三给李四转账100,对用户来说,就是一个操作。但对应到数据库中,至少需要三步:

//检查张三账户余额是否大于等于100
//张三账户-100
//李四账户+100

这三个操作可以没有顺序,但是必须全部成功或者全部失败。否则就可能导致张三损失100李四没收到,或者李四收到100但是张三没扣款成功(银行损失100)。

MySQL默认引擎InnoDB,支持事务;MyISAM引擎不支持事务。

事务的特性

前面说的其实都是事务的概念,具体怎么实现或者有什么要求呢?

一个运行良好的事务处理系统,必须基本满足四个特性(ACID),即原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

原子性(atomicity)

一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。(这个特性基本上等同于事务的定义,所以必须满足)。

一致性(consistency)

数据库总是从一个一致性的状态转换到另外一个一致性的状态。在转账的例子中,若转账成功,张三和李四的账户总额和转账前也是一致。

隔离性(isolation)

一般来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。(这是本文的讨论的主要内容,因为在实际情况下,并不一定能保证隔离性。)

持久性(durability)

一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。

事务的隔离级别

事务间的隔离

事务的四个特性中,原子性,一致性,持久性多数情况下都能保证。稍微复杂的是隔离性,因为我们在学习的过程中很多时候都是单用户,单连接,也就是一个事务执行完成再去执行另一个,很难体会到隔离性的内涵。

不妨想一下春运时候的12306系统,每个用户要完成的操作:

//假设某一线路总票数100张
//1.查询余票
//2.买票即总票数-1
//3.付款

这三个操作组成一个事务,在高并发的情况下,一个用户甲执行到第二步,票数减到99,此时第二个用户乙查询余票,票数应该是100还是99 ?

针对类似的疑问,SQL规范提出了四种情形,或者说用四种级别实现了不同的需求,不同级别决定了用户乙看到的是100还是99。

这四种级别分别是:

READ UNCOMMITTED(未提交读)

在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。
回到前面的例子,如果设置为这个级别,用户乙看到的将是99。这导致的问题是如果甲付款或者其他原因失败,乙读到其实是假数据,称为脏读。

脏读(Dirty Read)

事务读取到未提交的数据。

READ COMMITTED(提交读)

在READ COMMITTED级别,一个事务开始时,只能“看见”已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。

这是多数数据库系统的默认隔离级别(但MySQL不是)。

在前面的例子中,设置这个级别,乙看到的将是100。同样可能导致的问题是,乙在一次事务中,查询成功后,甲付款成功,票数变为99,乙再次查询,结果由100变成99。一次事务中两次查询结果不一致,也叫不可重复读。

不可重复读(nonrepeatable read)

两次执行同样的查询,得到了不一样的结果。

REPEATABLE READ(可重复读)

可重复读是MySQL的默认事务隔离级别。

REPEATABLE READ解决了脏读的问题。保证了在同一个事务中多次读取同样记录的结果是一致的(意思是对同样的数据修改不影响)。
但是可重复读隔离级别无法解决幻读的问题。

幻读(Phantom Read)

所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。

MySQL关于幻读的处理

InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。
InnoDB的MVCC是通过每一行加两个隐藏列来实现,一列记录行的创建时间,一列记录行的过期时间(或删除时间),实际记录的是版本号而非具体时间。 相当于行级锁,但是比加锁开销更低,也体现了一种用空间换时间的思想。详细内容参考文档或者相关书籍。
关于这个级别到底是否完全解决幻读问题,MySQL(8.0)文档是这样说的:

This is the default isolation level for InnoDB. Consistent reads within the same transaction read the snapshot established by the first read. This means that if you issue several plain (nonlocking) SELECT statements within the same transaction, these SELECT statements are consistent also with respect to each other.

For locking reads (SELECT with FOR UPDATE or FOR SHARE), UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition.

For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it.

For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key locks to block insertions by other sessions into the gaps covered by the range.

如果在同一事务中发出多个普通(非锁定)SELECT语句,则这些SELECT语句彼此之间也是一致的。

对于锁定读取(使用FOR UPDATE或FOR SHARE进行SELECT),UPDATE和DELETE语句,锁定取决于该语句是使用具有唯一搜索条件的唯一索引还是范围类型搜索条件。

对于具有唯一搜索条件的唯一索引,InnoDB仅锁定找到的索引记录,而不锁定其前的间隙。

对于其他搜索条件,InnoDB使用间隔锁定或下一键锁定来锁定扫描的索引范围,以阻止其他会话插入该范围覆盖的间隔。

总结:如果是普通查询,能够避免幻读。对于锁定查询,只能在部分情况下(范围查询时)避免幻读。

SERIALIZABLE(可串行化)

SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。

总结:

因为实际业务需要,产生了事务,又因为事务本身的特性,需要考虑不同事务之间的可见性,产生了隔离级别。

四种隔离级别:

READ UNCOMMITTED=》READ COMMITTED=》REPEATABLE READ=》SERIALIZABLE

越低的级别能够执行更高的并发,系统开销也更低。

本文主要参考了《高性能mysql(第3版)》,以及MySQL8.0官方文档。

posted @ 2020-09-02 09:54  希夷小道  阅读(272)  评论(0编辑  收藏  举报