事务持久性与原子性的保障

持久性的意义

所谓持久性就是说在事务成功提交了之后,数据的最终变更操作都会持久化到磁盘上去,不会因为故障导致数据丢失。而在计算机系统里,只有数据保存到了磁盘才能保证数据不会丢失,持久化的问题是所有数据库系统都需要面临的,无法保证持久性的数据库就意味着在数据库、系统、服务器发生故障时无法进行数据恢复。

持久性的难题

保证持久性的意义在于,就算遇到数据库、系统、服务器崩溃,我们依然能对已经损坏的数据进行恢复,用习惯了ACID的数据库,我们潜意识里就以为持久化是自然而然的事情,然而并非这样,我们经典的MyIsam 就不支持这个特性,至于为什么,因为保证持久化的操作本身是有难题需要攻克的,这个难题在于把数据写入磁盘的操作过程并不是原子性的。

因为一次事务会牵扯到多次调用磁盘写数据的操作,而多次磁盘操作相互是独立的,所以一个事务是无法保障多次磁盘读写的原子操作的。这也就意味着,如果事务提交的时候如果发生故障、崩溃,就有可能部分写磁盘操作调用成功,部分写操作调用失败的情况,从而导致产生了脏数据。

除了原子性问题之外,为了提升IO的效率, 数据库是允许异步写入数据到磁盘的,数据写入通常会先写内存、再写磁盘。 为了优化写入数据的性能,通常应用也会在内存中加入各种缓冲区, 所以数据最终什么时候写入到磁盘 这个时间是根据具体的策略来决定,简单来说就是应用程序写入数据成功了,但是并不一定代表数据已经持久化到磁盘了。

所以综合来看持久化的关键问题在于两方面,:

一方面,应用程序的一次业务操作会牵扯到多次调用磁盘写,而多次写入磁盘的操作并不具备原子性,所以就也会存在部分数据写入成功,部分数据写入失败的情况。

另一方面,数据从内存写入到磁盘的整个过程可以异步进行的,所以导致从程序上来看数据写入成功了,但是数据并不一定已经持久化到磁盘了。

redo log

上面我们已经了解到 如果提交事务的过程中发生崩溃、宕机,就有可能会导致数据丢失和损坏,那么如何解决这个问题就在于,在数据丢失和损坏之后我们能通过什么方式对数据进行恢复。

回想到现实世界里,有时候我们在做某件事情的时候,偶尔会遇到脑子突然断片或失忆的情况(好比应用崩溃、服务器宕机),然后断片了之后,回想不起来断片之前自己在做什么,也不知道事情做到了哪一步,而这个时候我们往往会想着要是在做事情之前把这些记录在笔记本里面就好了,按着记事本里面的记录就知道自己应该做什么事情,还有哪些事情没有做了,然后把没做的事情继续做完就行了。

在现实世界我们用记事本的方式解决脑子失忆、断片的问题,在计算机里我们用记日志的方式来解决数据崩溃恢复问题,而Mysql里这个日志就是redo log。 redo log的功能就是事务提交之前首先会把本次事务需要变更的数据,包括修改磁盘那块扇区、修改哪些数据等,通通都记录下来。所以只要保证成功记录了redo log ,那么就算事务提交之后发生了崩溃、宕机,此时内存的数据即便没有即时写入磁盘,又或则只写入了一部分数据到磁盘,我们只需要通过找到事务对应的redo log,再对数据进行对比恢复即可。

加入redo log后,事务执行过程如下:

原子性保障

所谓原子性就是说,在事务中进行的一系列操作,要么全部执行成功,要么全部执行失败,不存在部分执行部分不执行的情况。

首先事务的操作**“要么全部成功”**,这个是指事务提交后必须保证数据全部修改成功,在正常情况下事务提交后数据是肯定能修改成功的,所以这里需要考虑的是异常情况下能否保证数据全部修改成功 (比如在数据库崩溃、宕机等故障的情况),所以保证原子性操作全部成功的前提就得必须保证持久性,这这部分的功能在上面我们已经知道是通过redo log 来解决了。

然后原子性中还有一个特性**“要么全部失败”,**,这个是指事务可以在任何阶段进行回滚了,并且在执行回滚操作后,必须保证事务所变更的数据恢复到修改之前的样子。 比如上面的流程中,在修改数据后业务不具备事务提交的条件,那么此时就需要对事务进行回滚,可是需要注意的是,此时已经变更的数据有可能已经写入到磁盘了,所以如果需要进行事务回滚,那么就必然需要对已经修改的数据进行还原,所以此时我们就需要有一个记录的历史数据快照的日志,这个日志的作用在于如果事务需要进行回滚,那么通过该日志考记录的历史快照可以还原已经变更的数据信息,在Mysql 里这个快照日志就是undo log。

undo log

在Mysql里每次修改数据前,首先会把修改之前的数据作为历史保存一份到undo log里面的,数据里面会记录操作该数据的事务ID,在事务需要进行回滚的时候,通过对比undo log日志把已经修改的数据进行还原。

加入redo log后,事务执行过程如下:

总结:

事务持久性的难题在于,一个事务会牵扯到多次磁盘IO操作,多次IO之间操作相互独立,所以在发生故障和意外情况会出现部分数据写入成功,部分数据写入失败的情况,为了保障数据最终都能成功写入到磁盘,所以在修改数据之前,通过redo log提前记录一份最终需要完成修改的数据记录,如果在事务提交的的过程中发生故障、宕机的意外导致数据写入不成功,从而在意外发生后通过redo log来将未成功写入的数据重新写入,最终把需要修改的数据全部写入磁盘,从而保障了事务的持久性。

原子性的难题在于,当修改数据后如果需要对事务进行回滚,这个过程中内存中修改的数据可能持久化到磁盘,导致脏数据,所以必须通过undo log提前记录一份历史数据的快照,在事务需要进行回滚的时候,通过历史数据的快照来将已经变更的数据恢复如初,从而保障事务回滚后就像什么都没发生过一样,配合redo log 保障了事务原子性要么全部成功,要么全部失败的原子特性。

原文:数据库基础(五)事务持久性与原子性的保障 - 知乎

-------------本文结束感谢您的阅读-------------