事务,Oracle,MySQL及Spring事务隔离级别

摘要:
因此,Oracle不支持脏读SQL标准定义的默认事务隔离级别SERIALIZABLE,但Oracle默认使用READCOMMITTED将隔离级别设置为SETTRANSSACTIONSISOLATIONLEVEL[READUNCCOMMITTED | READCOMMITED | REPEATABLEREAD | SERIALIZABLE]以下是Oracle设置SERIALIZABLE隔离级别的示例:事务T1在左侧,事务T2在右侧,因为T2是SERIALIZIABLE。因此,即使在事务T1提交数据之后,事务T2仍然无法看到T1提交的数据。不允许进行奇妙的读取和不可重复的读取。也就是说,事务T2开始之后事务T1的插入和更新操作不会影响事务T2。

一、什么是事务: 
事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败.

二、事务特性(4种): 
原子性 (atomicity):强调事务的不可分割;
一致性 (consistency):事务的执行的前后数据的完整性保持一致;
隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰 ;
持久性(durability) :事务一旦结束,数据就持久到数据库。

三、如果不考虑隔离性引发安全性问题: 
脏读 :一个事务读到了另一个事务的未提交的数据 
不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致. 
虚幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致.

四、为了处理这些问题,SQL标准定义了以下几种事务隔离级别

READ UNCOMMITTED(读未提交): 不可重复读,脏读及幻读都允许;

READ COMMITTED(不可重复读):允许不可重复读,不允许脏读,允许幻读;

REPEATABLE READ (可重复读):不允许不可重复读和脏读,但允许幻读;

SERIALIZABLE(串行化):不可重复读,脏读和幻读都不允许。

说明:

  1、SQL规范所规定的标准,不同的数据库具体的实现可能会有些差异

  2、mysql中默认事务隔离级别是可重复读时并不会锁住读取到的行

  3、事务隔离级别为读提交时,写数据只会锁住相应的行

  4、事务隔离级别为可重复读时,写数据会锁住整张表

  5、事务隔离级别为串行化时,读写数据都会锁住整张表

   6、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

五、Oracle,MySQL及Spring事务隔离级别

1. Oracle事务隔离级别(2种)

Oracle数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别。所以Oracle不支持脏读

SQL标准所定义的默认事务隔离级别是SERIALIZABLE,但是Oracle 默认使用的是READ COMMITTED

设置隔离级别使用 SET TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]

下面是oracle 设置SERIALIZABLE隔离级别一个示例:

事务,Oracle,MySQL及Spring事务隔离级别第1张

左面是事务T1,右面是事务T2,因为T2级别为SERIALIZABLE,所以即使事务T1在提交了数据之后,事务T2还是看不到T1提交的数据,幻想读和不可重复读都不允许了。

那如何能查看到T1新增的记录呢? 上面T1和T2是并发执行,在T1执行insert的时候事务T2已经开始了,因为T2级别是SERIALIZABLE,所以T2所查询的数据集是T2事务开始前数据库的数据。即事务T1在事务T2开始之后的insert和update操作的影响都不会影响事务T2。现在重新开启一个事务T3 就可以看到T1新增的记录了。

当下列事件发生时,事务就开始了:

1)连接到数据库,并执行第一条DML语句

2)前一个事务结束后,又输入了另一条DML语句

2.MySQL事务隔离级别(4种)

事务,Oracle,MySQL及Spring事务隔离级别第2张

mysql默认的事务隔离级别为repeatable-read

事务,Oracle,MySQL及Spring事务隔离级别第3张

实例说明MySQL各个隔离级别的情况

1)、读未提交:

(1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:

事务,Oracle,MySQL及Spring事务隔离级别第4张

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

事务,Oracle,MySQL及Spring事务隔离级别第5张

(3)这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:

事务,Oracle,MySQL及Spring事务隔离级别第6张

(4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据:

事务,Oracle,MySQL及Spring事务隔离级别第7张

(5)在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没有变成350,居然是400,是不是很奇怪,数据的一致性没问啊,如果你这么想就太天真 了,在应用程序中,我们会用400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别

事务,Oracle,MySQL及Spring事务隔离级别第8张

2)、读已提交

(1)打开一个客户端A,并设置当前事务模式为read committed(未提交读),查询表account的初始值:

事务,Oracle,MySQL及Spring事务隔离级别第9张

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

事务,Oracle,MySQL及Spring事务隔离级别第10张

(3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:

事务,Oracle,MySQL及Spring事务隔离级别第11张

(4)客户端B的事务提交

事务,Oracle,MySQL及Spring事务隔离级别第12张

(5)客户端A执行与上一步相同的查询,结果与上一步不一致,即产生了不可重复读的问题,在应用程序中,假设我们处于客户端A的会话,查询到lilei的balance为450,但是其他事务将lilei的balance值改为400,我们并不知道,如果用450这个值去做其他操作,是有问题的,不过这个概率真的很小哦,要想避免这个问题,可以采用可重复读的隔离级别

事务,Oracle,MySQL及Spring事务隔离级别第13张

3)、可重复读

(1)打开一个客户端A,并设置当前事务模式为repeatable read,查询表account的初始值:

事务,Oracle,MySQL及Spring事务隔离级别第14张

(2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交,客户端B的事务居然可以修改客户端A事务查询到的行,也就是mysql的可重复读不会锁住事务查询到的行,这一点出乎我的意料,sql标准中事务隔离级别为可重复读时,读写操作要锁行的,mysql居然没有锁,我了个去。在应用程序中要注意给行加锁,不然你会以步骤(1)中lilei的balance为400作为中间值去做其他操作

事务,Oracle,MySQL及Spring事务隔离级别第15张

(3)在客户端A执行步骤(1)的查询:

事务,Oracle,MySQL及Spring事务隔离级别第16张

(4)执行步骤(1),lilei的balance仍然是400与步骤(1)查询结果一致,没有出现不可重复读的 问题;接着执行update balance = balance - 50 where id = 1,balance没有变成400-50=350,lilei的balance值用的是步骤(2)中的350来算的,所以是300,数据的一致性倒是没有被破坏,这个有点神奇,也许是mysql的特色吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> select* from account;
+------+--------+---------+
| id| name | balance |
+------+--------+---------+
| 1 | lilei |  400 |
| 2 | hanmei | 16000 |
| 3 | lucy | 2400 |
+------+--------+---------+
rows inset(0.00 sec)
 
mysql> update account setbalance = balance - 50 where id= 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
 
mysql> select* from account;
+------+--------+---------+
| id| name | balance |
+------+--------+---------+
| 1 | lilei |  300 |
| 2 | hanmei | 16000 |
| 3 | lucy | 2400 |
+------+--------+---------+
rows inset(0.00 sec)

(5) 在客户端A开启事务,查询表account的初始值

1
2
3
4
5
6
7
8
9
10
11
12
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select* from account;
+------+--------+---------+
| id| name | balance |
+------+--------+---------+
| 1 | lilei | 300 |
| 2 | hanmei | 16000 |
| 3 | lucy | 2400 |
+------+--------+---------+
rows inset(0.00 sec)

(6)在客户端B开启事务,新增一条数据,其中balance字段值为600,并提交

1
2
3
4
5
6
7
8
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into account values(4,'lily',600);
Query OK, 1 row affected (0.00 sec)
 
mysql> commit;
Query OK, 0 rows affected (0.01 sec)

(7) 在客户端A计算balance之和,值为300+16000+2400=18700,没有把客户端B的值算进去,客户端A提交后再计算balance之和,居然变成了19300,这是因为把客户端B的600算进去了,站在客户的角度,客户是看不到客户端B的,它会觉得是天下掉馅饼了,多了600块,这就是幻读,站在开发者的角度,数据的 一致性并没有破坏。但是在应用程序中,我们得代码可能会把18700提交给用户了,如果你一定要避免这情况小概率状况的发生,那么就要采取下面要介绍的事务隔离级别“串行化”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
mysql> selectsum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 18700 |
+--------------+
1 row inset(0.00 sec)
 
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
 
mysql> selectsum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 19300 |
+--------------+
1 row inset(0.00 sec)

4).串行化

(1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mysql> setsession transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
 
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
mysql> select* from account;
+------+--------+---------+
| id| name | balance |
+------+--------+---------+
| 1 | lilei | 10000 |
| 2 | hanmei | 10000 |
| 3 | lucy | 10000 |
| 4 | lily | 10000 |
+------+--------+---------+
rows inset(0.00 sec)

(2)打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,往往一个事务霸占了一张表,其他成千上万个事务只有干瞪眼,得等他用完提交才可以使用,开发中很少会用到。

1
2
3
4
5
6
7
8
mysql> setsession transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
 
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into account values(5,'tom',0);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

3.Spring事务隔离级别(5种) 
1)DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. 
2)未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生 ;
3)已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生 ;
4)可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生;
5)串行化的 (serializable) :避免以上所有读问题. 

Oracle 默认:读已提交
Mysql 默认:可重复读 


read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。 
read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。 
repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。 
serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读(避免三种)。

事务的传播行为 
PROPAGION_XXX :事务的传播行为 
* 保证同一个事务中 
PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认) 
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务 
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常 
* 保证没有在同一个事务中 
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务 
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务 
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常 
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行

免责声明:文章转载自《事务,Oracle,MySQL及Spring事务隔离级别》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇基于ARM64的Qemu/KVM学习环境搭建【Flink】Windows环境部署Flink 1.10版本问题解决下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

单表60亿记录等大数据场景的MySQL优化和运维之道

前言 MySQL数据库大家应该都很熟悉,而且随着前几年的阿里的去IOE,MySQL逐渐引起更多人的重视。 MySQL历史 1979年,Monty Widenius写了最初的版本,96年发布1.0 1995-2000年,MySQL AB成立,引入BDB 2000年4月,集成MyISAM和replication 2001年,Heikki Tuuri向M...

MySQL外键设置中的的 Cascade、NO ACTION、Restrict、SET NULL

1 . cascade方式  在父表上update/delete记录时,同步update/delete掉子表的匹配记录     2. set null方式 在父表上update/delete记录时,将子表上匹配记录的列设为null要注意子表的外键列不能为not null   3. No action方式 如果子表中有匹配的记录,则不允许对父表对应候选键进行...

CentOS安装mysql源码包

1.# cd /usr/local/src 2.上传mysql.tar.gz文件 3.# tar -zxvf mysql-5.6.36-linux-glibc2.5-x86_64.tar.gz 4.# mv mysql-5.6.36-linux-glibc2.5-x86_64.tar /usr/local/mysql 5.# useradd -s /sbi...

MySQL连接报错2059

当启动Django自带的服务器时,报错2059: ... MySQLdb._exceptions.OperationalError: (2059, <NULL>) ... 查看了一下mysql版本: mysql> select version(); +-----------+ | version() | +-----------+ |...

linux安装mysql(unrecognized service错误)

今天在Linux在安装MySQL,安装完成后却无法打开,百度找原因,试了很多方法终于解决了。 MySQL安装: [root@centos usr]# yum install mysql 无法正常启动: 查询确实已安装: 按照百度的方法启动,还是无法开启:找不到此文件 换个命令重新安装: [root@centos ~]# yum install mys...

mysql增删改和学生管理sql

importpymysql #2.建连 conn = pymysql.connect("localhost","root",'root','李森') print(conn) #3.获取游标 cur =conn.cursor() #4.增 sql="insert into student_1 values(default,%s,%s,%s,%s)"cur.e...