数据库面试题之事务

什么是MySQL事务?

简单说,事务就是一组原子性的SQL执行单元,如果数据库引擎能够成功地对数据库应用该组査询的全部语句,那么就执行该组SQL,如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行。

总结就是:要么全部执行成功(commit)要么全部执行失败(rollback)

例如:银行转账的例子
	有两张表:信用卡(credit)表和储蓄(savings)表,用户aismall要把信用卡里最后100块钱额度转到他的储蓄账户,那么需要至少三个步骤:
		检査信用卡余额是否髙于100块钱。
		从信用卡账户余额中减去100块钱。
		在储蓄账户余额中增加100块钱。	
	上述三个步骤必须在同一个事务中执行,任何一个SQL失败,则必须回滚所有的SQL。

这里用START TRANSACTION语句开启事务,要么使用COMMIT提交事务将修改的数据持久保留,要么使用ROLLBACK销所有的修改,事务SQL的样本如下:

-- 1、开启事务
START TRANSACTION;
-- 2、检查信用卡账户额度
SELECT balance FROM credit WHERE customer_id = 'aismall';
-- 3、信用卡表扣钱
UPDATE credit SET balance = balance - 100.00 WHERE customer_id = 'aismall';
-- 4、储蓄表加钱
UPDATE savings SET balance = balance + 100.00 WHERE customer_id = 'aismall';
-- 5、提交事务
COMMIT;

事务的四大特性是什么?

四大特性即ACID特性:原子性一致性隔离性持久性

原子性(Atomicity)

  • 单个事务,为一个不可分割的最小工作单元,整个事务中的所有操作要么全部commit成功,要么全部失败rollback,对于一个事务来说,不可能只执行其中的一部分SQL操作,这就是事务的原子性

一致性(Consistency)

  • 数据库总是从一个一致性的状态转换到另外一个一致性的状态。

  • 在前面的例子中, 一致性确保了,即使在执行第三、四条语句之间时系统崩潰,信用卡账户也不会损失100块,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中,保证数据一致性。

隔离性(Isolation)

  • 通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。

  • 在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外一个账户查询余额SQL开始运行,则其看到的信用卡账户的余额并没有被减去100元。

持久性(Durability)

  • 一旦事务提交,则其所做的修改就会永久保存到数据库中,此时即使系统崩溃,修改的数据也不会丢失。

事务的ACID特性可以确保银行不会弄丢你的钱。而在应用逻辑中,要实现这一点非常难, 甚至可以说是不可能完成的任务,一个兼容ACID的数据库系统,需要做很多复杂但可能用户并没有觉察到的工作,才能确保ACID的实现。

事务的ACID特性具体是怎么实现的?

对MySQL来说:逻辑备份日志(binlog)重做日志(redolog)回滚日志(undolog)锁技术 + MVCC就是MySQL实现事务的基础。

  • 原子性:通过undolog来实现。
  • 持久性:通过binlog、redolog来实现。
  • 隔离性:通过(读写锁+MVCC)来实现。
  • 一致性:MySQL通过原子性,持久性,隔离性最终实现(或者说定义)数据一致性。

1、原子性原理

事务通常是以BEGIN TRANSACTION 开始,以 COMMIT 或 ROLLBACK 结束。

  • COMMIT 表示提交:即提交事务的所有操作并持久化到数据库中。
  • ROLLBACK表示回滚:即在事务中运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库所有已完成的操作全部撤销,回滚到事务开始时的状态,这里的操作指对数据库的更新操作,已执行的查询操作不用管,这时候也就需要用到 undolog(一种日志) 来进行回滚。
undolog:
	每条数据变更(INSERT/UPDATE/DELETE/REPLACE)等操作都会生成一条undolog记录,在SQL执行前先于数据持久化到磁盘。
	当事务需要回滚时,MySQL会根据回滚日志对事务中已执行的SQL做逆向操作,比如 DELETE 掉一行数据的逆向操作就是再把这行数据 
	INSERT回去,其他操作同理。

2、持久性原理

先了解一下MySQL的数据存储机制,MySQL的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘 IO,然而即使是使用 SSD 磁盘 IO 也是非常消耗性能的。为此,为了提升性能 InnoDB 提供了缓冲池(Buffer Pool),Buffer Pool 中包含了磁盘数据页的映射,可以当做缓存来使用:

  • 读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
  • 写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;

我们知道,MySQL表数据是持久化到磁盘中的,但如果所有操作都去操作磁盘,等并发上来了,那处理速度谁都吃不消,因此引入了缓冲池(Buffer Pool)的概念,Buffer Pool 中包含了磁盘中部分数据页的映射,可以当做缓存来用;这样当修改表数据时,我们把操作记录先写到Buffer Pool中,并标记事务已完成,等MySQL空闲时,再把更新操作持久化到磁盘里(你可能会问,到底什么时候执行持久化呢?1、MySQL线程低于高水位;2、当有其他查询、更新语句操作该数据页时),从而大大缓解了MySQL并发压力。

但是它也带来了新的问题,当MySQL系统宕机,断电时Buffer Pool数据不就丢了?

因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。

于是 redolog + binlog的经典组合就登场了。

3、隔离性原理

隔离性是事务ACID特性里最复杂的一个。在SQL标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。

Mysql 隔离级别有以下四种(级别由低到高):

隔离级别效果
读未提交(READ UNCOMMITTED)一个事务还没提交时,它做的变更就能被别的事务看到。(别的事务指同一时间进行的增删改查操作)
读提交(READ COMMITTED)一个事务提交(commit)之后,它做的变更才会被其他事务看到。
可重复读(REPETABLE READ)一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
串行(xíng)化(SERIALIZABLE)正如物理书上写的,串行是单线路,顾名思义在MySQL中同一时刻只允许单个事务执行,写会加写锁读会加读锁。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

搞懂了隔离级别以及实现原理其实就可以理解ACID里的隔离性了。

前面说过原子性隔离性持久性的目的都是为了要做到一致性

但隔离型跟其他两个有所区别,原子性和持久性是为了要实现数据的正确、可用,比如要做到宕机后的恢复、事务的回滚等,保证数据是正确可用的!

那么隔离性是要做到什么呢?

  • 隔离性要管理的是:多个并发读写请求(事务)过来时的执行顺序。当并发处理多个DML更新操作时,如何让事务操作他该看到的数据,出现多个事务处理同一条数据时,让事务该排队的排队,别插队捣乱,保证数据和事务的相对隔离,这就是隔离性要干的事儿。
    所以,从隔离性的实现原理上,我们可以看出这是一场数据的可靠性性能之间的权衡。

4、一致性原理

一致性,我们要保障的是数据一致性,数据库中的增删改操作,使数据库不断从一个一致性的状态转移到另一个一致性的状态。

事务该回滚的回滚,该提交的提交,提交后该持久化磁盘的持久化磁盘,该写缓冲池的写缓冲池+写日志,对于数据可见性,通过四种隔离级别进行控制,使得库表中的有效数据范围可控,保证业务数据的正确性的前提下,进而提高并发程度,支撑服务高QPS的稳定运行,保证数据的一致性,这就是咱们叨叨叨说的清楚想不明白的数据库ACID四大特性。

并发场景下事务会存在哪些数据问题?

并发场景下MySQL事务可能会出现脏读不可重复读幻读问题;

  • 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。

    一个事务对数据做出的更改未提交,其他事务就可读。

  • 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新了原有的数据。

    一个事务对数据做出更改提交之后,其他事务可读。

  • 幻读(Phantom Read):在一个事务的两次查询中数据条数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

    一个事务对数据做出更新并提交不会产生幻读,但是如果对数据进行出入操作并提交,其他事务可读

InnoDB存储引擎是如何解决幻读问题的?

先说结论,MySQL 存储引擎 InnoDB 在可重复读(RR)隔离级别下是解决了幻读问题的。

方法是通过:next-key lock在当前读事务开启时

​ 1.给涉及到的行加写锁(行锁)防止写操作

​ 2.给涉及到的行两端加间隙锁(Gap Lock)防止新增行写入

从而解决了幻读问题。

幻读出现的场景:

幻读出现在可重复读(RR)隔离级别下:

​ 普通的SELECT查询就是快照读,是不会看到别的事务插入的数据的。因此,幻读在当前读下才会出现。(当前读会生成行锁,但行锁只能锁定存在的行,针对新插入的操作没有限定

假如session B 的修改结果,被 session A 之后的 select 语句用当前读看到,不能称为幻读,幻读仅专指新插入的行

因为这三个查询都是加了 for update,都是当前读。而当前读的规则,就是要能读到所有已经提交的记录的最新值。并且,session B 和 sessionC 的两条语句,执行后就会提交,所以 Q2 和 Q3 就是应该看到这两个事务的操作效果,而且也看到了,这跟事务的可见性规则并不矛盾。

为什么可重复读级别解决不了幻读?

因为幻读能仅通过行锁解决是解决不了的

在可重复读级别中,该sql第一次读取到数据后,就将这些数据加锁(悲观锁),其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。

需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。

所以要解决幻读,就必须得解决新增行的问题。

行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的间隙。因此,为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。顾名思义,间隙锁,锁的就是两个值之间的空隙。

MySQL将行锁 + 间隙锁组合统称为 next-key lock,通过 next-key lock 解决了幻读问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彤彤的小跟班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值