mysql事务和隔离级别笔记

一、数据库事务的知识

数据库事务具有以下4个基本特征,也就是著名的ACID。

Atomic(原子性):事务中包含的操作被看作一个整体的业务单元,这个业务单元中的操作要么全部成功,要么全部失败,不会出现部分失败、部分成功的场景。

Consistency(一致性):事务在完成时,必须使所有的数据都保持一致状态,在数据库中所有的修改都基于事务,保证了数据的完整性。

Isolation(隔离性):这是我们讨论的核心内容,正如上述,可能多个应用程序线程同时访问同一数据,这样数据库同样的数据就会在各个不同的事务中被访问,这样会产生丢失更新。为了压制丢失更新的产生,数据库定义了隔离级别的概念,通过它的选择,可以在不同程度上压制丢失更新的发生。因为互联网的应用常常面对高并发的场景,所以隔离性是需要掌握的重点内容。

Durability(持久性):事务结束后,所有的数据会固化到一个地方,如保存到磁盘当中,即使断电重启后也可以提供给应用程序访问。

这4个特性,除了隔离性,都还是比较好理解的,所以这里会更为深入地讨论隔离性。在多个事务同时操作数据的情况下,会引发丢失更新的场景,例如,电商有一种商品,在疯狂抢购中,会出现多个事务同时访问商品库存的场景,这样就会产生丢失更新。一般而言,存在两种类型的丢失更新,让我们了解下它们。下面假设一种商品的库存数量还有100,每次抢购都只能抢购1件商品,那么在抢购中就可能出现如表6-1所示的场景。

可以看到,T5时刻事务1回滚,导致原本库存为99的变为了100,显然事务2的结果就丢失了,这就是一个错误的值。类似地,对于这样一个事务回滚另外一个事务提交而引发的数据不一致的情况,我们称为第一类丢失更新。然而它却没有讨论的价值,因为目前大部分数据库已经克服了第类丢失更新的问题,也就是现今数据库系统已经不会再出现表6-1的情况了。所以对于这样的场景不再深入讨论,而是讨论第二类丢失更新,也就是多个事务都提交的场景如果是多个事务并发提交,会出现怎么样的不一致的场景呢?例如可能发生如表6-2所示的场景。

注意T5时刻提交的事务。因为在事务1中,无法感知事务2的操作,这样它就不知道事务2已经修改过了数据,因此它依旧认为只是发生了一笔业务,所以库存变为了99,而这个结果又是错误的结果。这样,T5时刻事务1提交的事务,就会引发事务2提交结果的丢失,我们把这样的多个事务都提交引发的丢失更新称为第二类丢失更新。这是我们互联网系统需要关注的重点内容。为了克服这些问题,数据库提出了事务之间的隔离级别的概念,这就是本章的重点内容之一。

二、详解隔离级别

上面我们讨论了第二类丢失更新。为了压制丢失更新,数据库标准提出了4类隔离级别,在不同的程度上压制丢失更新,这4类隔离级别是未提交读、读写提交、可重复读和串行化,它们会在不同的程度上压制丢失更新的情景。

也许你会有一个疑问,都全部消除丢失更新不就好了吗,为什么只是在不同的程度上压制丢失更新呢?其实这个问题是从两个角度去看的,一个是数据的一致性,另一个是性能。数据库现有的技术完全可以避免丢失更新,但是这样做的代价,就是付出锁的代价,在互联网中,系统不单单要考虑数据的一致性,还要考虑系统的性能。试想,在互联网中使用过多的锁,一旦出现商品抢购这样的场景,必然会导致大量的线程被挂起和恢复,因为使用了锁之后,一个时刻只能有一个线程访问数据,这样整个系统就会十分缓慢,当系统被数千甚至数万用户同时访问时,过多的锁就会引发宕机,大部分用户线程被挂起,等待持有锁事务的完成,这样用户体验就会十分糟糕。因为用户等待的时间会十分漫长,一般而言,互联网系统响应超过5秒,就会让用户觉得很不友好,进而引发用户忠诚度下降的问题。所以选择隔离级别的时候,既需要考虑数据的一致性避免脏数据,又要考虑系统性能的问题。因此数据库的规范就提出了4种隔离级别来在不同的程度上压制丢失更新。下面我们通过商品抢购的场景来讲述这4种隔离级别的区别。

1、未提交读

未提交读( read uncommitted)是最低的隔离级别,其含义是允许一个事务读取另外一个事务没有提交的数据。未提交读是一种危险的隔离级别,所以一般在我们实际的开发中应用不广,但是它的优点在于并发能力高,适合那些对数据一致性没有要求而追求高并发的场景,它的最大坏处是出现脏读。让我们看看可能发生的脏读场景,如表6-3所示。

 

表6-3中的T3时刻,因为采用未提交读,所以事务2可以读取事务1未提交的库存数据为1,这里当它扣减库存后则数据为0,然后它提交了事务,库存就变为了0,而事务1在T5时刻回滚事务,因为第一类丢失更新已经被克服,所以它不会将库存回滚到2,那么最后的结果就变为了0,这样就出现了错误。

脏读一般是比较危险的隔离级别,在我们实际应用中采用得不多。为了克服脏读的问题,数据库隔离级别还提供了读写提交( read commited)的级别,下面我们讨论它。

2、读写提交

读写提交( read committed)隔离级别,是指一个事务只能读取另外一个事务已经提交的数据,不能读取未提交的数据。例如,表6-3的场景在限制为读写提交后,就变为表6-4描述的场景了。

在T3时刻,由于采用了读写提交的隔离级别,因此事务2不能读取到事务1中未提交的库存1所以扣减库存的结果依旧为1,然后它提交事务,则库存在T4时刻就变为了1。T5时刻,事务1回滚,因为第一类丢失更新已经克服,所以最后结果库存为1,这是一个正确的结果。但是读写提交也会产生下面的问题,如表6-5所描述的场景。

在T3时刻事务2读取库存的时候,因为事务1未提交事务,所以读出的库存为1,于是事务2认为当前可扣减库存:在T4时刻,事务1已经提交事务,所以在T5时刻,它扣减库存的时候就发现库存为0,于是就无法扣减库存了。这里的问题在于事务2之前认为可以扣减,而到扣减那一步却发现已经不可以扣减,于是库存对于事务2而言是一个可变化的值,这样的现象我们称为不可重复卖,这就是读写提交的一个不足。为了克服这个不足,数据库的隔离级别还提出了可重复读的隔离级别,它能够消除不可重读的问题。

3、可重复读

可重复读的目标是克服读写提交中出现的不可重复读的现象,因为在读写提交的时候,可能出现一些值的变化,影响当前事务的执行,如上述的库存是个变化的值,这个时候数据库提出了可重复读的隔离级别。这样就能够克服不可重复读的现象如表6-6所示。

可以看到,事务2在T3时刻尝试读取库存,但是此时这个库存已经被事务1事先读取,所以这个时候数据库就阻塞它的读取,直至事务1提交,事务2才能读取库存的值。此时已经是T5时刻,而读取到的值为0,这时就已经无法扣减了,显然在读写提交中出现的不可重复读的场景被消除了。但是这样也会引发新的问题的出现,这就是幻读。假设现在商品交易正在进行中,而后台有人也在进行査询分析和打印的业务,让我们看看如表6-7所示可能发生的场景。

这便是幻读现象。可重复读和幻读,是读者比较难以理解的内容,这里稍微论述一下。首先这里的笔数不是数据库存储的值,而是一个统计值,商品库存则是数据库存储的值,这一点是要注意也就是幻读不是针对一条数据库记录而言,而是多条记录,例如,这51笔交易笔数就是多条数据库记录统计出来的。而可重复读是针对数据库的单一条记录,例如,商品的库存是以数据库里面条记录存储的,它可以产生可重复读,而不能产生幻读。

4、串行化

串行化( Serializable)是数据库最高的隔离级别,它会要求所有的SQL都会按照顺序执行,这样就可以克服上述隔离级别出现的各种问题,所以它能够完全保证数据的一致性。

5、使用合理的隔离级别

通过上面的讲述,读者应该对隔离级别有了更多的认识,使用它能够在不同程度上压制丢失更新,于是可以总结成如表6-8所示的一张表

作为互联网开发人员,在开发高并发业务时需要时刻记住隔离级别可能发生的各种概念和相关的现象,这是数据库事务的核心内容之一,也是互联网企业关注的重要内容之一。追求更高的隔离级别,它能更好地保证了数据的一致性,但是也要付出锁的代价。有了锁,就意味着性能的丢失,而且隔离级别越高,性能就越是直线地下降。所以我们在选择隔离级别时,要考虑的不单单是数据致性的问题,还要考虑系统性能的问题。例如,一个高并发抢购的场景,如果采用串行化隔离级别,能够有效避免数据的不一致性,但是这样会使得并发的各个线程挂起,因为只有一个线程可以操作数据,这样就会出现大量的线程挂起和恢复,导致系统缓慢。而后续的用户要得到系统响应就需要等待很长的时间,最终因为响应缓慢,而影响他们的忠诚度。

所以在现实中一般而言,选择隔离级别会以读写提交为主,它能够防止脏读,而不能避免不可重复读和幻读。为了克服数据不一致和性能问题,程序开发者还设计了乐观锁,甚至不再使用数据库而使用其他的手段。例如,使用 Redis作为数据载体,这些内容我们会在后续章节谈及。对于隔离级别,不同的数据库的支持也是不一样的。例如, Oracle只能支持读写提交和串行化,而 MYSQL则能够支持4种,对于 Oracle默认的隔离级别为读写提交, MYSQL则是可重复读,这些需要根据具体数据库来决定。

只要掌握了隔离级别的含义,使用隔离级别就很简单,只需要在@ Transactional配置对应即可,如代码清单6-14所示

 

上面的代码中我们使用了序列化的隔离级别来保证数据的一致性,这使它将阻塞其他的事务进行并发,所以它只能运用在那些低并发而又需要保证数据一致性的场景下。对于高并发下又要保证数据一致性的场景,则需要另行处理了。当然,有时候一个个地指定隔离级别会很不方便,因此 Spring Boot可以通过配置文件指定默认的隔离级别。例如,当我们需要把隔离级别设置为读写提交时,可以在 application. properties文件加入默认的配置,如代码清单6-15所示

代码中配置了 tomcat数据源的默认隔离级别,而注释的代码则是配置了DBCP2数据源的隔离级别,注释中已经说明了数字所代表的隔离级别,相信读者也有了比较清晰的认识,这里配置为2,说明将数据库的隔离级别默认为读写提交。

参考文献:《深入浅出Spring Boot 2.x》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值