通俗易懂-mysql事务持久化实现

mysql是基于硬盘的数据库,所以很多优化都是基于硬盘的瓶颈来做的,为了更好的理解,先花点时间了回忆下硬盘的知识

硬盘

通俗易懂-mysql事务持久化实现_第1张图片

目前主流的有2种,一种是机械硬盘,一种是固态硬盘

大家知道,固态硬盘读写速度比机械硬盘块,而且快很多,那么快的原因是什么呢? 主要跟他们的构造和工作原理有关

机械硬盘结构
通俗易懂-mysql事务持久化实现_第2张图片

机械硬盘主要由,转抽,磁头,盘片组成,一个盘片上划分了很多个磁道,每个磁道上又进一步分了扇区
其中扇区是实际存储数据的地方,一个扇区的大小是512个字节,当对机械硬盘进行读写时需要经过三个步骤

  • 1.定位到磁道
  • 2.等待旋转到对应扇区
  • 3.开始读写

其中步骤1,2的定位操作都是机械定位

固态硬盘结构
通俗易懂-mysql事务持久化实现_第3张图片

固态硬盘主要有电路,闪存芯片组成,其中闪存芯片是实际存储数据的地方
对固态硬盘的读写分两步

  • 1.定位到闪存芯片
  • 2.开始读写

其中步骤1的定位是电子定位

电子定位肯定比机械定位的速度要快,这也是固态硬盘读写速度快的主要原因

硬盘比较底层,写程序的时候也接触不到,我们更多是与文件系统打交道。

有人说过一句的名言:“计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决” 文件系统,对于硬盘和上层应用程序来说就是这个中间层,那么这个中间层解决了什么问题呢?

文件系统干了什么事?

通俗易懂-mysql事务持久化实现_第4张图片

主要就一个: 扇区变块
(默认:1block=8扇区=4K)

块这个概念我们很熟悉了,块是由多个连续组成

为什么要把扇区变为块呢?

假设一个要写1M的数据,直接操作扇区的块需要的写次数是 1024*1024/512=2048,意味转需要2048次的机械定位
如果操作块,需要写次数256次,直接提升了磁盘操作的效率,但是带来的就是磁盘碎片,磁盘碎片和效率比肯定效率更重要,毕竟磁盘的成本低

到此我们知道为什么不直接与硬盘打交道,额是与文件系统打交道的原因?

那么与文件系统打交道都有哪些方式的呢? 2种:

  • 随机读写
  • 顺序读写

先看下随机读写是怎么操作的?

假设要创建一个7kb的数据文件

通俗易懂-mysql事务持久化实现_第5张图片

通过对读写过程的分析我们知道,随机读写模式,空间分配策略是按需分配,要多少就分配多少,但是分配的block之间不是连续,由于不是连续的,磁头需要频繁切换,这是随机读写模式的瓶颈

顺序读写是怎么操作的?

通俗易懂-mysql事务持久化实现_第6张图片

顺序读写解决了磁头频繁切换的问题,因为block是连续的

这种模式不常见因为要采用这种方式需要两个条件:

  • 1.文件大小固定
  • 2.预分配磁盘空间

所以通过分析,我们平常写日志用的是随机读写模式,因为文件大小一般都是不固定的

如何印证下顺序读写和随机读写区别?
查看文件的存储地址

#使用debugfs来查看文件存储的block地址
#查看文件对应的inode信息
stat file  | grep Inode
#查看文件存储的block地址
debugfs -R "stat " /dev/vda1

随机读写文件存储block地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tGC7tWb6-1573913769588)(media/15738994378780/15739026489563.jpg)]

通俗易懂-mysql事务持久化实现_第7张图片

结论: 顺序读写block地址是连续,随机读写block地址是不连续的

顺序读写比随机读写快,那么到底快多少?

通俗易懂-mysql事务持久化实现_第8张图片

小结一下

  • 磁盘最小的磁盘单位是扇区, 大小为512字节
  • 文件系统为了提升存储效率,对扇区进行的划分,将多个连续的扇区划分一个block,系统默认block=4k
  • 文件系统提供两种读写模式,顺序读写,随机读写,默认为随机读写
  • 文件系统的瓶颈的原因主要受限于受磁盘的工作原理,读写都需要经过磁头的机械定位

操作系统如何保证上层应用程序对磁盘的读写性能?

不管是随机读写还是顺序读写都绕不开磁头机械操作,

那么对于多任务的操作系统,os如何保证上层应用程序对磁盘的读写性能?

性能不够缓存来凑
通俗易懂-mysql事务持久化实现_第9张图片

我们知道linux为了保证系统的安全,按照运行空间,分了内核空间和用户空间,文件系统运行在内核空间,上层应用程序运行在用户空间,为了提高文件的操作操作,上层应用并不直接操作磁盘,而且先写到文件buffer(一块内存),buffer的内容按照一定的策略异步刷新到磁盘,读的时候先读cache,cache读不到再去磁盘上读,这跟我们的用redis差不多,先读redis,读不到再去读库

文件的写入过程默认是异步的,又叫延迟写

buffer和cache使用情况

free –m

红框内就是buffer和cache
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MbFnj81f-1573913769597)(media/15738994378780/15739035342368.jpg)]

当系统内存不够时,系统的回收这部分内存,所以系统可用内存=total-used+buffer+cache

磁盘异步刷新策略是?
三种:

  • flush每隔5秒执行一次
  • 内存中驻留30秒以上的脏数据将由flush在下一次执行时写入磁盘
  • 若脏页占总物理内存10%以上,则触发flush把脏数据写回磁盘

策略值可用命令修改
通俗易懂-mysql事务持久化实现_第10张图片

系统突然宕机,断电,数据一致性如何保证?

为了解决数据丢失问题,linux系统提供了fsync,fysnc的作用是直接将数据刷新到磁盘,下面两个代码段是写文件,一个是延迟写入,一个是实时写入

通俗易懂-mysql事务持久化实现_第11张图片

进入正题-mysql事务持久化实现

mysql实现事务的持久化面临哪些挑战?

数据库特点:高性能
持久化特点:磁盘瓶颈

  • 磁盘写入物理瓶颈
  • 切入到内核态,用户太切换到内核态的性能损耗

高性能 与 持久化 存在矛盾
数据库选择了高性能

要高性能,毫无疑问在内存里面操作是性能最高的

所以Mysql对于数据的修改总是先修改内存中的数据,按照功能
划分为,数据buffer,索引buffer,锁buffer,relog等。。
通俗易懂-mysql事务持久化实现_第12张图片

事务具有四个特性:

  • 原子性
  • 隔离性
  • 一致性
  • 持久性

我们看这个事务,很简单,一个金额加,一个金额减

通俗易懂-mysql事务持久化实现_第13张图片

首先修改在内存里面修改金额,因为要支持回滚,所以修改前的数据要放到一个地方存起来,这个地方就是undo log,就是右边绿色的部分,当执行回滚操作时,直接把undo log的覆盖回来就行

修改后,数据buffer的内容定期同步到磁盘,落地的路径:

数据buffer-》os bufer-》硬盘

一个问题: 修改数据之后,数据buffer直接刷到os buferr ,然后调用磁盘同步函数fsync,不就可以直接实现持久化吗?

是的,可以这么做,mysql为啥没有这么做,主要原因是数据buffer直接落地的过程磁盘是一个随机写的过程,事务持久化的目的是保证数据不丢,那换个角度想,如果丢了可以能够找回来是不是也行?

是的,mysql,就是按照这个思路来实现持久化的,叫做WAF技术=write ahead log(写前日志)

  • 1.在修改数据buffer前,把更新后的值写入到redo log中(重做日志)中
  • 2.redo log落地后,再去修改数据buffer的值
  • 3.当突然断电后,数据以redolog中的值为准来恢复

根据分析redo log还是要落地才能保证持久化,跟在数据buffer修改后,直接持久化有啥不同呢?
主要是2点:

  • 1.数据buffer直接落地,磁盘是随机写,Redo log 是顺序写
  • 2.redo log只能追加,而且与其他事务共享,提高的写的效率

所以mysql是否持久化的关键要看redo log的持久化策略,mysql提供了三种redo log持久化策略

  • 0:只写redo log,每秒落地一次,性能最高,数据一致性最差,如果mysql奔溃可能丢失一秒的数据
  • 1:写redolog buffer同时落地,性能最差,一致性最高
  • 2:写redo log buffer同时写入到os buffer,性能好,安全性也高,只要os不宕机就能保证数据一致性
    通俗易懂-mysql事务持久化实现_第14张图片

所以数据使用哪种持久化方式要看应用具体场景

  • 支付类场景数据一致性要求高,适合1
  • 文章评论等场景,适合0
  • 订单类个人认为,适合2

如何查询mysql 使用了持久化哪种策略?
通俗易懂-mysql事务持久化实现_第15张图片
由于redolog要实现顺序写,所以文件大小要是固定的

不同持久化方式的对mysql性能影响?
看一个例子

#创建test_load表
CREATE TABLE `test_load` (
  `a` int(11) DEFAULT NULL,
  `b` char(80) DEFAULT NULL
) ENGINE=InnoDB
#创建一个存储过程
delimiter //
create procedure p_load(count int unsigned)
begin
declare s int unsigned default 1;
declare c char(80) default repeat('a',80);
while s <= count do
insert into test_load select null,c;
set s = s+1;
end while;

commit;
end;
//
delimiter ;

存储过程p_load的作用是将数据不断的插入test_load表中,并且每次插入就显示的就进行一次commit操作,在innodb_flush_log_at_trx_commit设置不同值时,调用存储过程记录插入10万行记录所需要的时间

innodb_flush_log_at_trx_commit=0
通俗易懂-mysql事务持久化实现_第16张图片

innodb_flush_log_at_trx_commit=1
通俗易懂-mysql事务持久化实现_第17张图片
innodb_flush_log_at_trx_commit=2;
通俗易懂-mysql事务持久化实现_第18张图片

innodb_flush_log_at_trx_commit 1万行插入耗时
0 0.67s
1 13.93s
2 0.93s

性能结果跟我们预期的一样,0最好,1最差,2中间

总结

  • Mysql由于cpu速度与磁盘速度的鸿沟,引入缓存池的概念,数据的写都是先在内存中操作,然后异步刷新到此磁盘
  • Mysql buffer刷新到磁盘的过程是一个随机写的过程
  • Mysql通过redo log 顺序写的优势来保证内存与磁盘的数据一致性
  • Redo log的刷盘策略决定了mysql事务的强一致性,还是弱一致性
  • 事务的设置强一致性还是弱一致性要看具体使用场景

你可能感兴趣的