一文搞懂数据库隔离级别及解决方案

介绍

在我们开发中,总会出点小bug,那我们程序员就需要一个一个解决。

隔离级别:一个事务必须与由其他事务进行的资源或数据更改相隔离的程度。隔离级别从允许的并发副作用(例如,脏读或虚拟读取)的角度进行描述。

遇到的问题

在我们开发中很容易因一些代码规范和逻辑控制的不足造成很多问题:

  1. 脏读一个事务读取到了另一个事务未提交的数据。

例:比如A给B转账,A给B打了100,但是还没有确认,这时,B收到100就跟A说我收到了。那么A就不会提交,既然B收到钱了,我就不用打了,就执行回滚操作。这就是个脏读了,B读取到了A未提交的数据。

  1. 不可重复读一个事务读取数据时,读取到了别的事务插入的数据,导致前后数据读取不一致。

例:A查询自己的账户有多少钱,查到了100,这时B偷偷给A转账100,A读取读到了200,两次金额不一样,A会想是不是出什么问题了。

  1. 幻读:幻读和不可重复读差不多,就是有个差别,幻读主要指查询的记录的条数不一致不可重复读就是单个行数的数据读取不一致

解决策略

我们的数据库事务给我们提供了隔离级别去解决这些问题,但是呢,事物都有其两面性,具体下文详细讲。
首先我们要知道有哪几个隔离级别:
按级别从低到高:
读未提交(Read uncommitted)
读已提交(Read commited)
可重复读(Repeatable read)
串行化(Serializable)

使用不同隔离级别可能会出现的问题:

隔离级别脏读不可重复读幻读
读未提交
读已提交
可重复读
串行化

可以看出,读未提交这个隔离级别啥都解决不了,串行化这个确都可以防止。
那么,我们直接使用串行化不就好了,为啥还要选来选去?
答案是因为,隔离级别越高,并发效果越差,甚至造成死锁这种麻烦。 所以开发中我们一般要根据业务去选择容错好的,也不会容易带来并发问题的。

解决方案解析

具体分析这些隔离级别怎么做的吧!
解决脏读——使用 读已提交
首先我们要理解什么是脏读,既然脏读是一个事物读到了另一个事务未提交的数据,那么我们就让它提交后再让别的事物读就好了,读已提交就是改变了释放锁的时机,让事务完成提交后再去释放锁。这样就解决了脏读问题。

解决不可重复读——使用 可重复读
不可重复读是因为读取过程中有其他事务修改数据,导致读取数据不一致。那我们就要保证一个事务读取数据的时候就让他老老实实读那个数据。可重复读是用一个MVCC(多版本并发控制)机制去解决的。

MVCC

多版本并发控制
MVCC其实就是行级锁的一个升级版。我们都知道数据库中有表锁和行锁,在表锁中读写操作是阻塞的,而MVCC的读写一般是不会阻塞的,这样避免了很多加锁过程。

MVCC具体实现:通过在每行记录后面保存两个隐藏的字段,一个保存的是此行的创建时间,一个保存的是此行的过期时间。它们存储的也并不是真的时间,而是系统版本号。就跟我们使用软件都有1.0,2.0这些版本,每个版本有它们自己的特点和数据。MVCC就是在每次开始事务时,都会对应自动递增并保存一个版本号,通过这个版本号去生成对应的一个时间点的数据快照,利用这个快照就可以保证数据读取的一致性。

MVCC把SQL分为两类:一种是快照读,就是普通的select操作,读的就是历史版本的数据。另一种是当前读,比如select … for update,insert,update,delete 读的都是最新的数据,不可重复读就是利用快照保存数据,然后就解决啦!

通俗的说就是:MVCC就是给每次事务操作的数据行都加个字段,代表这次事务的版本,那么如果我现在读取这行数据时,就会通过这个版本生成一个数据快照,那么我这个事务再读的时候,会直接从版本快照中获得数据,相当于帮我们缓存了一份数据,注意喔,我这两次读取都是同一个事务喔!

解决幻读——使用 串行化:
上文说到解决不可重复读用MVCC就可以根据版本保证读取的数据一致,那幻读不是也可以用这个去解决吗?
那么这里又要重申幻读和不可重复读的区别,不可重复读是针对某行数据,幻读是特指查询到记录条数,也就是多条数据的查询。
所以我们使用MVCC + Next-key Lock锁去解决幻读问题。


科普一下Innodb的三种行锁的算法:

  1. 行锁:就是对单条记录上锁。
  2. 间隙锁:锁定一个范围,但是不会包括记录自己,就是如果要查询一个id=10的数据时,就会把它范围外的加上锁防止插入数据操作,这个就是间隙锁。
  3. Next-key Lock:行锁+间隙锁的合体算法,使用它时不仅会把id=10 的范围加上行锁,也会把它间隙加上锁,对于行的查询,使用此法便ok。

具体实现:
当事务执行的是select…for update 时,Next-key Lock对范围加锁,这样事务A执行这个查询当前读语句时,事务B是不能去修改范围内的数据的。

总结

解决脏读:改变锁的释放时机。
解决不可重复读:MVCC机制,事务查询时相应保存一个事务版本号,然后保存查询的数据快照,期间不管其他事务怎么修改数据 ,本事务读的还是一样的数据。
解决幻读: MVCC+Next-key Lock锁 ,查询时对范围加锁,期间其他事务不能修改数据,保证数据读取一致性。

遇到的问题

我们使用串行化解决幻读会有什么问题产生呢?
我们知道解决幻读使用了间隙锁,那么我们在并发情况下很容易造成死锁!
举个栗子:
事务A、事务B同时执行select * from table where id = 10 for update ,前提是我们没有id=10这条数据,事务A执行时因加上了间隙锁,同时事务B也执行这条语句。这时,事务B如果去添加数据就会因为事务A的间隙锁造成阻塞,事务A再执行添加数据也会因为事务B间隙锁造成阻塞,这样就形成了一个死锁。

所以串行化的并发性不好,那我们实际项目中要合理的选择取舍。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值