1.事务(Transaction)定义:
是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。
2.事务的特性
- 原子性(Atomic):事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行。
- 一致性(Consistency):事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。这种特性称为事务的一致性。假如数据库的状态满足所有的完整性约束,就说该数据库是一致的。
- 隔离性(Isolation):由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态,到底是另一个事务执行之前的状态还是中间某个状态,相互之间存在什么影响,是可以通过隔离级别的设置来控制的。(即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行)。
- 持久性(Durability):事务结束后,事务处理的结果必须能够得到固化,即写入数据库文件中即使机器宕机数据也不会丢失,它对于系统的影响是永久性的。查看当前事务级别:
3.数据库隔离级别设置
查看当前事务级别:
查询结果:
设置数据库事务管理级别,将其设置为读未提交:
set transaction isolation level Read uncommitted
set global transaction isolation level Read uncommitted
set session transaction isolation level Read uncommitted
注意:
如果选择global,意思是此语句将应用于之后的所有session,而当前已经存在的session不受影响。如果选择session,意思是此语句将应用于当前session内之后的所有事务。
如果什么都不写,意思是此语句将应用于当前session内的下一个还未开始的事务。
修改隔离级别:
可以看到数据库的状态已经改变:
重新连接一下数据库即可生效
4.事务并发控制
多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:
(1)脏读
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:向A用户转账100元,对应SQL命令如下
update account set money=money+100 where name=’A’; (此时A查看账户余额)
当只执行完SQL时,A查看账户,发现确实钱已到账(此时即发生了脏读),而之后若该事务发生异常回滚,只要该事务不提交,那么当A以后再次查看账户时就会发现钱其实并没有转。
举个栗子
a.新建账户表如下:
注意:
查看该表的数据库引擎,若该引擎不位InnoDB引擎,需要将其修改为innoDB引擎。
修改语句为:ALTER TABLE account ENGINE = InnoDB;
b.创建java项目,使用jdbc链接数据库,代码如下:
在回滚处打一个断点,运行此代码
这时A查询数据库,查询到了没有提交的数据
但是当我们放开断点,执行回滚之后,数据恢复原状,若A再次查询数据库,会发现钱实际上没有到账。
(2)不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的结果,这是由于在查询间隔,被另一个事务修改并提交了。
例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
举个栗子:
新写一个通过jdbc查询数据的方法如下:
当执行完第一次查询,我们通过断点拦住,然后执行另一端代码,修改数据库中的值
运行结果:
(3)虚读(幻读)
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
代码同上,但是在第二次查询前,我们不是修改数据,而是插入数据,就可能造成幻读:
执行结果:
第二次读到的数据多了一条,这就是幻读。
总结:数据的更新操作会出现不可重复读,数据的增删会出现虚读(幻读)。
此外除了不配置事务的隔离性导致的以上事务问题,还有以下有关于事务的问题:
(4)第一类丢失更新
此种更新丢失是因为回滚的原因,所以也叫回滚丢失。此时两个事务同时更新count,两个事务都读取到100,事务一更新成功并提交,count=100+1=101,事务二出于某种原因更新失败了,然后回滚,事务二就把count还原为它一开始读到的100,此时事务一的更新就这样丢失了。
(5)第二类丢失更新
此种更新丢失是因为更新被其他事务给覆盖了,也可以叫覆盖丢失。举个例子,两个事务同时更新count,都读取100这个初始值,事务一先更新成功并提交,count=100+1=101,事务二后更新成功并提交,count=100+1=101,由于事务二count还是从100开始增加,事务一的更新就这样丢失了。
4.配置数据库事务隔离级别解决并发控制问题
我们可以看出,上面各种异常情况都是多个事务之间相互影响造成的,要解决这些问题,就需要将两个事务之间需要某种方式将他们从某种程度上分开,降低直至避免相互影响。
数据库有如下四种隔离级别:
- 读未提交(Read Uncommitted):该隔离级别指即使一个事务的更新语句没有提交,但是别的事务可以读到这个改变,几种异常情况都可能出现。极易出错,没有安全性可言,基本不会使用。
- 读已提交(Read Committed):该隔离级别指一个事务只能看到其他事务的已经提交的更新,看不到未提交的更新,消除了脏读和第一类丢失更新,这是大多数数据库的默认隔离级别,如Oracle,Sqlserver。
- 可重复读(Repeatable Read):该隔离级别指一个事务中进行两次或多次同样的对于数据内容的查询,得到的结果是一样的,但不保证对于数据条数的查询是一样的,只要存在读改行数据就禁止写,消除了不可重复读和第二类更新丢失,这是Mysql数据库的默认隔离级别。
- 串行化(Serializable):意思是说这个事务执行的时候不允许别的事务并发执行.完全串行化的读,只要存在读就禁止写,但可以同时读,消除了幻读。这是事务隔离的最高级别,虽然最安全最省心,但是效率太低,一般不会用。
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
总结:
级别可解决异常 | 第一类丢失更新 | 脏读 | 不可重复读 | 第二类丢失更新 | 虚读/幻读 |
读未提交 | N | N | N | N | N |
读已提交 | Y | Y | N | N | N |
可重复读 | Y | Y | Y | Y | N |
串行化 | Y | Y | Y | Y | Y |