数据库DataBase

Database(六) mysql的事务和实现

2020-05-14  本文已影响0人  joshuaXin

一:简介

1.事务特性:ACID
   原子性:Atomicity:一个事务必须被视为不可分割的最小单元,事务中的所有操作,要么全部提交成功,要么全部失败回滚; 
  一致性:Consistency:事务必须从一个一致性的状态变换到另一个一致性的状态,即事务执行之前和执行之后都必须处于一致性的状态;可分为强一致性和弱一致性;
  隔离性:Isolation:当多用户并发访问数据库时,数据可以根据不同的隔离级别达到隔离的要求;最高的要求是实现不同事务间,完全不影响;
  持久性:Durability:一个事务一旦被提交了,那么对数据库的改变就是永久性的,即使数据库系统遇到故障,也不会丢失提交事务的操作;
2.事务的提交和回滚
 
 2.1事务的开始和提交
      在未开启autocommit的情况下,事务的开始是使用start transaction,用来声明开启一个事务,事务的提交是commit,事务的动作在中间进行,如下面:

start transaction;

……  #一条或多条sql语句

commit;

2.2 事务的回滚
   如果sql语句执行出现问题,会调用rollback,回滚所有已经执行成功的sql语句。当然,也可以在事务中直接使用rollback语句进行回滚。
2.3 自动提交
  mysql中默认采用的是自动提交(autocommit)模式,即如果没有显示的声明start transaction,则由引擎自动自动声明一个事务,并在完成后默认提交;
  查询是否是自动提交,可以用 show variables like 'autocommit';
  在关闭了autocommit的情况下,存在一些特殊的命令,会马上强制执行commit提交事务;如DDL语句(create table/drop table/alter/table)、lock tables语句等等。不过,常用的select、insert、update和delete命令,都不会强制提交事务。

二:事务原子性的实现

 1.定义
   
原子性是指一个事务是一个不可分割的工作单位,要么全做。要么全不做,当其中的一个sql语句执行失败,则已执行的语句也必须全部回滚,数据库回退到事务前的状态;
2.实现原理:undo log
     
undo log又称为回滚日志,是Mysql的日志的一种;Mysql作为开源数据库中的佼佼者,小巧、效率高、可靠性好,高可靠性就是靠这些日志实现的,包括二进制日志binlog、错误日志error log、慢日志slow log、重做日志redo log等,其中undo log是实现事务原子性和隔离性的基础;
       在说明原子性原理之前,首先介绍一下MySQL的事务日志。MySQL的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等,此外InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。在隔离性的时候,讲了undo链在MVCC中的作用,这里主要说下原子性;
      在事务实现原子性的时候,如果成功则提交修改,如果失败,则需要将先前的操作回滚,回滚靠的就是undo log;当事务对数据进行修改的时候,会产生undo log,如果事务执行失败或调用了rollback,导致事务回滚,则可以利用undo log的信息将数据回滚到修改之前的样子;
     undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。

三:事务持久性的实现

1.概念
      持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作;即持久性主要关心的问题在于事务提交之后,数据如何落盘,正常情况下,落盘自然没有问题,那么在发生故障的情况下,如何落盘就是重点,比如宕机、磁盘失效等
2.Buffer:Innod buffer 
   Innodb作为Mysql的存储引擎,数据是放在磁盘上的,但因为每次读取都进行磁盘ID,效率很低;为了提高读写的效率,Innodb提供了一块连续的buffer,Buffer中包含了磁盘中部分数据的映射,作为数据库的缓冲,从DB中读取时,会先从Buffer中读取,向DB中写的时候,会先写入Buffer,Buffer中的数据会定期刷新到磁盘中(刷新脏数据);
   引入的问题:Buffer的使用大大提高了读写数据的效率,但是当Mysql宕机,而此时Buffer中修改的数据没有刷新到磁盘的时候,就会导致数据的丢失,事务的持久性没法保证;
3.redo log
   redo log又称为重做日志,用来引入解决这个问题,它记录了事务在数据页上做了哪些修改;当数据修改时,除了修改Buffer中的数据,还会再redo log中记录这次操作,当事务提交时,会调用fsync对redo log进行刷盘;
   当Mysql宕机,重启时就可以读取redo log中的数据,对数据库进行恢复;redo log是采用预写的形式,即所有修改先写入日志,再更新到Buffer,保证数据不会因Mysql宕机而丢失;当然这也不是万无一失的,因为fsync也是通过Buffer进行的,也存在风险;   
      既然redo log也要写入磁盘,不是多此一举吗,其实它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快,主要有以下两方面的原因:
     1.刷脏是随机IO,每次修改的数据位置随机,redo log是追加操作,属于顺序IO;
     2.刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。
4.磁盘、数据库的数组组织结构
    数据库、OS和磁盘读写的基本单位都是块,也称为block,数据库的块一般为8K、16K,而OS的一般为4K,磁盘IO块更小,Linux内核要求 IO block <= OS block;
    block是磁盘IO的逻辑操作单位,磁盘的物理单位为扇区sector,一般为512字节,一般block对应一个或几个扇区;
   总体上 DB block > OS block >= IO block > 磁盘 sector,且他们是整数倍的关系。
   导致的问题:
   
由于Innodb的page size一般为16KB,所以一般是磁盘sector的几倍,所以可能在极端的情况下,只写入一部分的page,即partial page write部分页写入的问题,
    解决:
     
当Mysql将脏数据flush到data file的时候,先使用memcopy将脏数据复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分2次,每次写入到1MB的共享表空间,然后马上调用fsync函数,刷新到磁盘,避免缓冲带来的问题;doublewrite是顺序写;开销不大。

四:数据恢复 Crash Recovery(持久性的必备条件)

1. binlog
  binlog又称为二进制日志,它记录了所有对表和数据进行修改的语句,如出select、show等查询的DDL和DML语句,以事件的形式记录,比如记录操作的语句;binlog的主要目的就是复制和恢复;复制主要是指主从或者是 集群的复制,留待后面再讲,这里主要讲数据的恢复,尤其是数据库Crash之后的数据恢复;
2.binlog会不会丢数据?
   2.1 参数
sync_binlog
       它表示每sync_binlog个事务,Mysql会调用一次fsync操作将binlog刷新到磁盘中其中0表示完全由OS控制,在Mysql 5.7.7版本之前默认为0,在之后默认的值为1,即每次事务都需要刷新到磁盘中;当sync_binlog=0,或者大于1的时候,这个时候表示牺牲一定的一致性,来获取更好的性能;
   2.2 innodb_flush_log_at_trx_commit
    它也是影响redo log刷新到硬盘的时机的参数:
    为0时: 由mysql的main_thread每秒将存储引擎log buffer中的redo日志写入到log file,并调用文件系统的sync操作,将日志刷新到磁盘。
    为1时:每次事务提交时,将存储引擎log buffer中的redo日志写入到log file,并调用文件系统的sync操作,将日志刷新到磁盘。
    为2时:每次事务提交时,将存储引擎log buffer中的redo日志写入到log file,并由存储引擎的main_thread 每秒将日志刷新到磁盘。

3.binlog的格式
  支持3种格式:Mysql 5.7.7之前,默认是statement,之后为row
     1)statement:基于SQL语句的复制( statement-based replication);优点:只记录SQL语句,简单;缺点是:由于只记录语句,会出现slave和master执行结果不一致的情况出现;
     2)row:基于行的复制( row-based replication),不记录SQL语句,只记录数据被修改后的结果,row的日志会非常清楚记录每一行数据修改的细节;优点:不会出现主备因执行顺序导致的不一致的问题,缺点:会产生大量的日志内容;
     3)mixed:混合模式的复制( mixed-based replication),一般的语句修改使用statement,对弈一些statement无法完成主从复制的操作,则由row来记录,Mysql会根据执行的每一条具体的sql语句,来区分对待记录的日志形式;
4. Crash recovery的过程
   数据库宕机之后恢复,分2种情况,一是redo log已经写入磁盘,但是数据的更新没有刷入磁盘;二是binlog已经记入,但是redo log没有刷盘成功;当然也有两者共同存在的情况,所以恢复动作分为2步:redo log恢复和binlog和undo log合作恢复;
4.1 技术简介
   1. LSN,log sequence number,日志序列号,在redo log中用来记录log随着时间变化的唯一标志;PS:每个数据页也有LSN;
   2.checkpoint技术:在一些日志和文件写入的场景,checkpoint挺常见的,比如kafka的日志中也有,该技术是为了解决几个问题:1.隔段时间,批量写入,并标志进度,缩短数据恢复时间,也加快写入速度;2.缓冲池不够大时,将脏页刷回磁盘;3.redo log不可用时,刷新脏页;
  3.redo恢复机制:因为checkpoint的存在,所以当数据库需要恢复时,checkpoint之前的已经写入了,所以只需恢复checkpoint之后的数据;
  4.redo log和binlog的一致性的保证:以update举例
     1)Mysql server 收到update请求,并转发 innodb存储引擎;
     2)innodb update操作成功,redo log 写盘,Innodb事务进入 prepare状态;
     3)binlog写盘,Innodb事务进入commit状态,并写入redo log 的commit日志;
  5.利用binlog和undo log进行恢复
     binlog和redo log是一个两段式提交的协议;但是理论上,在忽然宕机的情况下,还是会出现binlog写入,但是undo log没有写入成功的场景;这种场景就需要利用binlog来恢复redo log,然后利用redo log去恢复;
4.2 redo log恢复
    
恢复时,Innodb会通过redo log找到最近的checkpoint的位置,然后根据checkpoint的LSN找到需要重做的日志;
   因为redo log是事务对数据页做的修改,所以引擎会解析相应的redo log加载到内存中,读取相关的页进行恢复;恢复之后,在redo log和磁盘不一致的数据得到了恢复;4.3 binlog恢复
   当binlog已经成功写入,但是redo log未写入,导致的不一致的问题(可能是主备不一致,至少是事务的修改没有落盘),需要binlog和undo log合作来恢复;步骤为:
   1.根据binlog,获取到所有可能没有提交事务的xid列表;
   2.根据undo log中的信息,构造所有未提交事务的链表,;
   3.最后判断事务是否可以提交,判断依据是:
     凡是xid在通过binlog发构建的xid列表中存在的事务,都需要被提交,这些事务是在提交时,被写入binlog的;
    那些剩下的,需要被回滚,即这个事务没有提交成功,最后也没有写成binlog;
5.总结
   事务的一致性涉及的点很多,包括主备、集群、Mysql server和存储引擎,存储引擎和磁盘、redo log和binlog等等;这里仅从单机的角度,描述了单机Mysql的一致性,即在异常状态下数据的恢复,从而保证数据的一致性;

五:事务一致性的实现

1. 概念
     一致性是指事务执行结束后,数据的完整性约束没有被破坏,事务执行前后都是合法的数据状态;数据库的完整性约束包括但不限于:实体完整性(主键唯一)、列完整性(字段类型大小合法)、外键约束、用户自定义(如转账前后,两账户的余额总和不变);原子性、持久性和隔离性,都是为了保证数据库状态的一致性;
2.种类
     从逻辑层的角度来划分,数据一致性包括应用层面的一致性、主备数据库的一致性,还有单机mysql内部的数据一致性,这里主要讨论的是单机mysql内部的数据一致性;
     从一致性的强弱,又分为强一致性、弱一致性、最终一致性;

六:事务隔离性的实现

   上篇在介绍Innodb的锁的时候,介绍了事务的隔离性,隔离性关注的是不同的事务之间的相互影响,即事务内部的操作个其他事务是隔离的,并发执行的各个事务之间不能互相干扰;
    其中锁机制保证隔离性,MVCC保证读操作的隔离性;其中要再说明的是MVCC和间隙锁解决了幻读问题,但是并没有实现Serializable;

上一篇下一篇

猜你喜欢

热点阅读