【Java.mysql】——深度理解MYSQL事务 轻松应对面试问题

目录

🚩为什么使用事务

🚩 事务的概念

🚩事务的使用

🚩事务的特性

🎈隔离性 

🎓隔离级别

🔴.脏读问题

🔴不可重复读

 🔴幻读

🎓修改数据隔离级别 

🚩总结事务


🚩为什么使用事务

事务是一个经典的面试问题。

转账~的时候。

account(name ,balance)

             张三     1000

             李四      1000

张三给李四转账500,把张三的账户给-500,李四的账户给+500,如果执行一半,程序崩溃/数据库崩溃/机器断电了.....

此时数据就会出现“不上不下”的中间状态,非常明显的bug。(要想俩者都进行,那么就要引入事务来给俩者操作进行绑定,要么张三转账给李四成功,李四也接收到了,要么张三转账给李四成功了但是接收的时候突然断电,就直接重新执行了)

所以我们引入事务,就是为了避免上述的问题。事务就可以把多个sql打包成一个整体,可以保证这些sql要么全都执行正确,要么就“一个都不执行”~这里的“一个都不执行”不是真的一个都不执行,是必须得执行,才能知道失败,只是看起来好像“一个都不执行”,如果中间有某个执行不正确,我们就得重新操作,这个操作在数据库中,称为“回滚”(rollback).

解决方案:使用事务来控制,保证以上两句SQL要么全部执行成功,要么全部执行失败。

回滚是怎么做到的?

——日志的方式,记录事务中的关键操作,这样的记录就是回滚的依据

日志就是打印出来的内容,即使是主机掉电,也不影响(回滚用的日志已经放在文件中了)

一旦重新启动主机,mysql也重新启动 ,就会发现回滚日志中有一些需要进行回滚的操作,于是就可以完成这里的回滚了。 (回滚到意外之前的状态)

🚩 事务的概念

事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。
在不同的环境中,都可以有事务。对应在数据库中,就是数据库事务。
(比如成功需要两个方面都要执行正确,如果一方失败那么前功尽弃,如果都成功才是真正的成功)

🚩事务的使用

  • start transaction 开启事务 单独执行的每个sql,都是自成一个体系,此时这些sql之间没有原子性的,执行各种sql
  • /commit  事务结束了
  • rollback 主动触发回滚

🚩事务的特性

事务把这多个sql打包到一起,作为一个整体来执行,这样的特点,称为“原子性”。

  • 原子性:回滚的方式,保证这一系列操作,要么都能执行正确,或者恢复如初(就是统一成一个整体)
  • 一致性:事务执行之前和之后,数据都不能改变(很多时候都要靠数据库的约束以及一系列的检查机制来完成的)
  • 持久性:事务做出的修改,都是在硬盘上持久保存的,重启服务器,数据仍然存在,事务执行的修改仍然是有效的。(我们在命令行中创建数据库创建表增加的数据在退出后又登录都是存在的,因为都是在硬盘上持久保存的)

🎈隔离性 

数据库并发执行多个事务的时候,涉及到的问题。

并发执行其实是很常见的事情,mysql是一个客户端服务器结构的程序,一个服务器可以给多个客户端提供服务,多个客户端都会让数据库执行事务。很有可能,客户端1提交事务1,执行了一半,客户端2提交的事务2也过来了,数据库服务器就需要同时处理两个事务,并发执行。

就比如我去餐馆点了三个菜,另一个同学,也在餐馆,点了2个菜,很多小餐馆只有一个锅,只能抄一个菜,效率是很低的,如果有多个锅同时炒菜,那么效率就高了。并发程度越高,整体的效率就越高。

数据库并发执行多个事务的时候,涉及到的问题。

如果我们希望数据库服务器执行效率高,就希望提高并发程度,但是提高了并发程度之后,可能会存在一些问题,导致数据就出现一些“错误”的情况。

我们可以想到,在做任何事情的时候,我们都必须考虑到 “数据正确” 和 “效率” 之间做平衡。

就像我们写博客一样,如果我们一味的去追求效率,2个小时完成一个博客,那么你掌握了多少呢?你写的知识点是否是正确呢?如果我们花5个小时完成一个博客,虽然效率低了,但是我们掌握了肯定大于2小时完成的,我们的正确性大大提高。


🎓隔离级别

隔离级别,就是在“数据正确”和“效率”之间做权衡。往往提高了效率,就会牺牲正确性,提升了正确性就会牺牲效率。所以在并发事务的时候,会出现什么问题呢?

🔴.脏读问题

相当于我是事务A,另外一个同学是事务B,事务A在敲代码,事务B在读代码,等到事务B有事出去一下,此时事务A正在修改代码,等事务B来的时候,这个曾经它看到的代码已经不是曾经的代码,已经被修改了,已经过时/无效的数据(就称为脏读)

(一个事务A正在写数据的过程中,另一个事务B读取了同一个数据,接下来事务A又修改了数据,导致B之前读到的数据,是一个无效的数据/过时的数据——脏读)

如果这两个事务一个接一个的串行执行的话,那就没事,因为事务A等全部写完代码了之后,事务B进行读代码,此时事务B读到的代码就不是一个过时的数据了,因为事务A不会在其中间修改了。

解决脏读问题,核心思路,是针对写操作加锁(我和同学约定好,我写代码的时候,你不要来读,等到我确定写完了,提交到码云上,你从我的码云上来看)

此时 并发性降低了(对写加锁了),隔离性提高了,效率降低了,数据准确性提高了


🔴不可重复读

此时事务A在写代码的时候,和同学约定了写加锁(就是我写的时候不要看我的代码。等在码云上看我提交后的代码)

我写代码,提交给码云,此时,大家开始看代码,看的过程中,我想把代码再优化一下~~(我修改的过程中,同学感知不到),但是我修改完了之后,再次提交,同学们就能看到,码云的代码突然就变了。

并发执行事务过程中,如果事务A在内部多次读取同一个数据的时候,出现不同的情况,这种就是不可重复读,事务A在两次读的之间,有一个事务B修改了数据并提交了事务。

解决不可重复读,需要给读操作加锁 (也就是约定同学读的时候,我也不能写)

并发程度又进一步降低了(写加锁读加锁),隔离性也进一步提高了,效率降低了,数据的准确性又提高了 

如果两个事务之间的影响越大,隔离性就越低,影响越小,隔离性越高。 


 🔴幻读

我们上述约定了读加锁和写加锁。事务A在写代码的时候事务B不能读代码,事务A写完代码后提交到码云上之后,事务B读代码的时候,事务A不能修改代码。(这就是给写加锁,读加锁)。

但是上述情况是两个事务针对同一个数据进行读写。

但是事务B在读的时候,事务A是不能修改文件A,但是我可以写另一个文件B,并不影响事务A写一个数据的代码,此时等事务A写完文件B之后,提交给码云上后,此时码云上蹦出来一个B代码,一个事务A执行的过程中,两次的读取操作,数据内容虽然没有改变,但是结果集变了,这种称为“幻读”

 解决幻读问题,需要引入串行化的方式。保持绝对的串行执行事务,此时就是完全没有并发了。


根本上解决了并发中涉及到的各个问题,此时,并发程度最低(没并发),隔离性最高的,效率是最低的,数据是最准确的。 

效率和正确性 在不同的需求场景就有不同的要求,mysql服务器也提供了“隔离级别”让我们针对隔离程度进行设置应付不同的需求场景情况。有舍才有得,就像我们舍弃了玩耍得时间,来提升技术,那么我们随着持久的坚持,我们会在技术上得到大幅度的提升。

 有的场景追求正确性,效率是其次,有的场景追求效率,正确性是其次。

充值,转账等一系列和钱相关的场景,宁愿牺牲效率,也一定要保证准确性。短视频,点赞投币,转发,评论。。。这些数据,就不要求那么准确了,点赞10w和点赞在10w零1有什么区别嘛。

不同的业务场景,关注的点不一样,使用的数据库是一样的,有的时候,希望效率最高,有的时候希望准确性最高。

mysql提供了“隔离级别”概念,可以直接在mysql配置文件中,修改数据库的隔离级别。 


🎓修改数据隔离级别 

read uncommitted(读未提交)——并发程度最高,速度最快,隔离性最低,准确性最低。

(读未提交的代码块,就是会导致如果未提交的代码又修改了,那么就会出现脏读问题)

脏读

read committed(读已提交)——引入写加锁,只能读写完之后提交的版本,并发程度降低 了,速度变慢了,隔离性提高了,准确性提高了。

不可重复读   (默认隔离级别是可重复读,保证写加锁读加锁

repeatable read(可重复读) 引入写加锁和读加锁,写的时候不能读,读的时候不能写,并发程度又进一步的降低了,速度降低了,隔离性提高了,准确性提高了。

幻读

serializable(串行化) 严格的按照 串行 的方式,一个一个的执行事务,并发程度最低(没有并发),速度最低,隔离性最高,准确性最高。


🚩总结事务

当面试的时候问你如何理解事务?我们就要按照上述的逻辑分析一遍。

从最开始的我们在转账的时候,张三给李四转500,那么张三账户-500,李四账户+500,但是突然死机了,如果中间突然死机了,此时数据就会出现“不上不下”的中间状态,非常明显的bug。此时我们就需要事务将这两个操作捆绑起来要么都执行,要么都不执行。这也体现了事务的原子性。

中间每个sql操作执行不正确,重新操作,此时就需要回滚,回滚就是利用日志的方式进行还原故障之前的状态,即使主机掉电了,当我们重启主机,也就相当于重启mysql了,然后触发回滚操作,通过日志还原原先的数据。

除了原子性,我们还有持久性,数据库操作的数据都会存储在硬盘中永久保存,下次再打开的时候依旧是保存的,一致性,事务执行之前和之后,数据都是保持一致的。

接下来就是隔离性,隔离性是在多个事务并发执行的时候,并发执行是一个很常见的问题,一个mysql服务器要处理客户端的数据,如果多个客户端进行处理的时候,同时发送事务给服务器,那么就是并发执行,并发执行此时会出现几个问题。

1.当事务A进行写数据的时候,事务B也在看数据,但是事务B有事去了,此时事务A发现数据有点不对就进行了修改,事务B在看代码的时候,数据是已经修改过后的/过时的了,就会出现脏读现象,那么解决的方法就是给写加锁,在写完之后提交给码云之后确定不修改了,此时事务B开始读,这就导致了并发性降低,隔离性提高,正确性提高,速度降低了。

2.此时又会出现一个问题,我们给写加锁了之后,事务B读了数据时,事务A又对该数据进行修改,此时事务B就会读到两个相同数据的两个版本,这就导致了不可重复读的问题,(就像你去买水果,事务A去买水果3元一斤,事务A下次再去买水果的时候是10元一斤,这样就说不过去了,如果事务A去买水果3元一斤,事务B去买水果的时候是10元一斤,其实这样也是可以的)要解决不可重复读,我们就需要给读加锁,再事务B读数据的时候,事务A是不能修改的,此时并发性降低,隔离性大大提高,准确性提高,速度降低了。

3.进一步的我们对写加锁,对读加锁,但是我们在这基础上其实还是有点问题的,事务A在写数据A的时候事务B不能读,事务B在读数据A的时候事务A不能写数据,但是我们并不妨碍,事务A在读的时候,事务B去写数据B(不同于数据A的文件),那么此时事务A在码云上就会收到两个文件,会出现幻读问题,解决这个问题,就必须要串行化处理了,此时就没有并发执行,隔离性最高,准确性最高,速度是最低的。

我们可以看到,如果想要执行效率高,那么就会出现几个问题,最终串行化处理,效率低了,但是准确性提高了。引入隔离级别,就是在“效率”和“正确性”上进行平衡选择,让我们针对隔离程度进行设置对应不同的需求场景情况,read nocommitted读未提交,指的是,读的时候并没有提交代码,这就会导致了脏读问题,此时就给级别调成read committed(读已提交)就是给写加锁,此时写完了才能读,但是同样会出现一个问题就是,在读的时候,如果又想修改了,此时读到的数据前后不一致,我们就要解决这个问题,就要调成repeatable read(可重复读) 引入读加锁,此时写的时候不能读,读的时候不能写,此时又可能会出现问题,当事务B读数据A的时候,事务A也可以写别的数据B,这不影响读加锁和写加锁。此时就需要 serializable(可串行化) 使严格的按照串行的形式,一个一个的执行事务,并发程度最低,隔离性最高。


要祝她铮铮,祝她昂扬!

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值