事务
1 什么是数据库事务?
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。
假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明
的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。
所以事务就是保证这两个关键操作要么都成功,要么都要失败。
2 事物的四大特性(ACID)介绍一下? 以及对应的实现原理说下
四大特性也就是原子性,一致性,隔离性,持久性
首先我们看原子性:说白了就是当前事务的操作要么同时成功,要么同时失败,而原子性其实是由undo log日志来保证的
(这里简单介绍下undo log,undo log他其实是一种用于撤销回退的日志,也就是说在事务没提交之前 mysql会先记录更新前的数据到undo log日志文件中,当事务回滚时,可以用undo log来进行数据回滚,其实说白了undolog就是用执行的反向sql语句来进行回滚)
接着看隔离性:隔离性其实就是事务在并发执行过程中,不能互相干扰,而隔离性其实是由mysql中的各种锁以及mvcc机制实现的
(之后会具体介绍mvcc等机制)
再看持久性:持久性简单的理解就是一旦你提交了事务,那么它对于数据库的改变就是持久的,而持久性其实是由redo log日志来保证的
(这里也简单的说下redo log日志,首先 我们要知道当我们修改某条记录的时候其实并不是立刻刷入到磁盘的,而是会写buffer pool并将buffer pool标记为脏页,与此同时将本次对页的修改以redo log的形式记录下来,而后续innodb会在合适的时候通过后台线程将缓存再buffer pool中的脏页刷入到磁盘,这其实就是WAL技术
说白了WAL 就是mysql的写操作并不是立刻写到磁盘,而是先写文件,然后再合适的时间在写磁盘,
而正是因为有redo log的存在,即便系统崩溃了,还没来得及持久化脏页数据,mysql在重启后,会根据redo log的内容,将所有数据恢复到最新的状态。)
一致性:使用事务的最终目的,而一致性其实是由原子性,隔离性,持久性,这三个特性,以及业务代码正确逻辑来保证的
为啥说要保证正确逻辑呢?举个例子 比如说现在你先下单,在扣减库存,你下单成功了,但是扣减库存报错了,而你在try中执行的业务逻辑,catch中捕获时只是输出了日志并没有抛出异常,这就是导致了问题,即下单成功了,但是扣减库存失败了,所以说还需要保证逻辑的正确性
3 什么是脏读?幻读?不可重复读?
脏读:就是一个事务读取到了另外一个未提交事务的修改过的数据,就意味着发生了脏读,
简单解释下 就是比如事务A更新了一份数据,但是没有提交,而与此同时事务B也来读取相同的数据,因为事务A没有提交所以他随时是可以回滚的,一旦事务A发生回滚,那么事务B所读取的数据就是过期数据,这种现象就是脏读;
不可重复读:在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了不可重复读现象
而发生不可重复读可能是因为两次查询过程中间插入了事务更新原有数据的操作;
比如说现在有两个事务A和B,事务A先读取数据,之后继续执行逻辑处理,而在这过程中事务B更新了这条数据并提交了事务,那么当事务A再次读取数据的时候,就会发现前后两次读取到的数据不一致性
幻读:在一个事务的两次查询中,数据的记录条数不一致,这就意味着幻读
简单说下 比如说现在有事务A和B,他们同时在处理,一开始事务A进行范围查询,查询的记录条数为3条,同时事务B也按相同的查询条件去查,也查出了3条记录,紧接着,事务A在这个范围中插入了一条记录,并提交了事务,那么此时数据库的条数就变成了4条,而事务B再次去查的时候就是4条记录,就和之前查询的记录数量不一致了,这种现象就是幻读
4 那什么又是隔离级别,有哪几种隔离级别呢?
首先我们要知道 正是因为事务在并发执行的过程中可能会遇到上面的各种问题–即脏读,幻读,不可重复读,也就是会对事务的一致性产生不同程度的影响,所以才引入隔离级别来去解决这些问题
有哪几种隔离级别-有读未提交,读已提交,可重复读,串行化四种隔离级别,隔离级别是由低到高,同时隔离级别越高,性能也就越差
读未提交:最低的隔离级别,也就是允许读取尚未提交的数据变更,可能会发生脏读,幻读或者不可重复读
读已提交:见名知义-即允许读取并发事务中已经提交的数据,可能会发生幻读或者不可重复读
可重复读:对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
串行化: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是性能最差
这里需要注意的是:
Mysql 默认采用的 REPEATABLE_READ隔离级别
Oracle 默认采用的 READ_COMMITTED隔离级别
事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多
版本并发控制),通过undo log版本链和readview机制来支持并发一致性读和回滚等特
性。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是
READ-COMMITTED(读取提交内容):,
但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)并不会有任何性能损失。
InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行
化)**隔离级别。
5 MVCC机制了解吗?
首先我们要理解mvcc多版本并发控制,它其实主要是为了解决在RC,RR这种隔离级别下,读和写并发冲突的问题
这里的多版本就是多个undo日志版本链,
只要你对数据做了修改,不管你有没有提交 它都会加入到版本链中去,每条记录-id 都有自己的版本链
ReadView 是针对当前事务的,RR隔离级别下也就是说当你开启一个事务,进行查询的时候
它在那一刻查询到的readview 和你之后查询到的都是一样的,
注意你执行了更新操作,那其实是执行了当前读,之后这个视图再去查询,查询到的这条记录都是你更新后的。 但是注意readview并没有变,如果你查询其他的记录,还是需要拿着readview根据可见性视图算法规则去查找
而RC隔离级别是每一次查询都会查到当前最新的readview视图,都取了当前事务活跃事务和未活跃事务的状态
在可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束之前永远都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成read-view),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。
版本链比对规则:
- 如果 row 的 trx_id 落在绿色部分( trx_id<min_id ),表示这个版本是已提交的事务生成的,这个数据是可见的;
- 如果 row 的 trx_id 落在红色部分( trx_id>max_id ),表示这个版本是由将来启动的事务生成的,是不可见的(若 row 的 trx_id 就是当前自己的事务是可见的);
- 如果 row 的 trx_id 落在黄色部分(min_id <=trx_id<= max_id),那就包括两种情况
a. 若 row 的 trx_id 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的事务是可见的);
b. 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。
所以RR隔离级别和RC隔离都是适用上述的可见性算法规则,区别只是查询到的readView视图不同
6 为什么MySQL默认的RepeatableRead隔离级别,被改成了RC?(大厂面试题)
随着我们上面的讲解知道随着隔离级别的提高,性能也是不断降低的
所以也就不难理解为啥RR隔离级别被改成了RC,说白了也就是为了提高mysql吞吐量,并发量,允许短时间内数据实现不可重复读,幻读
因为我们要知道在互联网公司中存在高并发,尤其是每年的双十一,618这种活动,订单的峰值量可以达到百万笔/s. 无论是超高并发的读还是超高并发的写场景,mysql的RR隔离级别都有一定的性能损耗
RR在高并发写场景的性能损耗
在 MySQL 中,有三种类型的锁,分别是Record Lock、Gap Lock和 Next-Key Lock。
- Record Lock表示记录锁,锁的是索引记录。
- Gap Lock是间隙锁,锁的是索引记录之间的间隙。
- Next-Key Lock是Record Lock和Gap Lock的组合,同时锁索引记录和间隙。他的范围是左开右闭的。
而在RC隔离级别中只会对索引增加record lock 记录锁,不会添加Gap lock 和 next-key lock
在 RR 中,为了解决幻读的问题,在支持Record Lock的同时,还支持Gap Lock和Next-Key Lock;
那么间隙锁的触发条件又是什么呢?
事务隔离级别为RR。因为间隙锁只有在事务隔离级别RR中才会产生,隔离级别级别是RC的话,间隙锁将会失效
显式加锁。比如使用了类似于select…for update这样的加锁语句
查询条件必须有索引:
(1)若查询条件走唯一索引:只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙 锁;如果查询单条存在的记录,不会产生间隙锁
(2)若查询条件走普通索引:不管是锁住单条,还是多条记录,都会产生间隙锁
再简单解释下 间隙锁它其实属于排他锁,它在加锁的时候其实会锁住某一段间隙,防止其他事务在这个间隙内插入,删除和修改数据,这样一来,在高并发的环境下,必然会带来一定的性能损耗问题,并且最终锁影响的范围可能远远超过我们想要的操作的数据
那么既然间隙锁会影响性能,又为啥用它呢?还不是为了解决多次查询过程中的幻读问题,但是话又说回来,在真实的高并发环境下,我们必然是会尽量减少同一条sql的多次查询,比如说可以通过redis,es等分布式缓存组件来避免多次查询,没有了多次查询,其实也就意味着没有幻读的问题
所以最后总结下:如果实际业务场景中,如果无需锁住数据间隙,建议关闭间隙锁,或者将MySQL隔离级别由RR改为RC,否则会带来无谓的性能开销,甚至会引发死锁,影响业务运行。
RR在高并发读场景的性能损耗
为了提升性能,RR与RC下的普通读都是快照读,这里提到的普通读, 是指除了如下2种之外的select都是普通读
select * from table where ... for update;
select * from table where ... lock in share mode;
因此其实大部分的select都是普通读-即快照读;
这里所谓的快照读 通过上面的学习已经知道其实就是读取数据的历史版本,
RR与RC下的普通读虽然都是快照读,但两者的快照读有所不同:文章来源:https://www.toymoban.com/news/detail-823258.html
- RC下,事务内每次都是读最新版本的快照数据
- RR下,事务内每次都是读同一版本的快照数据(即首次read时的版本)
说人话 就是在RR隔离级别下事务会以第一次读的数据版本为准,而这个事务后续其他的普通读其实都是读取的该份快照数据,就是在一个事务内都是同一份快照读,这样一来,mysql在维护同一版本的快照数据,就需要额外的资源损耗,含计算损耗,内存损耗
但在现实中,一个事务里重复同一条sql再次查询的场景极低,且出于性能的考虑,一般也会尽量避免在同一事务内对同一数据进行多次查询。 因此,RR下所谓的事务内同一份快照读意义并不大。
最后总结:
MySQL 默认隔离级别是RR,为什么阿里等大厂会改成RC,主要是出于性能考虑。
RR-可重复读隔离级别,会带来很大的性能损耗
无论是超高并发读的场景,还是超高并发写的场景,带来了一些的性能损耗。
另外,通过程序手段,去规避幻读、可重复读的问题。文章来源地址https://www.toymoban.com/news/detail-823258.html
到了这里,关于MYSQL篇--事务机制高频面试题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!