数据库事务第一章 --- 事务日志

首先 抛出问题,望大家读完后,可以解答:   什么是数据库事务?  为什么要有数据库事务,解决什么问题?

一:知识回顾

  一个合理的数据库设计,需要保证ACID原则(虽然被提及太多次,但这里还是要啰嗦一下)

     原子性(Atomicity)

      原子性是指事务是一个不可分割的工作单位,事务中的一系列操作要么都发生,要么都不发生。

    一致性(Consistency)

      事务前后数据的完整性必须保持一致。

    隔离性(Isolation)

      事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

    持久性(Durability)

      持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

  举个说烂的例子。  从A账户转账100元 到B账户去。 第一步 A账户扣去100,第二步,B账户上增加100。这两步操作 肯定在同一个事务里。

    •   原子性:要么这两步 都不发生,要么都发生,不可以发生了第一步,然后第二步不发生了。
    •   一致性:两步发生后,数据库里的数据要保持一致,总钱数 肯定不能减少或者增多。
    •   隔离性:这个例子中 不能体现,下一章节会细讲
    •   持久性:假如第一步后,服务宕掉了,等到重启后,数据库得有恢复数据的能力,要么回滚第一步,要么继续执行第二步。

 

二:提出问题

  在详细介绍数据库技术层面 是如何实现上述原则前,我们需要先了解 系统程序 是如何修改数据库数据的。

    我们大家都知道,数据库的最终数据都是存在硬盘上的。如果在设计程序时,需要频繁的和硬盘的数据进行IO交互,响应时间就会非常缓慢(为什么IO交互时间慢,因为这里是随机IO,需要在磁盘上多个地方移动磁头,这里不再细说),所以这是我们需要避免的。

 

  为了解决这个问题,数据库在 自身的设计上,它也作出了努力。

    当第一次从数据库里查询出一系列数据时,数据库会将该查询结果数据 缓存在它自己的内存中(也称高速缓冲区,就是一块内存结构,用来存储数据备份,所有数据库用户共享),所以 其实每一次的查询,服务器进程都会尝试先从高速缓冲区里捞数据,如果命中,就直接返回,避免了直接和硬盘进行IO交互,如果命中不了,才会去硬盘上找。修改操作也如此,会先去修改内存中的数据,然后写入硬盘上去。

  问题来了:

  1. 高速缓冲区是什么?

    mysql使用的是InnoDB存储引擎。 InnoDB层 有自己的缓冲区,存放在主机内存中,它的目的主要是在应用层管理自己的数据,避免慢速的读写操作影响了InnoDB的响应时间。这个缓冲区 分为两块,innodb buffer pool 和 relog buffer , 前者就是上述的高速缓冲区,InnoDB buffer pool存储了从磁盘设备读到的InnoDB数据,也缓冲了对InnoDB数据写。relog buffer 下面的内容会着重介绍,这里先略过。

  2. 如果修改完了内存数据,还没来得及写入硬盘,数据库服务挂了,操作会不会丢失? 

    数据库为此 同样也做出了努力。 它也怕写入硬盘时出现故障,所以它干脆先不写入硬盘里,而是先通过事务日志的方式 记录操作行为。

 

三:引出主题 -- 事务日志

  什么是事务日志呢?为什么要有事务日志呢?

    事务日志是真实存在主机硬盘上的日志文件。简单来说,每一次的修改操作,都先修改高速缓存区拷贝的数据(这里不太准确,下面会详述),然后将修改行为记录到事务日志里去,之后,才异步将修改内容持久化到硬盘上去。

 

  事务日志可以帮助提高事务效率:

  • 使用事务日志,存储引擎在修改表的数据时只需要修改其内存拷贝,再把该修改行为记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘。
  • 事务日志采用的是追加的方式,因此写日志的操作是磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头,所以采用事务日志的方式相对来说要快得多。
  • 事务日志持久以后,内存中被修改的数据在后台可以慢慢刷回到磁盘。
  • 如果数据的修改已经记录到事务日志并持久化,但数据本身没有写回到磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这一部分修改的数据。

 

  目前来说,大多数存储引擎都是这样实现的,我们通常称之为预写式日志(Write-Ahead Logging),也称日志先行,修改数据需要写两次磁盘(第一次是将更新行为记录到事务日志,第二次是真正的将更新后的数据记录到数据库文件中)。

  可能看了上面的东西,你还是不清楚事务日志是个什么,也不太清楚数据存储的流程是怎么走的,那么我们现在可以总结一下,确实比较复杂,网上查阅的资料也概括的不全面。

 

  MySQL redo log存储状态

    1.  存在redo log buffer中,物理上在MySQL进程内存中;
    2.  写到磁盘write, 但是没有持久化fsync,物理上是在文件系统的page cache里面;
    3.  持久化到磁盘,对应的是hard disk.

  当有修改操作发生时,

    第一步,会去修改 innodb buffer pool(上面有过介绍)内存中的数据,同时也会在 relog buffer 内存中记录下操作步骤,由于这两个都是修改内存的操作,速度非常快。

    第二步,InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 write 方法将 redo log 写到文件系统的 page cache(这个就是操作系统的文件存储区域),这里就是在写事务日志文件,存储在硬盘上,然后调用 fsync 同步方法 持久化到磁盘。

 

  那这个log到底记录了什么?分两部分

    1. redo log

    在系统启动的时候,就已经为redo log分配了一块连续的存储空间,以顺序追加的方式记录Redo Log,通过顺序IO来改善性能。所有的事务共享redo log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起。如下一个简单示例:

    记录1:<trx1, insert...>

    记录2:<trx2, delete...>

    记录3:<trx3, update...>

    记录4:<trx1, update...>

    记录5:<trx3, insert...>

 

    2. undo log

      undo log主要为事务的回滚服务。在事务执行的过程中,除了记录redo log,还会记录一定量的undo log。undo log记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据undo log进行回滚操作。单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。

    以下是undo+redo事务的简化过程

    假设有2个数值,分别为A和B,值为1,2,  分别更为3,4

    1. start transaction;

    2. 记录 A=1 到undo log;

    3. update A = 3;

    4. 记录 A=3 到redo log;

    5. 记录 B=2 到undo log;

    6. update B = 4;

    7. 记录B = 4 到redo log;

    8. 将redo log刷新到磁盘

    9. commit

    在1-8的任意一步系统宕机,事务未提交,该事务就不会对磁盘上的数据做任何影响。如果在8-9之间宕机,恢复之后可以选择回滚,也可以选择继续完成事务提交,因为此时redo log已经持久化。若在9之后系统宕机,内存映射中变更的数据还来不及刷回磁盘,那么系统恢复之后,可以根据redo log把数据刷回磁盘。

 

  所以,redo log其实保障的是事务的持久性和一致性,而undo log则保障了事务的原子性。

 

  扩展为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,控制 redo log 的刷新。它有三种可能取值:

  • 设置为 0 的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中 ; 这样可能丢失1s的事务数据。
  • 设置为 1 的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘;这样的话,数据库对IO的要求非常高,如果底层硬件提供的IOPS比较差,MySQL数据库 并发很快就会由于硬件IO的问题而无法提升。(当然,InnoDB的组提交方法为降低IOPS做了很大优化)
  • 设置为 2 的时候,表示每次事务提交时都只是把 redo log 写到 page cache。如果只是MySQL数据库挂掉了,由于文件系统没有问题,那么对应的事务数据并没有丢失。只有在数据库所在的主机操作系统损坏或者突然掉电的情况下,数据库的事务数据可能丢失1秒之类的事务数据。这样的好处就是,减少了事务数据丢失的概率,而对底层硬件的IO要求也没有那么高(log buffer写到文件系统中,一般只是从log buffer的内存转移的文件系统的内存缓存中,对底层IO没有压力)

 

  注意,事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的,这些 redo log 也会被后台线程一起持久化到磁盘。也就是说,一个没有提交的事务的 redo log,也是可能已经持久化到磁盘的。

 

  数据丢失的场景:

    如果主机掉电或者MySQL异常宕机,innodb buffer pool将无法及时刷新到磁盘,那么InnoDB就只能从上一个checkpoint使用redo log来前滚;而redo log buffer如果不能及时刷新到磁盘,那么由于redo log中数据的丢失,就算使用redo 前滚,用户提交的事务由于没有真正的记录到非易失型的磁盘介质中,就丢失掉了。

 

  引出一个问题: 是否保证原子性,就可以保持了一致性呢?

 

  下一个章节会回答这个问题。介绍 数据库事务的隔离性是什么? 解决了什么问题? 如何实现的?

 

 

posted on 2019-12-31 13:59  半城枫叶半城雨丶  阅读(404)  评论(0编辑  收藏  举报