Mysql事务的隔离级别
一.事务的四大特性
1.原子性(Atomicity)
1.1什么是原子性
一般来说,原子是指不能分解成小部分的东西。这个词在计算的不同分支中意味着相似但又微妙不同的东西。例如,在多线程编程中,如果一个线程执行一个原子操作,这意味着另一个线程无法看到该操作的一半结果。系统只能处于操作之前或操作之后的状态,而不是介于两者之间的状态。 ACID原子性的定义特征是:能够在错误时中止事务,丢弃该事务进行的所有写入变更的能力。
1.2 如何实现原子性
WAL(预写日志) 是用于保证事务的原子性和持久性。简单来讲,事务更新数据之前,先写日志,然后在更新数据。当系统崩溃时,如果事务还没写WAL,那整个数据依然一致。如果事务只写了WAL,未更新具体的数据页后崩溃,那恢复流程可以根据WAL日志,重做相关操作,保证数据一致性。
2.一致性(Consistency)
ACID一致性的概念是,对数据的一组特定陈述必须始终成立。即不变量(invariants)。例如,在会计系统中,所有账户整体上必须借贷相抵。如果一个事务开始于一个满足这些不变量的有效数据库,且在事务处理期间的任何写入操作都保持这种有效性,那么可以确定,不变量总是满足的。 原子性,隔离性和持久性是数据库的属性,而一致性(在ACID意义上)是应用程序的属性。应用可能依赖数据库的原子性和隔离属性来实现一致性,但这并不仅取决于数据库。
3.隔离性(Isolation)
3.1什么是隔离性
大多数数据库都会同时被多个客户端访问。如果它们各自读写数据库的不同部分,这是没有问题的,但是如果它们访问相同的数据库记录,则可能会遇到并发问题(竞争条件(race conditions))。 ACID意义上的隔离性意味着,同时执行的事务是相互隔离的:它们不能相互冒犯。
如果两个事务不触及相同的数据,它们可以安全地并行(parallel) 运行,因为两者都不依赖于另一个。当一个事务读取由另一个事务同时修改的数据时,或者当两个事务试图同时修改相同的数据时,并发问题(竞争条件)才会出现。出于这个原因,数据库一直试图通过提供事务隔离(transaction isolation) 来隐藏应用程序开发者的并发问题。
serializable级别的隔离,保证事务的效果与连续运行(即一次一个,没有任何并发)是一样的,可以保证事务地安全执行。但是在serializable隔离级别,事务并发度很低,整个数据库的性能肯定不高。这时候,数据库开发人员有提出了四种不同的隔离级别,来平衡事务并发度与隔离性,这四个隔离级别分别是:
- 读未提交(Read Uncommitted):可以读取未提交的记录。
- 读已提交(Read Committed):事务中只能看到已提交的修改。
- 可重复读(Repeatable Read):解决了不可重复读问题(MySQL 默认隔离级别)
- 序列化(Serializable):最高隔离级别。
RU,RC和RR由于降低了隔离要求,自然在读取数据时,会产生各种异常(上帝为你打开一扇门的同时,肯定也为你关上一扇窗):
- RU会读取其他事务未提交的数据,这就产生了脏读,脏读取意味着另一个事务可能会只看到一部分更新,或者看到的数据已经被回滚了。
- RC级别的隔离,会产生不可重复读的问题。所谓不可重复读是指在一个事务内根据同一个条件对行记录进行多次查询,但是搜出来的结果却不一致。发生不可重复读的原因是在多次搜索期间查询条件覆盖的数据被其他事务修改了。
- RR级别的隔离,会产生幻读问题。幻读,并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。
3.2 如何支持隔离性
一般数据库不会考虑工作在RU隔离级别,因为读脏数据会引起太多的问题。数据库一般工作在RC或RR隔离级别,快照隔离级别就可以支持RC或RR隔离级别,所以数据库一般会实现快照隔离级别。
快照隔离的实现通常使用写锁来防止脏写,这意味着进行写入的事务会阻止另一个事务修改同一个对象。但是读取不需要任何锁定。从性能的角度来看,快照隔离的一个关键原则是:读不阻塞写,写不阻塞读。这允许数据库在处理一致性快照上的长时间查询时,可以正常地同时处理写入操作。且两者间没有任何锁定争用。
为了实现快照隔离,数据库必须保留一个对象的几个不同的提交版本,因为各种正在进行的事务可能需要看到数据库在不同的时间点的状态。因为它并排维护着多个版本的对象,所以这种技术被称为多版本并发控制(MVCC, multi-version concurrentcy control)。
最高的隔离级别:Serializable,一般是通过2PL来实现, 一阶段申请,一阶段释放。读写都要加锁。
4.持久性(Durability)
数据库系统的目的是,提供一个安全的地方存储数据,而不用担心丢失。持久性 是一个承诺,即一旦事务成功完成,即使发生硬件故障或数据库崩溃,写入的任何数据也不会丢失。
二.事务的隔离级别验证
1.准备工作
- 简单表:
-
Mysql版本 8.0:
2.脏读,幻读,不可重复读
打开两个客户端A,B,在A中改变隔离级别,在B中执行修改操作。
-
脏读
首先进入Mysql客户端,执行操作,查看mysql默认隔离级别:
select @@transaction_isolation;
设置等级读未提交:
set session transaction isolation level read uncommitted;//A端设置读未提交,B端默认可重复读
在A中查询admin表初始数据;
在B中修改admin表数据,但不提交;
之后再在A中查询,B的更改在A中可见;
在B中执行回滚操作,之后在A中查询,发现数据又变回原样;
结论:由于A设置读未提交,导致没有被提交的数据被查到,
在B端(B事务)回滚后又变回原数据,出现脏读。
-
不可重复读
前面测试中,等级为读未提交(Read uncommitted)导致幻读,我们可以使用读已提交(Read committed)解决这种情况;
将A端设置为读已提交;
set session transaction isolation level read committed;//A端设置读已提交,B端默认可重复读
在A中查询admin表初始数据;
在B中修改admin表数据,但不提交,在A端中查询数据没有变化;
update admin set admin_name='aa' where admin_id='1';
可以发现脏读的问题被解决了,但是出现了新的问题,幻读:当A多次查询时,B在此期间执行了修改操作并提交,导致查出的数据不相同;
结论:当隔离级别为读已提交(Read committed )可以解决脏读,但出现了不可重复读;
- 幻读
为了可重复读,把隔离级别设置为可重复读(REPEATABLE-READ ),可以解决不可重复读。
set session transaction isolation level REPEATABLE-READ ;
将A端设置为可重复读(MYsql默认级别)后,进行修改操作,可以发现B端无论是否提交事务,A端中查询的数据都相同。解决了脏读和不可重复读问题;
但这会导致幻读:当B事务向表中插入删除时,A端在B端插入前查询和插入后,会平白无故多数据或者少数据,就像幻觉一样,故称幻读;
为了解决幻读的问题,我们将A端设置为序列化级别;
set session transaction isolation level REPEATABLE-READ ;
启动事务A,启动事务B。在A端查询操作,B端插入(删除)操作,在B执行删除操作时,A进行查询操作会进入等待状态,只有在B提交后才能查询,确保了查到的数据是最新的,解决了幻读;
3.总结
事务的隔离级别,分别解决了以下问题:
![](https://i-blog.csdnimg.cn/blog_migrate/6940db7f52f78cf9753c458b1b703f0e.png)
在实际应用中,数据库中的数据是要被多个用户共同访问的,在多个用户同时操作相同的数据时,可能就会出现一些事务的并发问题,具体如下。
1)脏读
指一个事务读取到另一个事务未提交的数据。
2)不可重复读
指一个事务对同一行数据重复读取两次,但得到的结果不同。
3)虚读/幻读
指一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。
4)丢失更新
指两个事务同时更新一行数据,后提交(或撤销)的事务将之前事务提交的数据覆盖了。
丢失更新可分为两类,分别是第一类丢失更新和第二类丢失更新。
- 第一类丢失更新是指两个事务同时操作同一个数据时,当第一个事务撤销时,把已经提交的第二个事务的更新数据覆盖了,第二个事务就造成了数据丢失。
- 第二类丢失更新是指当两个事务同时操作同一个数据时,第一个事务将修改结果成功提交后,对第二个事务已经提交的修改结果进行了覆盖,对第二个事务造成了数据丢失。
为了避免上述事务并发问题的出现,在标准的 SQL 规范中定义了四种事务隔离级别,不同的隔离级别对事务的处理有所不同。这四种事务的隔离级别如下。
1)Read Uncommitted(读未提交)
一个事务在执行过程中,既可以访问其他事务未提交的新插入的数据,又可以访问未提交的修改数据。如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。此隔离级别可防止丢失更新。
2)Read Committed(读已提交)
一个事务在执行过程中,既可以访问其他事务成功提交的新插入的数据,又可以访问成功修改的数据。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。此隔离级别可有效防止脏读。
3)Repeatable Read(可重复读取)
一个事务在执行过程中,可以访问其他事务成功提交的新插入的数据,但不可以访问成功修改的数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。此隔离级别可有效防止不可重复读和脏读。
4)Serializable(可串行化)
提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。此隔离级别可有效防止脏读、不可重复读和幻读。但这个级别可能导致大量的超时现象和锁竞争,在实际应用中很少使用。
一般来说,事务的隔离级别越高,越能保证数据库的完整性和一致性,但相对来说,隔离级别越高,对并发性能的影响也越大。因此,通常将数据库的隔离级别设置为 Read Committed,即读已提交数据,它既能防止脏读,又能有较好的并发性能。虽然这种隔离级别会导致不可重复读、幻读和第二类丢失更新这些并发问题,但可通过在应用程序中采用悲观锁和乐观锁加以控制。
读和幻读。但这个级别可能导致大量的超时现象和锁竞争,在实际应用中很少使用。
一般来说,事务的隔离级别越高,越能保证数据库的完整性和一致性,但相对来说,隔离级别越高,对并发性能的影响也越大。因此,通常将数据库的隔离级别设置为 Read Committed,即读已提交数据,它既能防止脏读,又能有较好的并发性能。虽然这种隔离级别会导致不可重复读、幻读和第二类丢失更新这些并发问题,但可通过在应用程序中采用悲观锁和乐观锁加以控制。