数据库之并发问题以及隔离级别

第一个问题:更新丢失

这个问题不常见,但是我敢说,不少程序员写过存在这样隐患的代码。举个例子:小明在请假系统上提出了一个请假申请单,这个申请单班主任和年级主任都有权利审批(同意或者拒绝),加入班主任和年级主任同时操作了这个申请单(两个人同时操作相同的数据),班主任点击同意,但是年级主任点击拒绝,

伪代码如下(放在一个事务里):

1.先根据申请单编号查出这个数据,看看这笔数据的状态是不是还没有被处理

select  a.status from table t where t.apply_id='小明的请假单号';

2.如果还没有被处理,就根据前端请求的类型(同意或者拒绝)来更新这个申请单状态

if(status =="保存")

{

update table t set t.status = '新状态'  where t.status = '上面数据库查出来的状态';

}

两条请求同时调用了上面的方法,第一个请求A和第二个请求B先查出该请假单号状态为【保存】,A请求将数据改为同意,事务提交;B请求将将数据改为拒绝,但是发生异常,事务回滚后,该数据的状态又变为了【保存】。

或者两个事务都提交了:A把数据改成了同意状态,B随后把数据改成了拒绝状态,那么对于A来说,这笔数据就发生了更新丢失!

这样一来,就发生了数据丢失的情况,关键就在于,两个事务里并发的查出了同一条数据,然后对这条数据分别进行了操作,一个事务成功,另一个事务失败(导致事务回滚)。

解决办法:在事务中查询数据时候添加行级锁(for update);

select  a.status from table t where t.apply_id='小明的请假单号'  for update;

第一个事务查询时对该数据加了锁,第二个事务中再执行for update 查询,需要等到第一个事务完成,锁释放后才能查询出结果!

PS:for  update 锁又称悲观锁,是属于数据库本身的锁,还有一种锁,称作乐观锁(不要急,我们在后面会详细的讲解到)

第二问题:脏读

含义:脏读又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读

例如:事务T1修改了一行数据,但是还没有提交,这时候事务T2读取了被事务T1修改后的数据,之后事务T1因为某种原因Rollback了,那么事务T2读取的数据就是脏的。

见解:很多人都知道脏读是什么意思,但是实际情况下又很少能见到脏读的情况,那是因为Oracle默认的隔离级别是【读提交】(read commited),MySQL 默认的隔离级别是  【可重复读】(repeatable read),所以是见不到脏读的情况;

如果把隔离级别改为【读未提交】(read uncommited),脏读就会出现!!

PS:事务的隔离级别我会在下面讲到!

第三问题:不可重复读

不可重复读是指在同一个事务内,两个相同的查询返回了不同的结果。

例如:事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。

解决思路:将隔离级别设置为:可重复读取(Repeatable Read)

以操作同一行数据为前提,读事务禁止其他写事务(但允许其他读事务),未提交的写事务禁止其他读事务和写事务。

此隔离级别可以防止更新丢失、脏读、不可重复读,但不能防止幻读。

此隔离级别可以通过“共享读锁”和“排他写锁”实现。

第四问题:幻读

事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据

例如:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样。这就叫幻读!

讲到了这里,咱们说一下不可重复读和幻读的区别,不可重复读是由update操作造成,幻读是由于insert了新数据造成的!

解决思路:

序列化(Serializable)。5

提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。

此隔离级别可以防止更新丢失、脏读、不可重复读、幻读。

 

下面咱们简单说一下数据库的隔离级别:
以上的4种问题(更新丢失、脏读、不可重复读、幻读)都和事务的隔离级别有关。通过设置事务的隔离级别,可以避免上述问题的发生。

未授权读取级别:

也称为读未提交(Read Uncommitted)。

以操作同一行数据为前提,读事务允许其他读事务和写事务,未提交的写事务禁止其他写事务(但允许其他读事务)。

此隔离级别可以防止更新丢失,但不能防止脏读、不可重复读、幻读。

此隔离级别可以通过“排他写锁”实现。

授权读取级别:

也称为读提交(Read Committed)。

以操作同一行数据为前提,读事务允许其他读事务和写事务,未提交的写事务禁止其他读事务和写事务。

此隔离级别可以防止更新丢失、脏读,但不能防止不可重复读、幻读。

此隔离级别可以通过“瞬间共享读锁”和“排他写锁”实现。

可重复读取级别:

可重复读取(Repeatable Read)。

以操作同一行数据为前提,读事务禁止其他写事务(但允许其他读事务),未提交的写事务禁止其他读事务和写事务。

此隔离级别可以防止更新丢失、脏读、不可重复读,但不能防止幻读。

此隔离级别可以通过“共享读锁”和“排他写锁”实现。

序列化级别:

序列化(Serializable)。

提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。

此隔离级别可以防止更新丢失、脏读、不可重复读、幻读。

如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

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

总结:

 

 

 

posted @ 2019-12-06 15:56  SR丶  阅读(467)  评论(0编辑  收藏  举报