jakefei 2020-07-19
数据库事务可以被定义为一个或者几个数据库允许的操作的集合。这个集合需要支持ACID特性。
在ACID特性中,隔离性(isolation)指的是不同事务在提交的时候,最终呈现出来的效果是串行的,换句话说,既是不同事务,按照提交的先后顺序执行,再换句话说,对于事务本身来说,它所感知的数据库,应该只有它自己在操作。那么最简单的方法,我们按照定义来实现数据库事务的隔离性即可,很明显这在同时并发有多个事务要执行的环境下是没有执行效率的,一个事务的执行,必然会阻塞其他事务的执行。所以SQL的标准制定者对此作出妥协,提出隔离性的四个等级,其中最高级隔离等级才是串行执行。在没有到达串行执行等级的情况下,事务又是并发执行的,总是或多或少会存在问题,这在下面会讲到。
谈到事务一般都是以下四点:
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
是指应用层系统从一种正确的状态,在事务成功后,达成另一种正确的状态。比如:A、B账面共计100W,A向B转账,加上事务控制,转成功后,他们账户总额应还是100W,事务应保持这种应用逻辑正确一致。还有,转账(事务成功)前后,数据库内部的数据结构--比如账户表的主键、外键、列必须大于0、Btree、双向链表等约束需要是正确的,和原来一致的。
隔离是指当多个事务提交时,让它们按顺序串行提交,每个时刻只有一个事务提交。但隔离处理并发事务,效率很差。所以SQL标准制作者妥协了,提出了4种事务隔离等级(1,read-uncommited 未提交就读,可能产生脏读 2,read-commited 提交后读 可能产生不可重复读 3,repeatable-read 可重复读 可能产生幻读 4,serializable 序列化,最高级别,按顺序串行提交)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
举个简单的例子理解以上四点
针对同一个事务
这个过程包含两个步骤
A: 800 - 200 = 600
B: 200 + 200 = 400
原子性表示,这两个步骤一起成功,或者一起失败,不能只发生其中一个动作
针对一个事务操作前与操作后的状态一致
操作前A:800,B:200
操作后A:600,B:400
一致性表示事务完成后,符合逻辑运算
表示事务结束后的数据不随着外界原因导致数据丢失
操作前A:800,B:200
操作后A:600,B:400
如果在操作前(事务还没有提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为
A:800,B:200
如果在操作后(事务已经提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为
A:600,B:400
针对多个用户同时操作,主要是排除其他事务对本次事务的影响
不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
未提交就读,可能产生脏读;
指一个事务读取了另外一个事务未提交的数据。
就是A事务在读取数据时,B事务对同一个数据修改了,但B未提交,A再读取时,读到了B修改后的数据,但是B事务提交失败,回滚,A后读到的数据就是B修改后的脏数据,此为脏读。
提交后读 可能产生不可重复读;
在一个事务内读取表中的某一行数据,多次读取结果不同。
就是A事务读取数据,B事务改了这个数据,也提交成功了,A再读取就是B修改后的数据,再也不能重复读到最开始的那个数据值了,此为不可重复读;
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……
例如:
页面统计查询值
点击生成报表的时候,B有人转账进来300(事务已经提交)
可重复读 可能产生幻读;
是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
(一般是行影响,多了一行)
可重复读就是A事务读取数据,B事务改了这个数据(update),也提交成功了,A再读这个数据,SQL机制强行让A仍然读之前读到的数据值,这就是可重复读,这种机制对Insert操作无效,A事务在可重复读的机制下,读取数据,B事务insert一条数据,提交成功,A再读这个数据,会显示B插入的数据,此为幻读。
是最严格的事务隔离级别,它要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行。
设置 | 描述 |
Serializable | 可避免脏读、不可重复读、虚读情况的发生。(串行化) |
Repeatable read(可重复读) | 可避免脏读、不可重复读情况的发生。 可能产生幻读; |
Read committed (提交后读) | 可避免脏读情况发生(读已提交)。 可能产生不可重复读; |
Read uncommitted (读未提交) | 最低级别,以上情况均无法保证。可能产生脏读; |
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,隔离级别越高,越能保证数据的完整性和一致性,但是执行效率就越低,对并发性能的影响也越大。
MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);
Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
大多数的数据库默认隔离级别为 Read Commited,比如 SqlServer、Oracle
少数数据库默认隔离级别为:Repeatable Read 比如: MySQL InnoDB
如果是使用JDBC对数据库的事务设置隔离级别的话,也应该是在调用Connection对象的setAutoCommit(false)方法之前。调用Connection对象的setTransactionIsolation(level)即可设置当前链接的隔离级别,至于参数level,可以使用Connection对象的字段:
Connection conn=null;Statement st=null;ResultSet rs=null;try{ conn=JdbcUtils.getConnection(); //设置该链接的隔离级别 conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);conn.setAutoCommit(false);//开启事务
Connection字段对应各个级别说明:
设置 | 描述 |
TRANSACTION_NONE | 指示事务不受支持的常量 |
TRANSACTION_SERIALIZABLE | 指示不可以发生脏读、不可重复读和虚读的常量。 |
TRANSACTION_REPEATABLE_READ | 指示不可以发生脏读和不可重复读的常量;虚读可以发生。 |
TRANSACTION_READ_UNCOMMITTED | 指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。 |
TRANSACTION_READ_COMMITTED | 指示不可以发生脏读的常量;不可重复读和虚读可以发生。 |
SELECT @@session.tx_isolation; SELECT @@tx_isolation; SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted; SET SESSION TRANSACTION ISOLATION LEVEL read committed; SET SESSION TRANSACTION ISOLATION LEVEL repeatable read; SET SESSION TRANSACTION ISOLATION LEVEL serializable; start transaction; --建表 drop table AMOUNT; CREATE TABLE `AMOUNT` ( `id` varchar(10) NULL, `money` numeric NULL ) ; --插入数据 insert into amount(id,money) values(‘A‘, 800); insert into amount(id,money) values(‘B‘, 200); insert into amount(id,money) values(‘C‘, 1000); --测试可重复读,插入数据 insert into amount(id,money) values(‘D‘, 1000); --设置事务 SET SESSION TRANSACTION ISOLATION LEVEL read uncommitted; SELECT @@tx_isolation; --开启事务 start transaction; --脏读演示,读到其他事务未提交的数据 --案列1,事务一:A向B转200,事务二:查看B金额变化,事务一回滚事务 update amount set money = money - 200 where id = ‘A‘; update amount set money = money + 200 where id = ‘B‘; --不可重复读演示,读到了其他事务提交的数据 --案列2,事务一:B向A转200,事务二:B向C转200转100 SET SESSION TRANSACTION ISOLATION LEVEL read committed; --开启事务 start transaction; --两个事务都查一下数据(转账之前需要,查一下金额是否够满足转账) select * from amount; --事务一:B向A转200 update amount set money = money - 200 where id = ‘B‘; update amount set money = money + 200 where id = ‘A‘; commit; --事务二:B向C转200转100 update amount set money = money - 100 where id = ‘B‘; update amount set money = money + 100 where id = ‘C‘; commit; --从事务二的角度来看,读到了事务一提交事务的数据,导致金额出现负数 --幻读演示 --案列3,事务一:B向A转200,事务二:B向C转200转100 SET SESSION TRANSACTION ISOLATION LEVEL repeatable read; --开启事务 start transaction; --两个事务都查一下数据(转账之前需要,查一下金额是否够满足转账) select * from amount; --事务一:B向A转200 update amount set money = money - 200 where id = ‘B‘; update amount set money = money + 200 where id = ‘A‘; commit; --事务二:B向C转200转100 update amount set money = money - 100 where id = ‘B‘; update amount set money = money + 100 where id = ‘C‘; commit; --从事务二的角度来看,读到了事务一提交事务的数据,导致金额出现负数
设置