MYSQL日志和索引模块详解

一)为什么要有区,段,页?

1)页是内存和磁盘之间交互的基本单位内存中的值修改之后刷到磁盘的时候还是以页为单位的索引结构给程序员提供了高效的索引实现方式,不过索引信息以及数据记录都是记录在文件上面的,确切来说是存储在页结构中,另一方面,索引是在存储引擎中实现的,MYSQL服务器上面的存储引擎负责对于表中的数据进行读取和写入操作,不同的存储引擎中存放的格式是不同的,有的存储引擎例如Memory甚至都不用磁盘存储数据

2)InnoDB见数据划分成若干个页,页的大小默认是16KB,MYSQL以页作为磁盘和内存交互的基本单位,也就是说MYSQL一次至少将16KB的数据从磁盘加载到内存里面,一次也是至少把内存中的16KB数据刷新到磁盘上,也就是说从数据库中无论是读一行还是读取多行,都是将这些行所在的页进行加载,也就是说数据库进行存储空间的基本单位是页,数据库IO操作的最小单位是页,一个页中可以有多条行记录

3)页和页之间并不会在物理结构上直接相连,而是通过链表进行相连

1)表空间也就是ibd文件一个MYSQL实例可以对应着多个表空间,用于存储记录索引等数据

2)段:分为数据段,索引段和回滚段,InnoDB是索引组织表,数据段就是B+树的叶子节点,索引段就是B+树的非叶子节点,段使用来管理多个区的

3)区:表空间的单元结构,每一个区的大小是1M,默认情况下InnoDB存储引擎的页大小是16Kb,那么一个区中有64个连续的数据页

4)页:是Innodb存储引擎磁盘管理的最小单元,为了保证页的连续性,InnoDB存储引擎每一次向中申请4-5个区

5)行:InnoDB存储数据是按照行进行存放的

下面是两个重要的行格式:

Trx_id:每一次针对于某一一条记录进行修改的时候,都会把对应的事物ID赋值给trx_id隐藏列

Roll_pointer:每次对某一条记录进行改动时,都会把旧的版本写入到undolog日志中,然后这个隐藏列就相当于是下一个指针,可以通过它来找到该记录修改之前的信息

一个区的大小是1M,一个页的大小是16K,一个区中是包含着64个连续的页,innodb引擎再进行分配空间的时候一次性会申请4-5个区,从而来保证申请到的页是连续的

MVCC:在快照读的时候来MVCC决定我们提取的到底是哪一个记录版本,在链表的头部保存的是最新的旧版本的数据,在链表的尾部保存的是最旧的修改的数据

隐藏字段:事务ID+回滚指针+主键,undolog版本链,ReadView(决定当前快照读到底读取的是哪一个版本)

innodb的内存结构:

1)bufferpool:缓冲区是主内存中的一个区域,里面可以缓存磁盘上面的经常操作的真实数据,再进行增删改查操作的时候,是先进行操作缓冲池中的数据,如果缓冲池中没有数据,那么就从磁盘中加载并进行缓存,然后以一定频率更新到磁盘,从而减少磁盘IO,每隔一段时间进行刷新到磁盘;

2)页:在缓冲池中,底层是以page为单位,底层采用链式数据结构来管理page,根据状态page被分成三种类型:

2.1)free page:申请了这一段空间,但是还没有被使用

2.2)clean page:被使用到的page,但是数据没有被修改

2.3)dirty page:脏页,这个页被使用了,但是这个页中的数据也被修改了,和磁盘上的数据不一致,因为缓冲区的数据还没有刷新到磁盘上面

3)change buffer:更改缓冲区,针对于非唯一二级索引,对主键索引或者是唯一索引不生效,在执行DML语句的时候,如果这些数据没有在buffer pool中,不会直接操作磁盘,而是先将数据变更存放到更改缓冲区changeBuffer中,在未来数据进行读取的时候,再来将数据合并到buffer pool中,最后将合并到的数据刷新到磁盘中

4)自适应哈希:InnoDB存储引擎不支持哈希索引,但是内置了自适应哈希,Key是键值,Value为该索引键值匹配完整记录所在页面(Page)的位置

5)日志缓冲区:第一个参数是log buffer大小,第二个参数是刷盘策略

后台线程:

1)什么要有区? 

1.1)select * from user where userID>1 and userID<5,这些满足要求的数据可能分布在不同的页中,我们就需要找到所有数据所在的页,想要将页加载到内存中,实际上页和页之间在物理磁盘中离的是非常远的,MYSQL需要依次找到三个页的物理磁盘位置并把它们加载到磁盘中,因为磁盘中的磁头在去查找页的时候需要做寻道,盘片的旋转都需要时间,这种状况就称之为随机IO,随机IO中磁盘的读写速度和内存的操作速度相差了好几个数量级,在磁盘中查找页,如果页分散在磁盘上面的不同区域,找页就非常麻烦

1.2)顺序IO:所以说尽量让链表中相邻的页的物理位置也相邻,所以进行磁盘范围查询的时候方便,使用顺序IO,尽量让三个页在物理磁盘上是挨着的,是连续存放的,这样进行寻找的时候就能减少盘片旋转和磁道扫描的过程,这样加载速度就会快很多,分一个区就是为了保证一波页是在物理磁盘时尽量连续的,这样子就减少了随机IO;

1.3)但是区和区不一定是连续的,所以很有可能我们查询的数据都在一个区里面,多个页在一个区内,比之前多个页随机分布在磁盘上面的各个位置要靠谱得多

 

2)为什么要有段? 

1)在区里面存放页,区里面的页是连续存储,现在还是进行查询

select * from user where userID>1 and userID<5,现在已经定位到userID大于等于1小于等于5这个范围了,现在感兴趣的只是B+树的叶子节点中所在的页

2)但是非叶子节点也是页,但是现在的区中极有可能存放叶子节点中的页,也有可能存放非叶子节点中的这些目录页,区中既有目录页也有数据页,但是感兴趣的只有数据页,因为都放,可能就会导致数据页放的少了,此时加载数据的时候,发现加载一个页数据项不够,又要跳转到下一个区,这个时候就不纯粹了,所以说能不能做到这样,特定的区只能存放叶子结点的那些页,有一些区专门存放非叶子节点的那些页,这样在进行定位数据的时候直接定位到叶子节点所在的区即可

3)一个段里面可能有多个区,非叶子节点多分配几个区,这些区都在一个段里面

存储引擎:指用来存储,处理和保护数据的核心服务,也就是数据库的底层软件组织

二)索引失效的场景:

最左匹配原则:指的是索引以最左边为起点的任何连续的索引都是可以匹配上的,但是当遇到范围查询(>,<,like,between)就会停止匹配,都是从联合索引的最左边开始进行匹配的

1)最左前缀法则和and的字段前后顺序没关系

2)通过key_len来看看哪个字段用到了索引

3)MYSQL可以为多个字段创建索引,一个索引可以包含16个字段,对于多列索引来说,过滤条件使用索引必须按照索引创建的顺序,依次满足,一旦跳过了某个字段,那么索引后面的字段都是无法使用的,如果查询条件不包含第一个字段,那么后面的索引都是不会使用的

这里是不会走索引的,因为只有第一种情况下才是最确定的情况,b c情况不确定,所以不走索引

主键插入顺序:

对于InnoDB存储引擎来说,在我们没有显示的创建索引的时候,表中的数据实质上都是存在于聚簇索引的叶子节点上面的,而数据是存放在数据页中的,数据页和记录又是按照主键从小到大的顺序来排列的,如果插入的记录的主键值是依次增大的话,那么每当插满一个记录页之后就在开辟一个记录页继续插,如果说插入的主键值忽大忽小就比较麻烦了,假设此时某一个记录页的数值已经存满了,主键值在1-100之间

可是这个数据也此时已经满了,应该怎么办呢,此时需要将页分裂成两个页面,把本页中的一些记录移动到新创建的页中,然后再找到合适的位置进行插入,页面分裂和记录移动代表着是性能损耗,所以尽量让插入的记录依次递增,这样就不会发生性能损耗了

一)不满足最左匹配法则: 

二)索引列使用了列运算:

select * from user where id+1=4;

三)索引列使用到了函数:

select * from student where left(student.name,3)="abc",先针对于字段做用函数再依次进行比较,千奇白怪的函数多了去了,必须要进行全表扫描

四)模糊查询以%开头:

五)隐式类型转换:

如果索引列存在类型转换,那么也不会走索引,比如说某一列是字符串类型,但是查询的时候使用int类型的值就会导致索引失效

select * from user where username=123

六)范围条件右边的列索引失效,出现><<=>=between,在应用开发中例如说金额查询,日期查询往往都是范围性查询,应该将查询条件写在where的最后面,在创建的联合索引中应该把范围涉及到的字段写到最后(注意是写在索引的最后面,在等值查询的索引列写在前面,范围性索引列卸载联合索引的最后面)

下面这种情况范围查询后面的字段索引失效:

但是如果修改成这样子:create index idx_age_name_classid on student(age,name,classID)

这时候就使用到了全部的索引,主要取决于索引中先写谁后写谁

七)<>或者是!=导致索引失效,也是一种不确定的情况

八)is null可以使用索引,is not null不可以使用索引(遍历索引项一个一个的全都看一遍)

最好是在进行设计数据表的时候将字段设置成not null约束,这样在进行查询的时候就不会使用is not null了,如果说非要使用null值,可以将int类型的字段默认值设为0,字符类型的默认值设置成空字符串,同理,在查询中使用到not like也是无法使用到索引的,也会导致全表扫描;

九)or前后存在非索引列,索引失效,也就是前后的字段必须都有索引

select * from user where userID=1 or age=10,假设这里面的userID有索引,age没有索引,那么此时查询一定会走全表扫描,因为就算userID走的是索引,但是仍然要全表扫描找age=10的字段,此时还不如走一下全表扫描来的快

如果使用了or关键字,那么它前面和后面的字段都要加索引,不然所有的索引都会失效,这是一个大坑。

10)数据库和表的字符集统一使用utf8b4,统一字符集可以避免由于字符集转换而产生的乱码,不同的字符集进行比较从而进行转换的时候涉及到转换函数,导致索引失效

12)索引列上进行对比:可以导致索引失效

13)in通常是走索引的,当in后面的数据在数据表中超过30%的匹配时,会走全表扫描,即不走索引,因此in走不走索引和后面的数据有关系,exists也走索引

in有可能走索引也有可能不走索引

14)如果是小范围的查询,还是走索引的,type属于range,在随着数据量的增大时会自动进行全表的扫描(并且与要查询的结果是否包含在索引树中决定走index还是all);

not in则不走索引

对于order by来说:

1)没有加where或者是limit不走索引

2)对多个索引进行order by操作 

3)不满足最左匹配原则

4)不同的排序

聊聊索引失效的10种场景,太坑了 - 知乎 (zhihu.com)

三)索引覆盖:

覆盖索引是指在某一次查询里面,某个索引的值已经包含了所有的查询需求,此时不需要再次进行回表查询了,假设下面的查询针对于age建立了索引

 

假设现在针对于c2列进行查询,select * from user where c2>=1andc2<=5,如果需要进行回表查询的话,那么这些数据可能在一个数据页中也有可能不在同一个数据页中,当然这些页也是连续的,64个页在同一个区里面,很有可能索引中挨着的字段实际上存储在不同的区中,回表的时候使用随机IO的,所以覆盖索引就将将随机IO转化成顺序IO

四)索引下推:

索引下推是MYSQL5.6引入的新特性,是一种存储引擎使用索引过滤数据的一种优化方式

索引下推是指在索引在进行遍历的过程中,对索引中包含的字段先进行判断,先直接过滤掉不满足要求的记录,减少回表查询的次数就叫做索引下推,Using index condition,只有触发联合索引才能出发索引下推

索引中有这个字段,但是这个字段又索引失效了,没有办法使用到到这个索引,就可以使用索引条件下推来对索引的记录进行过滤

1)select * from user where key1>10 and key1 like "%张三";

正常情况下,我们是来考虑key1字段大于10会使用到索引,like查询以%开头是不会使用到索引的,所以程序会先根据key1>10在非聚簇索引找到主键ID,进行回表查询的时候再去找key1字段like"%张三"这样的一个查询过程,但是真实的查询优化器,现根据key1>10找到了100条记录,但是此时不会发生回表查询,二十次是根据key1 like "张三"的条件进行索引下推,然后根据这个条件进行判断,然后减少回表的次数

同理 select * from user where a>10 and a like "张%",此时是不是a like查询条件不会使用到a索引,但是实际上使用到了使用到了索引下推,但是本质上还是用到了a索引

2)index(a,b)创建联合索引explain select * from user where a="01" and b like "%张",此时虽然 b字段不会使用到索引,但是可以使用到索引下推来进行过滤,减少回表查询的次数,从而减少随机IO的次数

3)ICP不用于覆盖索引和覆盖索引

4)select * from user where name like "张%三" and age=10,这个语句在用到索引搜索树的时候,只是找到第一条满足条件等于张的记录

1)在MYSQL5.6之前,因为使用的是联合索引,它会先找到姓张的字段所有的记录的主键ID,而不是根据先找到name=张某某的所有的人的记录在接下来根据这些张某某的字段再来对age做进一步的筛选,而是再拿着第一条是张某某的所有四条记录的主键ID进行回表查询找到完整记录,根据完整的记录然后再来进行判断age再来判断ismale;

2)只有到了MYSQL5.6之后才会进行联合查询的时候才会使用到多个查询条件,根据姓张的找到了好几条,然后再根据age筛选出年龄不符合条件的,只剩下两条在进行回表查询,根据age过滤减少回表查询的次数就叫做回表查询

3)索引下推主要体现的是索引中有这个字段,但是这个字段又失效了,没有办法使用到索引的这个部分,那么这个时候使用索引条件的下推来对会表的失效的索引列再做一个筛选判断,所以说谈到索引下推就是首先使用联合索引,第二下推的条件列索引失效,但是程序还是根据这个索引列进行了筛选了数据从而减少了回表查询的次数

4)补充:varchar(50)对应的keylen是多少?

utf8一个字符占用三个字节,有一些特殊的复杂的汉字是不能够表示的

存储引擎使用的是utfmb4,这个字符集可以支持所有的复杂的汉字,一个char是使用4个字节来存储的,隐含的条件是varchar类型是不定长度最大长度是50的一个字符串类型,具体字符串所占用的空间大小是不定的,是根据值的大小来做申请空间的,还要额外使用2个字节来存储实际字符串不定长所存储的长度;

5)对于age来说,keylen是多少呢?5,因为额外要使用一个字节来判断是否为null,相当于是否为null,如果是not null,那么keylen就是4;

1)最左前缀法则比如说(a b c)建立联合索引,select * from user where a=10 and b>8 and c=3,它这个只有a和b走索引,b是根据索引列进行范围查询,但是我感觉他为啥c也不走索引呀,这样MYSQL设计有点不好吧,他这个联合索引是先按照a排序,a相同,再按照b排序,b相同再按照c排序,就我上面的那个SQL语句,完全先找a=10的,再从a=10的找b大于8的,然后在b大于8的找c=3的这样子也挺快呀?但是实际上c不走索引感觉有点不好,MYSQL为啥要这样子设计呀 

2)况且来说遇到"张%"后面的字段为什么也不是用到索引呀index(a,b,c)

select * from user where a=10 and b like"张%" and c=10,为什么c字段不使用到索引呢?

为什么遇到><like between就停止匹配了,不是精准查询,此时就需要遍历索引中的数据

此时索引底层是怎么走的呢?

1)针对c字段进行索引下推

2)根据a=10和b进行查询到主键ID,进行回表查询找到c=10的记录过滤

2)索引下推有一个问题:

那么如果针对于(name,age,address)变成select * from user where name="zhangsan" and age>10 and address like "%西安xx" and ismale=1,也会针对于address进行下推吧,虽然age以后字段不在走索引,但是优化器针对于address进行了下推,是不是经过优化器优化导致最终的结果是address也使用到了索引,因为使用address的条件进行了下推,所以也叫作address使用到了索引)(如果没有索引下推,根据索引失效原则,是不是address没有用索引

所以说MYSQL针对于索引下推的字段到底用没用到索引呢?那说下推的字段叫做使用到了索引对吗?但是MYSQL的确是针对于这个条件进行了过滤呀?

如果索引下推是针对于联合索引中某一个索引列出现了范围查询导致(比如说like between ><)之后一个字段不再使用联合索引,那么索引下推就是根据后面的这个索引列进行判断

3)in exists not in和not exists到底走不走索引呀

4)范围条件右边的列索引失效,是不是根据同一个索引或者是联合索引来说的

select * from user where a>10 and b=10,此时a和b建立了不同的索引,此时a和b都是用索引吧或者说a使用索引或者是b使用索引,但是如果是a,b是联合索引,上面的这一条SQL语句b就一定不是用到索引了吧

5)select * from user where a like "张%%%" and c>10,此时index(a,b,c)此时c索引下推吗?select * from user where b like "张%%" and c>10 此时是不是索引不下推了

6)当事务进行回滚之后,redolog.file中的日志被删除,innodb引擎还会保留undog日志的内容吗?为什么?

五)事务的四大特性:

1)原子性:指的是事务本身是一个不可分割的工作单位,要么全部执行成功,要么全部执行失败,要么转账成功,要么转账失败,不存在中间状态,如果无法保证原子性,那么就会出现数据不一致的场景A账户减去100元,B账户增加100元增加失败

2)一致性:指的是事务在执行前后,MYSQL中的数据始终处于一种合法性的状态,也就是说在进行转账前后,A,B两个人的总钱数是不变的

3)持久性:指的是事务一旦提交,提交的数据对于数据库的修改就是永久性的,接下来的操作和数据库的故障不应该对其有任何的影响

持久性是根据重做日志来实现的,但事务针对于数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,提交后在针对数据库中的数据进行修改,即使数据库的系统崩溃,数据库重启之后也是可以找到重做日志中的内容来进行恢复

4)隔离性:指的是一个事务的执行不能被其他事务所干扰,即一个事务内部的操作以及使用到的数据对于并发的其他事务来说是隔离的,并发执行的相互事务之间不能干扰

开启一个事务:start transaction或者是begin表示开启一个事物,后面可以加上一个read only表示当前事务是一个只读事务,也就是该事务只能读取数据而不能修改数据

1)savepoint 保存点名称,在事务中创建保存点,方便后续针对于保存点进行回滚,一个事务中可以存在多个保存点

2)删除某一个保存点:release +保存点名称

3)是事物回滚到某一个保存点:rollback to savepoint 

set autocommit=true;

show variables like "autocommit"

在autocommit=true的情况下,每一个DML语句都是一个事务,只是针对DML语句

脏读:读到了其他事务没有进行提交落盘的数据

不可重复读:读到了其他事务提交的数据,两次查询数据的过程中数据被其他事务修改了,侧重于修改

幻读:两次查询数据的过程中数据被其他事务新增了数据或者是删除的数据,侧重于新增

串行化:保证数据的完全隔离,

查看MYSQL的隔离级别:

select @@global.transaction_isolation,@@transaction_isolation;

六)MYSQL是如何保证数据库四大特性的?事务底层是怎么实现的?

一致性:两个数据源操作的时候前后数据始终处于合法的情况

隔离性:指的是并发事务之间相互执行时相互之间对于结果的可见性

1)保证原子性:undolog回滚日志:

进行事务一个MYSQL非查询操作之前会现在回滚日志会先记录当前执行的这个操作,当进行转账操作的时候,张三要捡钱,一旦发现转账失败,那么直接进入到undolog日志查询这个操作,然后在进行相反操作即可,直接给张三加钱即可;

2)保证持久性:redolog重做日志将修改的数据放到redolog里面,可以减少IO操作,因为每一次写操作都将数据写入到磁盘中效率是非常低,还会增加随机IO,事务提交以后再把reddolog日志刷新到磁盘里面,还可以在系统恢复和断电以后再进行数据恢复;

3)隔离性:MVCC(多版本并发控制,不能完全解决隔离性)和锁来实现的

4)一致性:是通过各种约束,主键外键唯一性约束+其他三大特性来保证的

既然这俩日志也是持久化,但是为什么不把数据直接持久化到磁盘上面的数据里面呢?

而是直接放到日志里面?

保证MYSQL效率,保证系统崩溃恢复的主要手段,减少IO操作

1)因为持久化redolog速度非常快,效率很高,因为重做日志不需要考虑任何东西,只是单纯的数据的保存,存储的结构非常简单

2)但是如果一直更新磁盘上面的表数据,因为MYSQL要把这些数据分门别类地存放到磁盘上改存的地方,比如说A表已经存放了2亿条数据了,如果你此时向这个表中新增数据是很慢的,如果此时这个表里面有索引,此时不但要填充数据,还需要填充索引的值,所以说直接更新数据效率是很低的,但是redolog直接只存放数据信息,下一次直接进行刷盘即可,保证MYSQL效率,保证IO次数尽量少,还可以事务未提交防止断电重启数据丢失

redolog是用来保证事务的持久性的,它主要是用来记录事务提交时候的数据也的修改操作,该日志文件由两部分组成,重做日志缓冲区(redologbuffer)和重做日志文件,前者是在内存中,后者是在磁盘中

1)修改数据是先在buffer pool中修改脏页要依靠后台线程以一定的频率一定的时间刷新到磁盘中,从而最终保证缓冲区的数据和磁盘中的数据保持一致,但是脏页的数据并不是实时刷新的,而是在一段时间后由后台线程将脏页的数据刷新到磁盘中,但是假设脏页中的数据在想磁盘中刷新的时候出错,此时内存中的数据没有刷新到磁盘中,但是事务已经提交了,但是最终脏页刷新失败了,这样就会使得持久性得不到保障

2)于是就有了redologbuffer,然后又有了redo.file,如果脏页刷新失败了,那么可以依靠redo.log日志来进行恢复,所以redolog主要用于bufferpool将脏页刷新到磁盘中发生错误的时候来进行数据恢复的,但是为什么不把bufferpool直接刷新到ibd文件呢?后面有解释

3)WAL:先写日志,在写记录

七)MYSQL中的redolog和undolog日志详解:

事物的四大特性本质上是依靠日志来实现的,事物的隔离性是依靠锁机制来实现的,但是事务的原子性,持久性和一致性是依靠事务中的redolog和undolog日志来实现的

1)redolog被称之为是重做日志,提供再写入操作,用于恢复提交事务修改的页操作,用于保证事务的持久性,他的底层记录的是物理级别上面的页的操作,比如说页号XXX,偏移量XXX位置写入了XXX数据,主要是为了保证数据的可靠性

2)undolog被称之为是回滚日志,回滚记录行到某一个特定版本,用于保证事务的原子性一致性,他也是存储引擎层生成的日志,记录的是逻辑操作日志,比如说在针对某一行进行了insert操作,那么undolog就纪录一条与之相反的delete操作,主要用于事物的回滚,本质上记录的是每一个修改操作的逆操作和一致性非锁定读,undolog回滚记录到某一种特定的版本MVCC,就是多版本并发控制;

一)redolog日志详解:redologbuffer(重做日志缓冲区)+red.file(重做日志文件)

redolog是重做日志,用来实现事务的持久性,该日志文件由两部分组成,重做日志缓冲区和重做日志文件,前者是在内存中,就这是在磁盘中

1)innodb存储引擎是以页为单位进行管理存储空间的,在真正访问页面之前,需要把磁盘上面的页缓存到内存中的buffer pool中才可以进行访问,所有的变更必须先更新缓冲池中的数据,然后将缓冲池中的脏页以一定的频率刷入磁盘中,脏页就是内存中改了但是磁盘中没改

2)为什么说是以一定的频率刷新到磁盘中呢?

因为从内存写入磁盘本身就慢,经常刷盘,效率很低,刷盘中的页本身可能是不连续的,随机IO,性能不太好

3)假设事务里面有很多DML操作,这些DML操作都是操作的是内存中的数据,这个时候事务提交了,内存中的数据的修改都完成了,此时磁盘还没有被写入刷盘,逻辑上磁盘上的数据应该被修改了,但是此时commit操作MYSQL宕机了断电了,内存中的数据还没刷盘,如果这个时候没有redlog日志,再次重启内存中的数据没了,磁盘上的数据还是从前的,相当于事务从来没有执行过,磁盘上的数据没有修改,此时事务就不保证持久性;

4)一方面缓冲池的存在可以保证数据的最终落盘,但是由于缓冲池并不是由每一次变更的时候就进行触发的,而是说是由master线程隔一段时间来进行处理的,所以最坏的情况下就是事务提交了,刚写完缓冲池,数据库就宕机了,那么这段数据就是丢失的,无法恢复,另一方面是说事务包含持久性的特质,就是说对于一个已经提交的事务,即使在事务提交以后系统发生了崩溃,这个事务对于数据库的修改也是不能丢失的,那么如何来保证持久性呢?

一个简单粗暴的方法就是在事务提交完成之前将该事务修改的所有页面都刷新到磁盘上

就是一旦内存中的数据被修改了,那么马上就去更新磁盘上的数据,实时的修改,可不可以呢?别等到事务提交的时候才去更新磁盘上的数据

4.1)修改量和刷新磁盘工作量严重不成比例:

4.2)随机IO太多:修改的页可能不连续

redolog的好处和特点:

1)redolog日志降低了刷盘频率,减少磁盘IO

2)redolog日志占用的空间非常小,只是存储表空间ID,页号偏移量以及要更新的值,所占用的空间非常少,刷盘就快

3)redolog是按照顺序写入磁盘的:再执行事务的过程中,每执行一条SQL语句,就可能产生很多redolog日志,这些日志是按照产生的顺序写入到磁盘中的,也就是顺序IO效率比随机IO快

4)在事务执行的过程中redolog日志会不断地进行记录

redolog和binlog的区别:

redolog是在存储引擎层产生的,而binlog是数据层产生的

假设有一个事务,对MYSQL的表做10W次的记录插入,在这个过程中是不断地向redolog中进行记录,而binlog是不会记录的,直到事务提交binlog才会记录

binlog日志,比如说在主从复制的环境下,主机负责写,从机负责读,那么如何来保证主从数据的一致性呢?就是依靠binlog二进制来实现的,比如说现在在主机中新增了一条记录,那么就在binLog中记录一下,从机从binlog中获取记录;

整个redolog可以分成两个部分:

1)在MYSQL服务器启动的时候就像操作系统申请了一大块的连续内存空间,翻译成中文就是redlog日志缓冲区,这篇缓冲区被划分成若干个连续的redo log block,一个大概占用512字节左右

2)重做日志文件,保存在磁盘上,默认存储位置是当前的数据目录,先把文件空间开辟出来,是把空间先占用好

二)redolog执行流程:现在以更新事务为例

1)假设现在要进行更新操作,那么首先MYSQL会将要更新的数据的表加载到内存中的buffer pool中

2)每当执行事务中的一条语句的时候,修改完成之后,先在内存中的redologbuffer中做一个记录,就是生成一条重做日志写入到redologbuffer中,此时记录的是数据被修改后的值

3)当事务进行提交的时候,将redologbuffer中的内容刷新到redologfile中,redologfile采用追加写的方式;

4)事务提交后,定期地将内存中的数据刷新到磁盘中

redolog的刷盘策略:将redologbuffer中的数据放在redlogfile中 

1)注意:redologbuffer将数据刷盘到redologfile的过程并不是真的刷新到磁盘上去,只是刷入到文件系统缓存上去了(page cache),这是现代操作系统为了提升效率而做的优化,真正的写入是由操作系统本身决定的(比如说page cache足够大了),那么对于innodb存储引擎来说,如果真的交给系统来进行同步,那么同时如果系统宕机了,那么数据也就丢失了,虽然整个系统宕机的概率还是比较小的

2)真对于这种情况,InnoDB给出了innodb_flush_log_at_commit参数,该参数再进行控制commit提交事务的时候,如何将redoogbuffer中的数据刷新到磁盘中的

只要修改数据,redo log buffer就会实时地做记录,参数影响的只是log buffer中的刷盘策略

2.1)设置为1:每一次进行事务提交的时候都立即把redologbuffer中的数据刷入到pagecache中,再从pagecache中刷新到磁盘上,是一个默认值,马上,马上;

只要事务提交成功,马上马上,但是效率最低,IO次数最多;

2.2)设置为0:事务提交的时候啥也不干,什么都不干,全权交给后台线程管理,InnoDB本身后台有一个线程,每隔1s就会将redologbuffer中的数据写入到文件系统缓存的pagecache中,然后再调用刷盘操作,最放飞,有可能在1s内丢失数据;效率最高

2.3)设置为2:表示每当事务提交的时候都只是立即将redologbuffer中的数据写入到page cache中,由操作系统本身决定什么时候将page cache中的数据写入到redo.file中

假设此时事务0.5s执行完成了,此时MYSQL立即把redologbuffer中的数据存放到文件系统缓存中,然后再等了0.5s以后,操作系统将文件系统缓存中的数据放到磁盘文件上,但是后0.5s后,MYSQL数据库服务器挂了,但是只会影响到redo log buffer中,但是数据已经更新到文件系统缓存中,只要操作系统不挂,就没事,IO次数剧中

1)InnoDB存储引擎有一个后台线程,每隔1秒,就会把redo log buffer中的内容写到文件系统缓存(page cache)然后调用刷盘操作

2)一个没有提交事务的redo log日志,也可能会刷盘,因为在事务执行过程redo log记录是会写入redo log buffer中,这些redo log记录会被后台线程刷盘

3)除了后台线程每隔1s的刷盘策略之外,还有一种情况就是当redo log buffer中的占用的空间达到一定程度的时候,后台线程会默认进行刷盘策略

三)如何理解undoLog日志:MVCC+保证事务的原子性

redolog日志是事务持久性的一个保证,但是undolog日志是事务一致性的保证,它是在事务更新前的前置操作写入一个undo log,记录的是逻辑日志记录的是每一步执行的是什么操作

每进行针对一条数据记录进行修改的时候,这里的改动可以是insert delete和update,都是需要留一手的,需要把回滚的时候所需要的东西记录下来

1)当要去插入一条记录的时候,需要将这条记录的主键值记录下来,之后进行回滚的时候只是需要将这个主键值所对应的记录直接删除掉就好了,真对于每一个insert操作,都要进行一次delete操作

2)当要去删除一条记录的时候,至少要把这条记录的所记录的内容要全部记录下来,这样在进行回滚的时候再把这些内容所组成的记录更新成旧值就好了,对于每一个delete操作,innodb引擎回滚的时候都要执行一次insert操作

3)当你进行修改一条记录的时候,至少要把这条记录之前的旧值全部记录下来,这样之后进行回滚的时候要把这条记录更新成旧值就好了,对于每一个update操作,innodb引擎会进行一次相反的update操作,将修改之前的行放回去

4)MYSQL把这些为了回滚而记录的这些内容称之为是撤销日志或者是回滚日志,但是由于查询操作并不会修改任何用户记录所以在查询操作进行的时候是不需要记录任何的undolog日志

5)除此之外undolog本身也会产生redolog,这是因为undo log也是需要持久化的保护

undolog日志的作用:

1)插入数据进行回滚但是不影响到由于插入数据新产生的数据页,undolog只是会撤销插入操作,但是不会回收数据页,数据也的空间已经开辟了,那么就是真的开辟空间,但是rollback只是把新增加的数据给删除了,但是这个物理层面上的数据页是不会消失的,MYSQL数据库同一时刻是有可能执行成百上千的事务的,不是只有你当前一个事务添加记录,而是成百上千的事务都有可能添加记录,此时有可能用的都是新的数据页,只是因为你当前的事务回滚把整个数据页都收回了,其他事务的数据就蒙了,只是在逻辑上将事务回滚了,但是物理层面上变化是无法改变了,是逻辑上而不是物理层面上;

2)MVCC:多版本并发控制:事务提交以后并不能直接删除undolog记录,因为还有其他事务会尝试通过undolog日志来读取行记录的版本,因此在事务进行提交的时候需要将undolog放到一个链表中,有后台线程来决定;

undolog的分类:

1)insert undolog:是指在insert过程中产生的undolog,因为在insert操作的时候,只是对事务本身是可见的,对其他事务是不可见的,这是事务隔离性的要求,因此该undolog可以在事务提交之后直接删除;

2)update undolog:记录的是delete和update操作产生的undo log,该undolog可能需要提供MVCC机制,因此不能再事务提交的时候就进行删除,提交的时候放入到undolog链表

undolog的结构:

1)undolog本身是采取段的方式,也就是回滚段,也被称之为是rollback segment,每一个回滚段都记录了1024个undo log segment,而在每一个undo log segment进行undo页的申请,最终在undo页中写具体回滚的记录,在以前innodb只是饱含着一个rollback segemnt,因此同时支持在线的事物限制为1024,但是后来innodb支持的事务时128个,所以将在线的事物支持数量增加到了128*1024个

2)undo页的重用:每当开启一个事务需要写undolog的时候,就需要先去undo log segment中去找一个空闲的位置,当有空闲位置的时候,就去申请undo页,在这个申请的undo页中进行数据的写入,MYSQL默认的页的大小时16K,如果一个事务去分配一个undo页的话,那么是十分浪费空间的,一个事务对应着一个页,如果数据无法写满,那么还是很浪费空间的

于是此时undo页就可以设置成重用了,当事务提交之后并不会立即的删除undo页,因为重用,所以这个undo页可能会夹杂着其他事务的undolog

当事务进行提交的时候,innodb存储引擎会做两件事情:

1)将undolog放入到列表中以方便后续的线程进行清理

2)判断undolog中的页是否可以重用,如果可以就分配给下一个事务进行使用

八)对于MVCC的理解:隐藏字段+undolog版本链+readview

MVCC:MVCC是通过数据行的多个版本管理(通过undolog进行记录)来进行实现对数据库的一个并发控制,就是为了查询一些正在被另一个事务更新的行,并且可以看到他们被修改之前的值(看到那个数据版本),这样在做查询的时候就不需要等待另一个事务释放锁了,MVCC针对于读不需要进行排队

1)当前读:读取的是当前记录的最新版本,读取的时候还要保证其他并发事务不会修改到当前记录,会针对于当前读的记录进行加锁,对于我们日常的操作,select.....lock in share mode,和select.....for update,update,insert,delete(排他锁)都是当前读

2)快照读:简单的select版本不加锁,就是快照读,快照读读取的是当前记录的可见版本,有可能是历史数据,不加锁,也是非阻塞读

行格式里面包含三个隐藏字段:

transactionID:每一次事务对某一条的聚簇索引记录进行改动的时候,都会把当前的事务ID给trx_id隐藏列;

roll_pointer:每次针对于聚簇索引记录进行改动的时候,都会把旧的版本写入到undolog日志中,然后这个隐藏列就是相当于是一个指针,可以通过它来找到修改前的信息

越从版本链向下数据就越旧一些,越往版本链向上数据就越新一些

undolog的版本链:不同事务或者是相同事务针对于同一条记录进行修改,会导致该纪录的undolog生成一个记录版本的链表,链表的头部记录的是最新的旧纪录,刚刚被修改过的原值,链表尾部是最早的旧纪录,所有的记录就是历史记录版本,当进行查询的时候,具体要返回哪一个数据版本完全是取决于MVCC中的ReadView

readview中包含着比较重要的四个内容:

1)creator_trx_id:创建这个读视图的事务ID,注意只有在针对表中的记录进行改动的时候,执行insert,delete和update语句的时候才会为事务分配事务ID,否则在只读事务中的事务ID默认都是0;

2)trx_ids:表示在生成readview是当前活跃的读写事务的列表,活跃事务指的是启动但是没有提交的事务,提交的事务就不再这个活跃列表中了;

3)up_limit_id:当前的活跃的事务最小的id

4)low_limited_id:表示生成的ReadView系统中的应该分配给的下一个事务的id值,low_limited_id指的是系统中的事务ID,要区分于正在活跃的事务ID,注意此时的low_limited_id并不是trx_ids里面的最大值,事务id是根据系统进行递增分配的,假设现在有三个事务,id为3的事务进行提交了,那么一个新的事务在生成读视图的时候,trx_ids的内容就是1和2,up_limited_id的值就是1,low_limited_id的值就是4,就是当前系统中的最大事务ID+1;

max_trx_id不是当前事务的最大活跃ID,是当前事务即将要向下分配的ID,下一个要分配的事务ID

trx_id代表的是当前被访问的记录的ID

trd_idx相当于是undolog中列表的一条一条记录,每一次遍历trd_idx都要套用上面的四条规则来进行判断当前事务是否可以读取该纪录

在一个事务中,你想获取快照数据时,会先从当前行开始比对事务id,符合读取的条件就使用,不符合就往undo log中找,继续读取链表的下一条数据

假设有了readview之后,再进行访问到某一条记录的时候,只需要按照下面的步骤进行判断记录中的某一个版本是否可见,先以系统中的undolog日志中的第一条记录开始进行访问,相当于是遍历整个链表,如果某一条undolog的记录满足下面的这四条规则,那么直接进行返回即可,如果不满足,那么直接遍历这个链表的下一条记录看看是否符合下面的四条规则

1)如果undolog中被访问版本的transaction_id属性值和当前记录中的readView中的creator_trx_id相同,意味着这个事务在进行访问他自己所修改的数据,该版本可以被这个事务可见;

2)如果undoLog中的一条这记录的当前版本的transaction_id小于当前readview中的up_limited_id值,表明生成的该版本的事务在当前事务生成readView的时候已经被提交,所以该版本可以被该事务访问;

3)如果被访问版本的trx_id属性值大于或者是等于readview中的low_limit_id值,表名生成该版本的事务在当前事务生成ReadView之后才开启,该版本不可以被当前版本的事物可见,当前修改这条记录的事务是在生成readView视图的事务之后才出现的,比如说一开始一个事务进行select操作此时时间是A,这个时候生成了读视图去undolog版本连链去读取数据,但是有一个事务在生成读视图的过程中修改了这一条数据此时刻B,但是第一个事务想读的是时刻A的数据,所以不会读这条被其他事务修改的数据;

4)如果被访问版本的trx_id属性值在ReadView中的up_limited_id和low_limited_id之间,那么此时还是需要进行判断trx_id属性值是否在trx_ids列表中

4.1)如果在,说明创建读视图生成版本的时候该事务还是处于活跃状态,那么就不可以读到该版本的数据

4.2)如果不在,说明创建读视图生成版本的时候该版本的事务已经被提交,该版本可以被访问

begin/start transaction命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向mysql申请事务id,但是如果这个事务操作只是进行查询操作的话,那么事务id就是随机值,这个时候如果生成读视图那么这个事务ID不是包含在活跃的读写事务ID里面的,这个时候如果这个没有执行过任何DML语句的事务开启查询操作,那么生成的读视图里面的creator_trx_id等于0,只有进行修改操作之后才会分配一个真正的事务ID,才是真正的活跃的事务ID,才是真的trx_id;

2)此时事务2执行提交操作,事务3再来执行事务进行提交操作,提交事务以后undolog不会立即删除,因为有可能有活动事务正在使用到这个undolog记录

3)事务4又开始进行修改

1)读已提交:每一次都会生成快照,当前这个情况下只会读取倒数第二条记录,只会读到其他事务提交的数据,快照读只能读取到最近一条更新事务的数据,后续进行读取的时候会复用这个readview

2)对于可重复读来说,只会生成一次快照读:解决可以看到其他事务提交的数据

 MVCC:

这两条记录交叉执行有可能吗?答案是不可能,因为update语句本身加锁

ServEriable隔离级别完全加锁

 

第一种和上面的一样

第二种:还是张三

第三种:还是张三

无论提交几次永远是张三

MVCC能否解决幻读问题?

查询分为两类:快照读(可以保证没有幻读问题,readView)和当前读(加上了for update读取当前信息)存在幻读问题

如何解决?通过锁机制,设置更高的事务隔离级别

解决幻读:给读加排他锁直接加表锁,其他事务想要进行插入是不可能的

1)没设计到修改的时候,MVCC 走进入事务时的快照读,就不会有幻读的

2)自身没有 update 操作的时候,别的事务 insert,他也是不会产生幻读的,只有自身的 update 等操作时,才会查询最新的结果

binlog日志:

首先使用vim或者是vi命令时无法查看此二进制文件下面的内容

  • 逻辑日志:可以简单理解为记录的就是sql语句
  • 物理日志:因为mysql数据最终是保存在数据页中的,物理日志记录的就是数据页变更

redolog是物理日志,记录内容是在某一个数据页上做了什么修改,是属于InnoDB存储引擎层产生的,但是binlog是逻辑日志,记录的是语句的原始逻辑,是在MYSQL的服务层产生的,类似于记录的是SQL语句,redolog保证了innodb存储引擎又恢复数据的能力,binlog则保证了MYSQL集群架构的数据一致性

1)binlog是mysql中的二进制日志,其记录了数据库发生更改的各种变化,所以通过binlog可以回滚或者恢复失误的操作, 恢复一般使用mysqlbinlog命令,该命令是mysql自带的,使用简单, 其运行的本质是将日志记录中的事件再次执行一遍

binlog 为二进制日志,主要作用是主备流复制、数据增量备份、误操作数据恢复,binlog 是数据库server 层支持的,独立于引擎之外,也就是说数据库的任何引擎都是支持binlog 的,redolog和 undolog 是 InnoDB 特有的

2)使用mysqlbinlog进行增量日志恢复最重要的就是确定待恢复的起始位置(start-position)和终止位置(stop-position),起始位置(start-position)是我们执行全被之后的位置,而终止位置则是故障发生之前的位置

3)中继日志(relay log)在从节点中存储接收到的 binlog 日志内容,用于主从同步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值