数据库事务的四种隔离性及Oracle\MySQL默认隔离级别和原因分析

发布时间 2023-08-02 21:21:11作者: DAYTOY-105

1 事务

一个事务中的一系列的处理操作要么全部成功,要么一个都不做。在数据库操作中,一项事务(Transaction)是由一条或多条操作数据库的SQL语句组成的一个不可分割的工作单元。

事务的处理结果有两种:

1)当事务中的所有步骤全部成功执行时,事务提交,成功;

2)如果其中任何一个步骤失败,该事务都将发生回滚操作,撤销已执行的所有操作。

2 事务四大特性(ACID)

2.1 原子性(Atomic)

表示将事务中所进行的操作捆绑成一个不可分割的单元,即对事务所进行的数据修改等操作,要么全部执行,要么全不执行;如果失败,就回滚到事务开始前的状态。

2.2 一致性(Consistency)

事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。

2.3 隔离性(Isolation)

指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

2.4 持久性(Durability)

持久性也称永久性(permanence),持久性就是指如果事务一旦被提交,数据库中数据的改变就是永久性的,即使断电或者宕机的情况下,也不会丢失提交的事务操作。

3 隔离性详解

说明:隔离性是指,多个用户的并发事务访问同一个数据库时,一个用户的事务不应该被其他用户的事务干扰,多个并发事务之间要相互隔离。

事务隔离性的重要性:事务的隔离性主要是从提升数据库的数据处理速度,即并发角度考虑的;换句话说,事务隔离性和整体数据库的性能/并发执行,有直接决定性作用。

一个思维模式:想要理解一个知识点,就假设如果没有该点会有什么发生;事务隔离性也是,如果事务中没有隔离性这个概念,会发生点啥事?

在实际应用中,数据库中性能的好坏标准之一就是数据能被尽可能多的用户共同访问,当多个用户同时处理同一数据时,可能就会出现一些事务的并发问题,导致如下四种情况出现:

1)脏读

指一个事务读取到另一个事务未提交的数据。

2)不可重复读

指一个事务对同一行数据重复读取两次,但得到的结果不同。

举例:事务1读取表的一条数据期间,事务2更新了该条记录并提交,事务1再次读取该表该条记录时,发现和第一次内容不一致。

3)幻读

指一个事务执行两次查询,但第二次查询的结果包含了第一次查询中未出现的数据。

举例:事务1读取一个表期间,事务2对表做了delete/update/insert操作并提交,事务1再次读取表时,此时读取到事务2操作的记录,两次操作结果不一致。

这里,有朋友会问,不可重复读和幻读这不一样吗?

答案为:不一样。有啥区别?

幻读和不可重复读都是读取了另一条已经提交的事务;但不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

不可重复读和幻读是初学者不易分清的概念;简单来说,解决不可重复读的方法是大家常说的加行锁,解决幻读方式是加表锁

4)丢失更新

指两个事务同时更新一行数据,后提交(或撤销)的事务将之前事务提交的数据覆盖了。

注意:丢失更新可分为两类,分别为第一类丢失更新和第二类丢失更新。

第一类丢失更新:两个事务同时操作同一个数据时,当第一个事务撤销时,把已经提交的第二个事务的更新数据覆盖了,第二个事务就造成了数据丢失。

第二类丢失更新:当两个事务同时操作同一个数据时,第一个事务将修改结果成功提交后,对第二个事务已经提交的修改结果进行了覆盖,对第二个事务造成了数据丢失。

可以看出,上述问题,均是在并发情况下发生的,并发度越高,上述出现的情况也普遍。

怎么办?

为了避免上述事务并发问题的出现,标准 SQL 规范定义了四种事务隔离级别,不同的隔离级别对事务的处理有所不同。这四种事务的隔离级别如下:

1)Read Uncommitted(读未提交)

一个事务在执行过程中,既读取其他事务未提交的数据,又可以读取本事务未提交的修改数据。一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据。此隔离级别可防止丢失更新。

但因为能读取到其他事务未修改的数据,即不能防止“脏读”。这种事务隔离级别下,select语句不加锁。此时,可能读取到不一致的数据。

读未提交是并发最高,但一致性也最差的隔离级别。

2)Read Committed(读已提交)

此隔离级别可有效防止脏读。

在该隔离级别下,不允许2个未提交的事务之间并行执行,但它允许在一个事务执行的过程中,另外一个事务得到执行并提交。读取数据的一个事务不会禁止其他写事务,读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。此隔离级别不能解决不可重复读问题。

该方式是oracle数据库默认的隔离级别,事务提交需手动进行。

注意,在互联网大数据量,高并发量的场景下,几乎不会使用上述两种隔离级别。

原因如下:

“读未提交”虽说有最高的并行执行度,但大量的“脏读”是不被用户认可的;互联网场景下,经常会有大量的读写操作,当有大量写操作未提交时,会限制其他事务对数据的任何访问,这对互联网需要访问热点数据的需求下显得极为不够友好。

3)Repeatable Read(可重复读取)

该隔离级别可解决不可重复读的问题。在该隔离级别下,在一个事务使用某行的数据的过程中,不允许别的事务再对该行数据进行操作。可重复读是给数据库的行加上了锁。

读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。这里的事务读数据时禁止其他进程写,就保障了一个事务的可重复读性。

但是不能解决幻读,为啥?看个场景

可重复读取隔离级别下,因为只是对一个事务写操作的行加了行锁,但依旧允许别的事务在该表其他行插入和删除数据,于是就会出现,在事务1执行的过程中,如果先后两次select出符合某个条件的行,如果在这两次select之间另一个事务得到了执行,insert或delete了某些行,就会出现先后两次select出来的符合同一个条件的结果不一样,第一次select好像出现了幻觉一样,因此,这个问题也被成为幻读。要想解决幻读问题,需要将数据库的隔离级别设置为串行化。

4)Serializable(可串行化)

提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。此隔离级别可有效防止脏读、不可重复读和幻读。

但这个级别可能导致大量的超时现象和锁竞争,在实际应用中很少使用。

Serializable(可串行化)隔离级别可避免脏读、不可重复读、幻读 的发生,但失去了并发效率。

介绍完四种隔离级别,这里小结下:

Oracle数据库只支持Serializable (串行化) 级别和 Read committed (读已提交) 两种级别,默认隔离级别为 Read committed(读已提交)级别;

MySQL数据库中支持上面四种隔离级别,默认隔离级别为Repeatable read (可重复读)。

总结:

1.四种隔离级别最高的是 Serializable 级别,最低的是 Read uncommitted 级别,当然级别越高,执行效率就越低。像 Serializable 这样的级别,就是以 锁表 的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读) ;

2.在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读) ;而在 Oracle数据库 中,只支持Serializable (串行化) 级别和 Read committed (读已提交) 这两种级别,其中默认的为 Read committed(读已提交) 级别;

3.通常来说,事务的隔离级别越高,越能保证数据库的完整性和一致性,但相对来说,隔离级别越高,对并发性能的影响也越大。因此,Oracle通常将数据库的隔离级别设置为 Read Committed,即读已提交数据,它既能防止脏读,又能有较好的并发性能。虽然这种隔离级别会导致不可重复读、幻读和第二类丢失更新这些并发问题,但可通过在应用程序中采用悲观锁和乐观锁加以控制;

原文链接:https://mp.weixin.qq.com/s/NfQugRHDSrhl_hFWM7Vp4Q