内容概述
● 事务机制:
(1)MySQL4.0的时候,是不支持事务机制的;所以,当时MySQL中保存的都是一些不太重要的低价值数据,就像现在的NoSQL数据库一样;
(2)MySQL5.0才有了事务机制,此时很多重要系统才使用MySQL来保存重要数据;
● 数据的导出导入:
(1)不能直接拷贝数据目录;
事务的基本介绍
MySQL5.0版本引入事务机制,才得以进入企业市场的。
1.没有事务机制时,情况很糟糕
平时在修改一份重要文件的时候,通常先备份一下,然后在副本里面去修改;在数据库领域也是如此,如果SQL语句直接操作数据文件是非常危险的;
比如,给员工涨工资,UPDATE语句执行的过程中,系统突然重启了或者停电了,UPDATE语句没有执行完,于是哪些记录修改了,哪些记录没有修改就弄不清了;
……………………………………………………
2.undo日志,redo日志
日志文件就相当于是数据文件的一个副本;SQL语句操作什么样的记录,MySQL数据库就会把这些记录拷贝到undo日志中;增删改查的操作就会记录到redo日志中;
如果这些操作没有什么问题,最后把redo日志和数据文件做一个同步就可以了,即便同步过程中出现了停电、系统重启等问题,因为有redo日志的存在,在重启了MySQL之后,继续完成这个同步就可以了;
同步成功以后,我们修改的数据就真正写到数据文件中了;
即说白了,就是先在undo这个存储记录的日志文件里,尝试去操作,没有问题后再执行同步;;;即使出现中断,因为有日志的存在,还是可以继续同步。
……………………………………………………
3.事务机制引入
……………………………………………………
4.【自动提交事务】和【手动管理事务】
(1)●以前在使用SQL语句的时候,没觉得使用事务啊:这是因为MySQL默认情况下,执行一条SQL语句之前都会为我们开启事务,等这条SQL语句执行完毕之后,MySQL会提交事务(即让redo日志和数据文件作作同步);
● 如果完成某个业务,只需要一条SQL语句,那么MySQL这种自动提交事务的模式是没有问题的;
(2)● 但是,某些业务,需要多条SQL语句的时候,MySQL这种自动提交事务的模式就GG了;如下图中的业务,需要两条SQL语句,一条UPDATE语句和一条DELETE语句;;而且UPDATE语句和DELETE语句必须是“原子的”,从外界业务的角度来看,这两条语句是不可分的,要么全部成功,要么全部失败。
● 要想实现这个效果,UPDATE语句和DELETE语句必须在同一个事务之下运行才行;;;这就涉及到手动去管理事务了;
● 执行过程中,如果发生了意外中断,重启后,如果希望MySQL继续执行事务,就让redo日志继续同步就可以了;;;如果不想MySQL继续同步,就可以把undo日志文件中的数据同步到数据文件中,这样数据文件就恢复到数据提交之前的状态了;
(3)事务回滚:(假如一些特殊原因,当事务不能继续提交的时候,就应该把事务回滚;;;比如,抢购商品的时候,虽然抢到了商品,但是由于支付晚了,那么这个事务就需要回滚,而不能提交);
事务回滚:只需要把redo和undo中的数据做一个标记就可以了,这些被标记过的数据就不会被同步到数据文件中了;
……………………………………………………
5.【手动管理事务】的语法,案例演示
案例:看下整个流程
COMMIT:提交的时候,是提交的整个事务的所有内容。一个事务中的所有SQL语句是一个整体,共进退的!!!
即,是将最终结果持久化到数据库,而不是把中间状态持久化到数据中;
如果在最后,使用ROLLBACK回滚:
即可以这样理解,一旦使用ROLLBACK,本次事务的那些SQL操作 在 redo日志中的临时数据就会被标记,然后就失效了,然后就没有然后了……
即,回滚的时候,mysql把该事务所有的数据都打上标记,这些数据就全都不会同步到数据文件中了;
……………………………………………………
6.事务的ACID属性:
(6.1)事务的原子性
(6.2)事务的一致性
事务通过【阻止事务之间相互读取临时数据】,来实现事务的一致性;
否则,如果事务之间可以相互读取临时数据,比如A原本有500块,【一个事务是A向B转10块,但是这个事务还没有提交的时候,此时在这个事务中临时文件中,A中还有490块,但其实此时在数据文件中A还是有500块的,因为这个事务还没有提交嘛】,此时,【另一个事务,向A中转100块,如果这个事务可以读取上一个事务的零时文件,那么在这个事务中,A中就会有590块,然后整个事务提交后,数据文件中A就变成590块了】。。。。这样以后,如果A向B转10块这个事务回滚取消了,取消就取消了,最后的结果是A中有590块,,,,10块钱就不翼而飞了,出了问题。
所以,为了不出现歧义的数据,MySQL的事务在执行的时候,是完全的隔离的;
(6.3)事务的隔离性
● 每个事务只能看到自己事务内的相关数据,别的数据的临时数据在当前事务中是不可见的;正是因为这种隔离性,让每个事务都认为自己是当前时间段内数据库运行的一个唯一事务;
● 事务是如何实现隔离性:undo和redo日志中,数据都会被标记是属于那个事务的,所以事务执行过程中,只能读到自己的临时数据;
(6.4)事务的持久性
四种隔离级别
数据库中的事务都是并发执行的,因为事务具有隔离性,会给一些业务带来问题。本篇博客主要介绍【事务并发执行的条件下,怎么去修改事务的隔离级别,以满足业务的要求。】
默认情况下,MySQL是不允许事务之间相互读取临时数据的,但是在某些特殊场合下,我们需要允许事务之间能读取到一些临时数据,这个就必须去修改事务的隔离级别了。
需要根据具体的业务场景,从这四个隔离级别中提挑选合适的一个来使用。
1.买票的业务场景:适合【READ UNCOMMITTED】的隔离级别
业务案例1:A事务看到G8047 1车厢1A坐席是未售出状态,于是A事务就使用UPDATE语句修改了这条记录的状态为已售出,因为A事务还没有提交,所以修改操作只是记录在了redo日志中,真实的数据并没有发生改变。。。。如果,此时B事务启动了,B事务也会看到G8047 1车厢1A坐席是未售出的,于是B事务使用UPDATE修改了这条记录的状态为已售出,然后B事务很快就提交了,于是真实的数据文件就发生了改变。。。。此时,A事务再去提交事务,就发现这个坐席已经是已售出了,于是A事务就引发了回滚操作。
虽然,这个案例中没有产生歧义的数据,但是动不动就购票失败,这种体验很差;;;于是在这种场景中,就应该允许当前的事务去读取其他事务的临时状态,B事务发现A事务的临时数据中已经购买了G8047 1车厢1A坐席的信息,于是B事务就可以去购买其他的坐席了;
对于【买票的业务】这种场景,适合使用“READ UNCOMMITTED”这种隔离级别,意思是:允许事务读取其他事务未提交的临时数据;
其中, “SET SESSION”意思是,设置当前会话的事务隔离级别;这种设置不是全局的,只在当前会话生效 ;会话的意思是:在Navicat中打开一个SQL面板,编写SQL语句,这个就是一个会话,当把这个面板关了,这个会话就结束了;
演示:
So,如果想让某个事务能够看到其他事务的临时数据,应该怎么做?
2.银行转账的业务场景:适合使用【READ COMMITTED】这种隔离级别
银行转账之类的业务,绝对不能让某个事务去读取其他事务没有提交的临时数据;
比如业务案例2:银行转账类业务,只能让当前事务去读取其他事务提交以后的数据,绝对不能读取其他事务没有提交的临时数据;比如Scott账户原有5000元,A事务开启了同时还没有来得及做任何操作,此时Scott账户有消费行为,B事务将账户余额改成了100块,然后A事务向Scott转账1000元了,如果A事务读取了B事务的临时数据,A事务执行完毕之后,Scott账户余额是4900元。。。如果A事务和B事务都正常COMMIT了,最终的余额是4900元,是没有问题的。。。但是,如果B事务是一笔错误的消费,需要ROLLBACK,但由于允许A事务读取B事务的临时数据,所以最终Scott账户余额是4900元,出错了。。。
对于【银行转账】这种场景,适合使用“READ COMMITTED”这种隔离级别,意思是:当前事务只能读取其他事务已经提交的数据,未提交的临时数据是读取不到的;
演示:
3.电商订单支付场景:适合使用【REPEATABLE READ】这种隔离级别
比如“业务案例3”:比如在京东商城中,顾客下单之后在支出订单之前,卖家给商品涨价啦,顾客应该按照涨价之前的价格支付;;;所以,在这个业务场景中,我们希望当前事务的执行不要受到其他事务的影响,甚至其他事务提交以后的结果也不要影响到当前事务。。。这个事务的隔离级别就是【REPEATABLE READ】,这种隔离级别意味着,当前的事务能读到的数据是事务开始之前的数据,事务开启之后,其他事务提交的数据是读不到的;
这种隔离级别的底层实现:事务执行之前,会把数据copy到undo日志中,一个隔离级别是【REPEATABLE READ】的事务只会去读取undo日志中的属于自己的那部分数据;
电商订单支付场景:适合使用【REPEATABLE READ】这种隔离级别;
案例:
下面需要说明一点:一个事务在执行select操作之后,才能把数据表中的数据copy到undo日志中;;;即下面的事务,如果在【上面的将sal设置为1的事务】提交之前没有select,当【上面的将sal设置为1的事务】提交之后,下面这个事务再第一次执行select的话,其查询的结果就是sal已经被设置为1的t_emp表的数据了 ;( _ _ 好吧,undo日志和redo日志的原理需要了解一下。。。。__ )
**MySQL数据库默认的事务隔离级别就是【REPEATABLE READ】 ;即在新的会话中,没有特意通过【SET SESSION TRANSACTION ISOLATION LEVEL ****】去修改事务隔离级别的话,事务的隔离级别就是【REPEATABLE READ】。
4.【SERIALIZABLE】隔离级别
序列化隔离级别:一旦设置了这种隔离级别后,当前会话里的事务,需要等待其他事务执行结束之后,才可以执行,不再会出现并发执行的事务了;虽然序列化执行事务能避免一切业务场景上的问题,但是随之而来的是数据库的并发性能急剧下降;;;;;所以,这种隔离级别是很少使用的;
演示:
可以发现,序列化隔离级别不太好用。。。
数据的持久化
数据的导入和导出,多图预警。
1.【数据导出】和【数据备份】简介
MySQL的【数据导出和导入】和【数据备份】是不同的。
● 【数据库的当前状态】:包含数据文件、错误日志、redo日志、undo日志等等;
● 【数据导出】:数据的导出,导出的是纯粹的业务数据,而不是数据库的当前状态;数据导出导出的仅仅是数据文件而已。
● 【数据备份】:是完成的备份数据库的当前状态。数据备份,既备份了数据文件,又备份了各种日志文件,还有索引文件,甚至包括MySQL的配置文件;所以,要备份完整的MySQL数据库,还是要选择数据备份这种方式;
……………………………………………………
MySQL的数据备份包括全量备份、增量备份
【全量备份】:完整的备份数据库,但这种方式是最占用硬盘空间的;
【增量备份】:有了全量备份之后,以后的备份就可以选择增量备份了,即只备份变动的那部分数据;这个对硬盘占用就小了很多;
MySQL的备份第一次备份是全量备份,后续的是增量备份;
……………………………………………………
MySQL的备份需要用到Linux环境,而且没有图形工具可以使用。这部分内容以后再了解。 本篇博客主要介绍数据的导出和导入。
2.数据导出:SQL文件
如果导出的数据不是很多的时候,建议导出成SQL文档;如果导出的数据很多的时候,建议导出成文本文档;
导出成SQL文档:这其中包含了表结构【CREATE TABLE建表语句,GREATE INDEX创建索引语句】,也包含了业务数据【INSERT INTO插入记录的语句】;
如:下面这个demo.sql就包含了表结构和业务数据;
MySQL在导入SQL文件的时候,就相当于把这个文件中的所有SQL语句重新执行了一遍。
如果数据库里有50万条记录,导出成SQL文件后,SQL文件中就应该有50万条INSERT语句,MySQL在执行每一条SQL语句的时候都要做词法分析和优化,所以导入这50万条记录,需要花费一定的时间。所以,只有当数据量很小的时候,才建议导出成SQL文件;当数据量很大的时候,不建议导出成SQL文件。
2.1 导出SQL文件:命令行的方式:
如果想只导出表结构的话,可以加上no-data;如果不写no-data,导出的东西既包含表结构又包含数据;(2)>前后都有一个空格;
2.1.1 导出表结构也业务数据
2.1.2 只导出表结构
2.2 导出SQL文件:图形界面的方式:
2.2.1 导出结构和业务数据
2.2.2 只导出结构
3.数据导入:SQL文件
3.1 导入SQL文件:图形界面方式
3.2 导入SQL文件:命令行的方式
4.数据导出&导入:文本文件(文本文件以txt格式的为例)
这种方式导出的文档中只有纯粹的数据,而不是SQL语句。 导入文本文档的时候,由于文本文档中没有SQL语句,所以MySQL在导入文本文档的时候是跳过词法分析和语法优化的,直接把文本文档中的数据写入到MySQL的数据文件中;导入文本文档的速度是非常快的;
所以,当逻辑库的数据非常多的时候,选择导出成文本文档:第一个优点是文本文档的体积比SQL文档要小很多;第二个优点,导入文本文档的速度很快。
导出和导入文本文档的SQL命令非常复杂,不容易掌握。这儿仅仅展示图形界面的方式。
4.1导出文本文件:图形界面的方式
选择只导出文本文档,把这个文本文档拿到其他的数据库上是没办法还原的,因为不知道表结构是什么。
(4.1.1)首先,需要先备份表结构:
(4.1.2)然后,导出文本文档
4.2导入文本文件:图形界面的方式
(4.2.1)首先,导入表结构
(4.2.2)然后,导入文本文档的数据
注:导入txt文档数据的时候,因为没有SQL语句的执行,MySQL在导入文档数据的时候,是完全跳过了词法分析和语法优化,直接把数据导入到数据文档中,这个速度是非常、非常、非常快的,比如导入几千万条数据可能花费的时间连一分钟都不到;
redo和undo日志(转载)
本篇文章转载自redo和undo日志,说说MySQL中的Redo log Undo log都在干啥;
这篇博客半懂不懂……
在数据库系统中,既有存放数据的文件,也有存放日志的文件。日志在内存中也是有缓存Log buffer,也有磁盘文件log file,本文主要描述存放日志的文件。
MySQL中的日志文件,有这么两类常常讨论到:undo日志与redo日志。
1 undo
1.1 undo是啥
undo日志用于存放数据修改被修改前的值,假设修改 tba 表中 id=2的行数据,把Name=’B’ 修改为Name = ‘B2’ ,那么undo日志就会用来存放Name=’B’的记录,如果这个修改出现异常,可以使用undo日志来实现回滚操作,保证事务的一致性。
对数据的变更操作,主要来自 INSERT UPDATE DELETE,而UNDO LOG中分为两种类型,一种是 INSERT_UNDO(INSERT操作),记录插入的唯一键值;一种是 UPDATE_UNDO(包含UPDATE及DELETE操作),记录修改的唯一键值以及old column记录。
Id | Name |
---|---|
1 | A |
2 | B |
3 | C |
4 | D |
1.2 undo参数
MySQL跟undo有关的参数设置有这些:
1 mysql> show global variables like ‘%undo%’; 2 +--------------------------+------------+ 3 | Variable_name | Value | 4 +--------------------------+------------+ 5 | innodb_max_undo_log_size | 1073741824 | 6 | innodb_undo_directory | ./ | 7 | innodb_undo_log_truncate | OFF | 8 | innodb_undo_logs | 128 | 9 | innodb_undo_tablespaces | 3 | 10 +--------------------------+------------+ 11 12 mysql> show global variables like ‘%truncate%’; 13 +--------------------------------------+-------+ 14 | Variable_name | Value | 15 +--------------------------------------+-------+ 16 | innodb_purge_rseg_truncate_frequency | 128 | 17 | innodb_undo_log_truncate | OFF | 18 +--------------------------------------+-------+
- innodb_max_undo_log_size
控制最大undo tablespace文件的大小,当启动了innodb_undo_log_truncate 时,undo tablespace 超过innodb_max_undo_log_size 阀值时才会去尝试truncate。该值默认大小为1G,truncate后的大小默认为10M。
- innodb_undo_tablespaces
设置undo独立表空间个数,范围为0-128, 默认为0,0表示表示不开启独立undo表空间 且 undo日志存储在ibdata文件中。该参数只能在最开始初始化MySQL实例的时候指定,如果实例已创建,这个参数是不能变动的,如果在数据库配置文 件 .cnf 中指定innodb_undo_tablespaces 的个数大于实例创建时的指定个数,则会启动失败,提示该参数设置有误。
如果设置了该参数为n(n>0),那么就会在undo目录下创建n个undo文件(undo001,undo002 …… undo n),每个文件默认大小为10M.
什么时候需要来设置这个参数呢?
当DB写压力较大时,可以设置独立UNDO表空间,把UNDO LOG从ibdata文件中分离开来,指定 innodb_undo_directory目录存放,可以制定到高速磁盘上,加快UNDO LOG 的读写性能。
- innodb_undo_log_truncate
InnoDB的purge线程,根据innodb_undo_log_truncate设置开启或关闭、innodb_max_undo_log_size的参数值,以及truncate的频率来进行空间回收和 undo file 的重新初始化。
该参数生效的前提是,已设置独立表空间且独立表空间个数大于等于2个。
purge线程在truncate undo log file的过程中,需要检查该文件上是否还有活动事务,如果没有,需要把该undo log file标记为不可分配,这个时候,undo log 都会记录到其他文件上,所以至少需要2个独立表空间文件,才能进行truncate 操作,标注不可分配后,会创建一个独立的文件undo_ trunc.log,记录现在正在truncate 某个undo log文件,然后开始初始化undo log file到10M,操作结束后,删除表示truncate动作的 undo _trunc.log 文件,这个文件保证了即使在truncate过程中发生了故障重启数据库服务,重启后,服务发现这个文件,也会继续完成truncate操作,删除文件结束后,标识该undo log file可分配。
- innodb_purge_rseg_truncate_frequency
用于控制purge回滚段的频度,默认为128。假设设置为n,则说明,当Innodb Purge操作的协调线程 purge事务128次时,就会触发一次History purge,检查当前的undo log 表空间状态是否会触发truncate。
1.3 undo空间管理
如果需要设置独立表空间,需要在初始化数据库实例的时候,指定独立表空间的数量。
UNDO内部由多个回滚段组成,即 Rollback segment,一共有128个,保存在ibdata系统表空间中,分别从resg slot0 - resg slot127,每一个resg slot,也就是每一个回滚段,内部由1024个undo segment 组成。
回滚段(rollback segment)分配如下:
- slot 0 ,预留给系统表空间;
- slot 1- 32,预留给临时表空间,每次数据库重启的时候,都会重建临时表空间;
- slot33-127,如果有独立表空间,则预留给UNDO独立表空间;如果没有,则预留给系统表空间;
回滚段中除去32个提供给临时表事务使用,剩下的 128-32=96个回滚段,可执行 96*1024 个并发事务操作,每个事务占用一个 undo
segment slot,注意,如果事务中有临时表事务,还会在临时表空间中的 undo segment slot 再占用一个 undo segment
slot,即占用2个undo segment slot。如果错误日志中有:Cannot find a free slot for an undo log。
则说明并发的事务太多了,需要考虑下是否要分流业务。
回滚段(rollback segment )采用 轮询调度的方式来分配使用,如果设置了独立表空间,那么就不会使用系统表空间回滚段中undo segment,而是使用独立表空间的,同时,如果回顾段正在 Truncate操作,则不分配。
2 redo
2.1 redo是啥
当数据库对数据做修改的时候,需要把数据页从磁盘读到buffer pool中,然后在buffer pool中进行修改,那么这个时候buffer pool中的数据页就与磁盘上的数据页内容不一致,称buffer pool的数据页为dirty page 脏数据,如果这个时候发生非正常的DB服务重启,那么这些数据还没在内存,并没有同步到磁盘文件中(注意,同步到磁盘文件是个随机IO),也就是会发生数据丢失,如果这个时候,能够在有一个文件,当buffer pool 中的data page变更结束后,把相应修改记录记录到这个文件(注意,记录日志是顺序IO),那么当DB服务发生crash的情况,恢复DB的时候,也可以根据这个文件的记录内容,重新应用到磁盘文件,数据保持一致。
这个文件就是redo log ,用于记录 数据修改后的记录,顺序记录。它可以带来这些好处:
- 当buffer pool中的dirty page 还没有刷新到磁盘的时候,发生crash,启动服务后,可通过redo log 找到需要重新刷新到磁盘文件的记录;
- buffer pool中的数据直接flush到disk file,是一个随机IO,效率较差,而把buffer pool中的数据记录到redo log,是一个顺序IO,可以提高事务提交的速度;
假设修改 tba 表中 id=2的行数据,把Name=’B’ 修改为Name = ‘B2’ ,那么redo日志就会用来存放Name=’B2’的记录,如果这个修改在flush 到磁盘文件时出现异常,可以使用redo log实现重做操作,保证事务的持久性。
Id | Name |
---|---|
1 | A |
2 | B |
3 | C |
4 | D |
这里注意下redo log 跟binary log 的区别,redo log 是存储引擎层产生的,而binary log是数据库层产生的。假设一个大事务,对tba做10万行的记录插入,在这个过程中,一直不断的往redo log顺序记录,而binary log不会记录,知道这个事务提交,才会一次写入到binary log文件中。binary log的记录格式有3种:row,statement跟mixed,不同格式记录形式不一样。
2.2 redo 参数
- innodb_log_files_in_group
redo log 文件的个数,命名方式如:ib_logfile0,iblogfile1… iblogfilen。默认2个,最大100个。
- innodb_log_file_size
文件设置大小,默认值为 48M,最大值为512G,注意最大值指的是整个 redo log系列文件之和,即(innodb_log_files_in_group
-
innodb_log_file_size )不能大于最大值512G。
- innodb_log_group_home_dir
文件存放路径
- innodb_log_buffer_size
Redo Log 缓存区,默认8M,可设置1-8M。延迟事务日志写入磁盘,把redo log 放到该缓冲区,然后根据 innodb_flush_log_at_trx_commit参数的设置,再把日志从buffer 中flush 到磁盘中。
-
innodb_flush_log_at_trx_commit
-
innodb_flush_log_at_trx_commit=1,每次commit都会把redo log从redo log buffer写入到system,并fsync刷新到磁盘文件中。
-
innodb_flush_log_at_trx_commit=2,每次事务提交时MySQL会把日志从redo log buffer写入到system,但只写入到file system buffer,由系统内部来fsync到磁盘文件。如果数据库实例crash,不会丢失redo log,但是如果服务器crash,由于file system buffer还来不及fsync到磁盘文件,所以会丢失这一部分的数据。
-
innodb_flush_log_at_trx_commit=0,事务发生过程,日志一直激励在redo log buffer中,跟其他设置一样,但是在事务提交时,不产生redo 写操作,而是MySQL内部每秒操作一次,从redo log buffer,把数据写入到系统中去。如果发生crash,即丢失1s内的事务修改操作。
-
注意:由于进程调度策略问题,这个“每秒执行一次 flush(刷到磁盘)操作”并不是保证100%的“每秒”。
2.3 redo 空间管理
Redo log文件以ib_logfile[number]命名,Redo log 以顺序的方式写入文件文件,写满时则回溯到第一个文件,进行覆盖写。(但在做redo checkpoint时,也会更新第一个日志文件的头部checkpoint标记,所以严格来讲也不算顺序写)。
实际上redo log有两部分组成:redo log buffer 跟redo log file。buffer pool中把数据修改情况记录到redo log buffer,出现以下情况,再把redo log刷下到redo log file: Redo log buffer空间不足 事务提交(依赖innodb_flush_log_at_trx_commit参数设置) 后台线程 做checkpoint 实例shutdown binlog切换
3 undo及redo如何记录事务
这部分内容推荐阅读这系列的博客,写的好好:<http://www.zhdba.com/mysqlops/2012/04/06/innodb- log1/> 以下内容部分节选自这博客,感谢作者总结,深入浅出超好理解。
3.1 Undo + Redo事务的简化过程
假设有A、B两个数据,值分别为1,2,开始一个事务,事务的操作内容为:把1修改为3,2修改为4,那么实际的记录如下(简化): A.事务开始. B.记录A=1到undo log. C.修改A=3. D.记录A=3到redo log. E.记录B=2到undo log. F.修改B=4. G.记录B=4到redo log. H.将redo log写入磁盘。 I.事务提交
3.2 IO影响
Undo + Redo的设计主要考虑的是提升IO性能,增大数据库吞吐量。可以看出,B D E G H,均是新增操作,但是B D E G 是缓冲到buffer区,只有G是增加了IO操作,为了保证Redo Log能够有比较好的IO性能,InnoDB 的 Redo Log的设计有以下几个特点:
A. 尽量保持Redo Log存储在一段连续的空间上。因此在系统第一次启动时就会将日志文件的空间完全分配。 以顺序追加的方式记录Redo Log,通过顺序IO来改善性能。 B. 批量写入日志。日志并不是直接写入文件,而是先写入redo log buffer.当需要将日志刷新到磁盘时 (如事务提交),将许多日志一起写入磁盘. C. 并发的事务共享Redo Log的存储空间,它们的Redo Log按语句的执行顺序,依次交替的记录在一起, 以减少日志占用的空间。例如,Redo Log中的记录内容可能是这样的:
记录1: <trx1, insert …> 记录2: <trx2, update …> 记录3: <trx1, delete …> 记录4: <trx3, update …> 记录5: <trx2, insert …>
D. 因为C的原因,当一个事务将Redo Log写入磁盘时,也会将其他未提交的事务的日志写入磁盘。 E. Redo Log上只进行顺序追加的操作,当一个事务需要回滚时,它的Redo Log记录也不会从Redo Log中删除掉。
3.3 恢复
前面说到未提交的事务和回滚了的事务也会记录Redo Log,因此在进行恢复时,这些事务要进行特殊的的处理。有2种不同的恢复策略:
A. 进行恢复时,只重做已经提交了的事务。 B. 进行恢复时,重做所有事务包括未提交的事务和回滚了的事务。然后通过Undo Log回滚那些 未提交的事务。
MySQL数据库InnoDB存储引擎使用了B策略, InnoDB存储引擎中的恢复机制有几个特点:
A. 在重做Redo Log时,并不关心事务性。 恢复时,没有BEGIN,也没有COMMIT,ROLLBACK的行为。也不关心每个日志是哪个事务的。尽管事务ID等事务相关的内容会记入Redo Log,这些内容只是被当作要操作的数据的一部分。
B. 使用B策略就必须要将Undo Log持久化,而且必须要在写Redo Log之前将对应的Undo Log写入磁盘。Undo和Redo Log的这种关联,使得持久化变得复杂起来。为了降低复杂度,InnoDB将Undo Log看作数据,因此记录Undo Log的操作也会记录到redo log中。这样undo log就可以象数据一样缓存起来,而不用在redo log之前写入磁盘了。 包含Undo Log操作的Redo Log,看起来是这样的:
记录1: <trx1, Undo log insert <undo_insert …>> 记录2: <trx1, insert …> 记录3: <trx2, Undo log insert <undo_update …>> 记录4: <trx2, update …> 记录5: <trx3, Undo log insert <undo_delete …>> 记录6: <trx3, delete …>
C. 到这里,还有一个问题没有弄清楚。既然Redo没有事务性,那岂不是会重新执行被回滚了的事务? 确实是这样。同时Innodb也会将事务回滚时的操作也记录到redo log中。回滚操作本质上也是 对数据进行修改,因此回滚时对数据的操作也会记录到Redo Log中。 一个回滚了的事务的Redo Log,看起来是这样的:
记录1: <trx1, Undo log insert <undo_insert …>> 记录2: <trx1, insert A…> 记录3: <trx1, Undo log insert <undo_update …>> 记录4: <trx1, update B…> 记录5: <trx1, Undo log insert <undo_delete …>> 记录6: <trx1, delete C…> 记录7: <trx1, insert C> 记录8: <trx1, update B to old value> 记录9: <trx1, delete A>
一个被回滚了的事务在恢复时的操作就是先redo再undo,因此不会破坏数据的一致性。
参考文章: http://mysql.taobao.org/monthly/2016/07/01/ https://yq.aliyun.com/articles/50747 http://www.zhdba.com/mysqlops/2012/04/06/innodb-log1/ http://mysql.taobao.org/monthly/2015/04/01/ 原文链接:http://www.cnblogs.com/xinysu/p/6555082.html