MySQL-day4|青训营笔记

106 阅读11分钟

数据库事务

事务可以由一条SQL组成,也可以由一组复杂的SQL组成。

事务中的操作要么全部执行,要么全部不执行,这是事务的目的,也是其重要特征。

事务需要遵循ACID四个特性:

A(Atomicity),原子性。原子性是指整个数据库事务是不可分割的单位,只有整个事务中所有的数据库操作全部成功才算成功。事务中的任意一个数据库操作执行失败时,已经执行成功改的SQL操作也应该撤销,数据库应该回退到执行事务前的状态。

C(consistency),一致性。一致性事务是指事务开始前和事务开始后,数据库的完整性约束都没有被破坏。

I(isolation),隔离性。事务的隔离性是指在并发的环境中,事物之间的执行是互相隔离的,即每个事务都能独立地执行,一个事务的执行并不会影响另一个事务的结果。

D(durability),持久性。事务一旦提交,其结果就是永久性的,即使发生宕机,数据库也能将数据进行恢复。持久性保证了高可靠性。

事务有哪几种类型

扁平事务:最简单的事务由BEGIN 开始,由COMMIT或者ROLLBACK 结束,要么全部执行,要么全部回滚。

带有保存点的扁平事务:使用SAVEPOINT建立保存点,允许事务回滚到已经建立好的保存点。

链式事务:上一个事务的结束和下一个事务的开始合并为一个原子操作。

嵌套事务:由一个顶层的事务控制各个层次的子事务。子事务的提交并不会立马生效,必须等待父事务也提交才会生效。MySQL不支持嵌套事务,但是可以使用带有保存点的事务来模拟嵌套事务。

分布式事务:通常是指在分布式环境下运行的扁平事务。根据业务的不同需求可能会访问不同网络节点的数据库,也需要满足ACID的特性。

MySQL事务的ACID特性是如何实现的

atomicity 原子性

MySQL事务的原子性实现的关键在于,当事务发生回滚的时候,需要撤销所有的已经成功执行的SQL语句。InnoDB实现回滚依赖的是Undo Log,当事务对数据进行修改的时候,会生成对应的Undo log。如果事务执行失败或者调用了RollBack,都可以利用Undo Log将数据恢复到事务执行前的状态。

注:InnoDB的Undo log,索引,数据,全都存储在ibdata1这个文件中。而redo log 主要是存储在ib_logfile0和ib_logfile1两个文件中。

consistency 一致性

一致性是事务的追求的最终目标,要想让数据库的完整性约束不被破坏,那么原子性、持久性、隔离性缺一不可。

要实现一致性,主要从两个层面进行考虑:数据库层和应用层层面

在数据库层面:首先保证事务的原子性、持久性、和隔离性。其次数据库本身提供保障,比如不允许向整数列插入字符串。

应用层层面:设计合理的应用层逻辑,如果应用层逻辑不合理,那么一致性也就无法保证了。

durability 持久性

持久性实现原理:

InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,如果每次读写数据都需要磁盘IO,效率会很低。为此InnoDB提供了Buffer Pool。Buffer Pool是存在于内存中的,在进行读取的时候,首先会去Buffer中寻找,如果Buffer Pool中不存在该数据页,则将磁盘中的该数据页加载到Buffer pool中。后续如果需要对该数据进行修改,则会对Buffer Pool中的该数据页中的数据进行修改,然后将该数据页标记为脏页,并定时将Buffer Pool中的脏页刷新到磁盘中(刷盘操作)。

这存在一个问题:如果在输盘前数据库宕机了,脏数据就丢失了。为了解决这个问题InnoDB采用了日志优先写的机制,也就是说在数据写入到Buffer Pool前,先将数据修改的操作写入RedoLog中,当事务提交时,调用fsync接口进行刷盘。

既然Redo log依然也需要在事务提交时进行刷盘操作,为什么将脏数据记入redolog比直接将BufferPool中的脏数据页输盘要快呢?

如果不采用定时操作刷盘,而是将BufferPool中的数据实时刷盘,将会极大的影响性能。因为BufferPool中的脏数据有很大的可能存在于分散的页中,在刷盘时这将会进行随机IO,产生大量的无效IO,也就是说实时刷盘将会产生较大的性能消耗。

但是我们采用:实时的将脏数据记录实时写入RedoLog,定时对RedoLog进行刷盘操作,就可以在消耗性能较小的的同时,实现了数据的持久化。

Buffer Pool的实时刷盘是一个随机IO的过程,而将数据修改的操作写入RedoLog中的过程,以及读取RedoLog的过程都是顺序IO的过程。因此这种方案在保证数据持久化的前提下,同时也保证了性能。

isolation 隔离性

MySQL隔离性的实现原理:

隔离性追求的是,并发的情况下事物之间互不干扰。MySQL有两种方案实现事务的隔离性:锁、MVCC。

MySQL事务的隔离级别

读未提交、读已提交、可重复读、串行化

事务隔离是为了解决:脏写、脏读、不可重复读、幻读等问题,这些问题解决的难度依次增加。

这四个隔离级别全都解决了脏写的问题,读未提交没有解决脏读及以后的问题。读已提交解决了脏读问题,可重复读解决了不可重复读的问题,串行化解决了幻读的问题。

注:在InnoDB存储引擎中,不可重复读的隔离级别下,因为采用了MVCC技术以及NEXT KEY LOCK技术,在不可重复读的隔离级别下就已经解决了幻读的问题。

MVCC技术已经解决了幻读问题,为什么还要有NEXT KEY LOCK技术

NEXT KEY LOCK技术是什么?

若命中唯一索,退化为行锁。 NEXT KEY LOCK技术是针对索引记录进行当前读时防止幻读提出的。因为B+树中的索引记录是有序的,我们可以将索引记录换分成若干区间,通过NEXT key LOCK 可以对当前读的索引记录所在的区间和右边界行数据加锁,防止幻读的发生(左开右闭)。这种锁是GapLOCK和RecordLOCK的组合。

因为MVCC技术和NEXT key LOCK技术是用在不同读场景下的。MVCC只能用于快照读场景下,在当前读场景下若没有NEXT key LOCK技术对索引行记录增加右边界行锁和间隙锁,仍然会出现幻读。

各种隔离级别是怎么实现的?

read uncommitted: 最简单,因为它根本就不加锁。 serilizable: 采用串行化实现,读的时候加共享锁,可以并发读。写的时候加排它锁,不能并发读也不能并发写。

repeatable read和read committed采用MVCC实现,他们的区别在于readView生成的时间不同。

MySQL锁机制实现事务的隔离性

锁机制概述:在读写数据时,都需要先拿到相应的锁才可以进行后续的操作。在InnoDB中主要有共享锁、排它锁、意向共享锁、意向排它锁四种锁。其中共享锁和排它锁可以加在行数据上,而意向锁只可以加在表上。

锁升级? 意向锁解决了什么问题?

当行锁超过一定的阈值,会占用较多的资源,如果改用一个页锁,会在几乎不降低性能的条件下减少资源的占用。同样,当页锁数量超过一定的阈值,会升级为表锁。

  1. 在myIsam存储引擎中只存在表锁。
  2. InnoDB存储引擎采用位图来进行锁的管理,每个页都会维护一个位图,位图中的0和1代表该页的行数据是否上锁(0代表未上锁)。位图的管理方式决定了,对行锁进行管理时,只需要修改位图对应位置中的0、1值就可以了。因此行锁的数量增加时,占用的资源几乎不变,所以InnoDB不存在锁升级的过程。

意向锁可以在行级锁和页级锁并存的时候,提升锁的效率和并发性。比如:

当我们在加行级的共享锁或排它锁时,就已经添加了表级的意向锁,那么在需要添加表级的共享锁或者排它锁时,只需要检查该表级的共享锁、排它锁与意向锁是否兼容即可,这样可以快速的去判断是否有冲突。不需要再去检查表中的各种行级锁了。

MySQL的MVCC机制实现事务的隔离性

一个事务是写事务,一个事务是读事务,则可以不加锁,通过MVCC来保证事务的隔离性。

MVCC(多版本并发控制):

MVCC也是一种乐观锁。

MVCC技术的实现依赖于以下的技术:

隐藏列:InnoDB中每一个数据行都有其隐藏列,其中包含:本行数据的事务ID(生成本行数据的事务ID),指向UndoLog的指针(可以利用该Undo log将该数据恢复到上一个版本)。

基于UndoLog的版本链:因为数据有指向UndoLog的指针,且每条UndoLog都能指向更早的UndoLog,因此构成了一条基于UndoLog的版本链。依赖此版本链可以将数据恢复成任意历史版本。

ReadView:前面提到,根据基于UndoLog的版本链,可以将数据恢复成过去的任意版本。至于恢复到哪一个版本,也就是说哪一个版本是可见的,由ReadView来决定。比如在某一时刻打快照,生成该时刻的ReadView,在之后的查询操作中,将读取到的数据的事务ID,与这个ReadView中的数据结构利用可见性算法进行比对,从而决定可见的数据版本。

上面提到的ReadView中的数据结构是指:ReadView内部维护了一个生成快照时刻的未提交事务的ID列表,并记录了最小未提交事务ID和将要生成的新事务ID。

InnoDB存储引擎中不管是RR还是RC隔离级别,都是使用MVCC实现的,区别在哪里

RC隔离级别也使用了MVCC技术,但是RC隔离级别下之所以没有做到可重复读,是因为生成快照(ReadView)的时机问题。在RR隔离级别下,只有在事务的第一次快照读时会生成ReadView,在这个事务的生命周期中不会再生成第二个ReadView了,之后的快照读都不会再生成新的ReadView了,都是利用这个ReadView和数据的事务ID进行比对来进行数据可见性判断。而RC隔离级别下,每次快照读操作都会生成当前时刻的ReadView,用此ReadView和数据的事务ID进行比对来进行数据可见性判断。如果当前数据是不可见的,那就利用基于Undo Log的版本链将数据恢复到上一个版本,直到数据是可见的。

事务如何回滚

  1. 直接ROLLBACK
  2. ROLLBACK至SAVEPOIONT

数据库死锁

当两个事务互相等待,就会产生死锁。

死锁是什么:

比如事务A和B都有两句SQL,事务A的第一句SQL和事务B的第一句SQL一起执行,并且这两句SQL都对对应的行数据加上排它锁。事务A的第二句SQL和事务B的第一句SQL一致,事务B的第二句SQL和事务A的第一句SQL一致,这样出现了互相等待锁资源,而彼此都不释放的问题。这就是死锁。

死锁怎么解决:

主动解决:

可以通过等待图来排查死锁的情况,等待图中如果出现了回路,那就说明出现了死锁现象。一般来讲InnoDB会选择回滚Undo量最小的事务。

被动解决:

对等待时间设定一个阈值,当等待时间超过了这个阈值,回滚其中的一个事务,一般选择回滚Undo量较少的那个事务。