数据库事务的隔离级别和并发异常

众所周知,数据库事务具有隔离性,理论上来说事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样。然而完全的隔离性会导致系统并发性能很低,降低对资源的利用率,因而实际上对隔离性的要求会有所放宽,这也会一定程度造成对数据库一致性要求降低。

隔离级别

常见的事务隔离级别有四种,从低到高依次是:

  • 读未提交(Read uncommitted)
  • 读已提交(Read committed)
  • 可重复读(Repeatable read)
  • 串行化(Serializable)

事务的隔离级别越低,可能出现的并发异常越多,但通常而言系统能提供的并发能力就越强。

读未提交(Read uncommitted),B事务可以读取到未提交的A事务中操作的数据,会导致脏读,为解决脏读,引入读已提交。

读已提交(Read committed),B事务要等A事务提交后才能读取到A事务中操作的数据,但是依然会导致不可重复读的问题(即B事务在A事务提交前后读取的数据不一致)。

可重复读(Repeatable read),一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的,Mysql是通过快照版本来实现可重复读的,即事务开始时就拿到了一个版本号,别的事务操作的数据版本号不一样,就不会影响当前事务重复读取的数据,但是会看不到别的事务提交的数据。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

序列化(Serializable),最高的事务隔离级别,事务串行化顺序执行,但是效率低下,比较耗数据库性能,一般不使用。

 脏写脏读不可重复读幻读丢失更新
读未提交 可能可能可能可能
读已提交  可能可能可能
可重复读   可能 
串行化     

另外,还有SQLServer中的快照(Snapshot)已提交读快照(Read Committed Snapshot)两种常见隔离级别。

并发异常

1、脏写

A事务可以修改另外B事务未提交的修改过的数据。任何一种隔离级别都不允许脏写的情况发生,解决方案是写操作时采用排它锁,A事务对某条数据进行写操作时,该记录加排它锁,B事务对该条数据的写操作必须等待。

2、脏读

如果一个事务A向数据库写了数据,但事务还没提交或终止,另一个事务B就看到了事务A写进数据库的数据,并且在这个数据基础上进行操作。如果此时恰巧A事务进行回滚,那么B事务读到的数据是根本不被承认的。

如下:账户应为150,脏读导致结果为130.

3、不可重复读

不可重复读指的是由于事务A对数据的操作,导致事务B前后两次读取到的结果不一致。

4、幻读

A事务读取B提交的新增数据,这时A事务将出现幻想读的问题。幻读一般发生在计算统计数据的事务中。举个例子,假设银行系统在同一个事务中两次统计存款的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时两次统计的总金额将不一致。

幻读和不可重复读是两个容易混淆的概念,前者是指读到了其他事物已经提交的新增数据,而后者是读到了已经提交事务的更改数据(更改或删除)。为了避免这两种情况,采取的策略是不同的:防止读到更改数据,只需对操作的数据添加行级锁,阻止操作过程中的数据发生变化,而防止读到新增数据,则往往需要添加表级锁或者间隙锁。

5、丢失更新

当多个事务并发写同一数据时,先执行的事务所写的数据会被后写的覆盖,这也就是更新丢失。除此之外,更新丢失主要发生在read-modify-write类型的事务当中:就是要先查询数据,然后计算新的数据,最后写回新的数据。

如下两种情形都可直接导致数据出错:

更新丢失的解决方案:

排它锁:select for update,这样在读取数据时就不允许其他事务的更新操作。 

乐观锁:版本号机制,增加一个版本或者时间戳的列,对于进行修改行时先获取版本号,修改的时候比较一下版本号是否一致,一致再进行修改并将版本号加一更新。

/**------SQL--------------查询前一个版本号和需要操作的数据----------------------*/
SELECT account,last_modify FROM `user` WHERE user_id = '1dce2f04ed664f5ca74b9e6cacf74e1b';
/**------Java-------------数据处理和准备----------------------*/
USER user = select.fetchOne();
double newAount = user.account + 100;
long timestampNow = System.currentTimeMillis();
/**------SQL--------------根据上一个版本号更新数据,如果版本号不对说明已被另外的事务并发更新----------------------*/
UPDATE `user` SET account = newAcount,last_modify = timestampNow WHERE last_modify = user.last_modify AND user_id = '1dce2f04ed664f5ca74b9e6cacf74e1b';

各数据库的事务隔离级别

  • Mysql中默认的事务隔离级别为REPEATABLE-READ

---查询Mysql隔离级别
select @@transaction_isolation;
---设置Mysql隔离级别为READ COMMITTED
set session transaction isolation level read committed;
  • SQLServer中默认的事务隔离级别为READ COMMITTED

---查询SQLServer隔离级别
DBCC USEROPTIONS;
---设置SQLServer隔离级别为REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  • Oracle默认的事务隔离级别为READ COMMITTED
---先开启一个事务
declare
	trans_id Varchar2(100);
begin
	trans_id := dbms_transaction.local_transaction_id( TRUE );
end;
---查询Oracle的隔离级别
SELECT CASE BITAND(t.flag, POWER(2, 28)) WHEN 0 THEN 'READ COMMITTED' ELSE 'SERIALIZABLE' END AS isolation_level FROM v$transaction t;
---设置Oracle隔离级别为SERIALIZABLE---执行报错
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

Oracle的隔离级别只有两种---SERIALIZABLE和READ COMMITTED

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值