前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mysql系列第二十五讲 mysql如何确保数据不丢失?有几点值得我们借鉴

Mysql系列第二十五讲 mysql如何确保数据不丢失?有几点值得我们借鉴

作者头像
易兮科技
发布2020-10-23 11:29:14
3030
发布2020-10-23 11:29:14
举报
文章被收录于专栏:CSDN博客专栏

Mysql系列第二十五讲

预备知识

  1. mysql内部是使用b+树的结构将数据存储在磁盘中,b+树中节点对应mysql中的页,mysql和磁盘交互的最小单位为页,页默认情况下为16kb,表中的数据记录存储在b+树的叶子节点中,当我们需要修改、删除、插入数据时,都需要按照页来对磁盘进行操作。
  2. 磁盘顺序写比随机写效率要高很多,通常我们使用的是机械硬盘,机械硬盘写数据的时候涉及磁盘寻道、磁盘旋转寻址、数据写入的时间,耗时比较长,如果是顺序写,省去了寻道和磁盘旋转的时间,效率会高几个数量级。
  3. 内存中数据读写操作比磁盘中数据读写操作速度高好多个数量级。

mysql确保数据不丢失原理分析

我们来思考一下,下面这条语句的执行过程是什么样的:

代码语言:javascript
复制
start transaction;
update t_user set name = '易兮' where user_id = 666;
commit;

按照正常的思路,通常过程如下:

  1. 找到user_id=666这条记录所在的页p1,将p1从磁盘加载到内存中
  2. 在内存中对p1中user_id=666这条记录信息进行修改
  3. mysql收到commit指令
  4. 将p1页写入磁盘
  5. 给客户端返回更新成功

上面过程可以确保数据被持久化到了磁盘中。

我们将需求改一下,如下:

代码语言:javascript
复制
start transaction;
update t_user set name = '易兮' where user_id = 666;
update t_user set name = 'javacode2018' where user_id = 888;
commit;

来看一下处理过程:

  1. 找到user_id=666这条记录所在的页p1,将p1从磁盘加载到内存中
  2. 在内存中对p1中user_id=666这条记录信息进行修改
  3. 找到user_id=888这条记录所在的页p2,将p2从磁盘加载到内存中
  4. 在内存中对p2中user_id=888这条记录信息进行修改
  5. mysql收到commit指令
  6. 将p1页写入磁盘
  7. 将p2页写入磁盘
  8. 给客户端返回更新成功

上面过程我们看有什么问题

  1. 假如6成功之后,mysql宕机了,此时p1修改已写入磁盘,但是p2的修改还未写入磁盘,最终导致user_id=666的记录被修改成功了,user_id=888的数据被修改失败了,数据是有问题的
  2. 上面p1和p2可能位于磁盘的不同位置,涉及到磁盘随机写的问题,导致整个过程耗时也比较长

上面问题可以归纳为2点:无法确保数据可靠性、随机写导致耗时比较长。

关于上面问题,我们看一下mysql是如何优化的,mysql内部引入了一个redo log,这是一个文件,对于上面2条更新操作,mysql实现如下:

mysql内部有个redo log buffer,是内存中一块区域,我们将其理解为数组结构,向redo log文件中写数据时,会先将内容写入redo log buffer中,后续会将这个buffer中的内容写入磁盘中的redo log文件,这个个redo log buffer是整个mysql中所有连接共享的内存区域,可以被重复使用。

  1. mysql收到start transaction后,生成一个全局的事务编号trx_id,比如trx_id=10
  2. user_id=666这个记录我们就叫r1,user_id=888这个记录叫r2
  3. 找到r1记录所在的数据页p1,将其从磁盘中加载到内存中
  4. 在内存中找到r1在p1中的位置,然后对p1进行修改(这个过程可以描述为:将p1中的pos_start1到pos_start2位置的值改为v1),这个过程我们记为rb1(内部包含事务编号trx_id),将rb1放入redo log buffer数组中,此时p1的信息在内存中被修改了,和磁盘中p1的数据不一样了
  5. 找到r2记录所在的数据页p2,将其从磁盘中加载到内存中
  6. 在内存中找到r2在p2中的位置,然后对p2进行修改(这个过程可以描述为:将p2中的pos_start1到pos_start2位置的值改为v2),这个过程我们记为rb2(内部包含事务编号trx_id),将rb2放入redo log buffer数组中,此时p2的信息在内存中被修改了,和磁盘中p2的数据不一样了
  7. 此时redo log buffer数组中有2条记录[rb1,rb2]
  8. mysql收到commit指令
  9. 将redo log buffer数组中内容写入到redo log文件中,写入的内容:
代码语言:javascript
复制
1.start trx=10;
2.写入rb1
3.写入rb2
4.end trx=10;
  1. 返回给客户端更新成功。

上面过程执行完毕之后,数据是这样的:

  1. 内存中p1、p2页被修改了,还未同步到磁盘中,此时内存中数据页和磁盘中数据页是不一致的,此时内存中数据页我们称为脏页
  2. 对p1、p2页修改被持久到磁盘中的redolog文件中了,不会丢失

认真看一下上面过程中第9步骤,一个成功的事务记录在redo log中是有start和end的,redo log文件中如果一个trx_id对应start和end成对出现,说明这个事务执行成功了,如果只有start没有end说明是有问题的。

那么对p1、p2页的修改什么时候会同步到磁盘中呢?

redo log是mysql中所有连接共享的文件,对mysql执行insert、delete和上面update的过程类似,都是先在内存中修改页数据,然后将修改过程持久化到redo log所在的磁盘文件中,然后返回成功。redo log文件是有大小的,需要重复利用的(redo log有多个,多个之间采用环形结构结合几个变量来做到重复利用,这块知识不做说明,有兴趣的可以去网上找一下),当redo log满了,或者系统比较闲的时候,会对redo log文件中的内容进行处理,处理过程如下:

  1. 读取redo log信息,读取一个完整的trx_id对应的信息,然后进行处理
  2. 比如读取到了trx_id=10的完整内容,包含了start end,表示这个事务操作是成功的,然后继续向下
  3. 判断p1在内存中是否存在,如果存在,则直接将p1信息写到p1所在的磁盘中;如果p1在内存中不存在,则将p1从磁盘加载到内存,通过redo log中的信息在内存中对p1进行修改,然后将其写到磁盘中

上面的update之后,p1在内存中是存在的,并且p1是已经被修改过的,可以直接刷新到磁盘中。

如果上面的update之后,mysql宕机,然后重启了,p1在内存中是不存在的,此时系统会读取redo log文件中的内容进行恢复处理。

  1. 将redo log文件中trx_id=10的占有的空间标记为已处理,这块空间会被释放出来可以重复利用了
  2. 如果第2步读取到的trx_id对应的内容没有end,表示这个事务执行到一半失败了(可能是第9步骤写到一半宕机了),此时这个记录是无效的,可以直接跳过不用处理

上面的过程做到了:数据最后一定会被持久化到磁盘中的页中,不会丢失,做到了可靠性。

并且内部采用了先把页的修改操作先在内存中进行操作,然后再写入了redo log文件,此处redo log是按顺序写的,使用到了io的顺序写,效率会非常高,相对于用户来说响应会更快。

对于将数据页的变更持久化到磁盘中,此处又采用了异步的方式去读取redo log的内容,然后将页的变更刷到磁盘中,这块的设计也非常好,异步刷盘操作!

但是有一种情况,当一个事务commit的时候,刚好发现redo log不够了,此时会先停下来处理redo log中的内容,然后在进行后续的操作,遇到这种情况时,整个事物响应会稍微慢一些。

mysql中还有一个binlog,在事务操作过程中也会写binlog,先说一下binlog的作用,binlog中详细记录了对数据库做了什么操作,算是对数据库操作的一个流水,这个流水也是相当重要的,主从同步就是使用binlog来实现的,从库读取主库中binlog的信息,然后在从库中执行,最后,从库就和主库信息保持同步一致了。还有一些其他系统也可以使用binlog的功能,比如可以通过binlog来实现bi系统中etl的功能,将业务数据抽取到数据仓库,阿里提供了一个java版本的项目:canal,这个项目可以模拟从库从主库读取binlog的功能,也就是说可以通过java程序来监控数据库详细变化的流水,这个大家可以脑洞大开一下,可以做很多事情的,有兴趣的朋友可以去研究一下;所以binlog对mysql来说也是相当重要的,我们来看一下系统如何确保redo log 和binlog在一致性的,都写入成功的。

还是以update为例:

代码语言:javascript
复制
start transaction;
update t_user set name = '路人甲Java' where user_id = 666;
update t_user set name = 'javacode2018' where user_id = 888;
commit;

一个事务中可能有很多操作,这些操作会写很多binlog日志,为了加快写的速度,mysql先把整个过程中产生的binlog日志先写到内存中的binlog cache缓存中,后面再将binlog cache中内容一次性持久化到binlog文件中。

过程如下:

  1. mysql收到start transaction后,生成一个全局的事务编号trx_id,比如trx_id=10
  2. user_id=666这个记录我们就叫r1,user_id=888这个记录叫r2
  3. 找到r1记录所在的数据页p1,将其从磁盘中加载到内存中
  4. 在内存中对p1进行修改
  5. 将p1修改操作记录到redo log buffer中
  6. 将p1修改记录流水记录到binlog cache中
  7. 找到r2记录所在的数据页p2,将其从磁盘中加载到内存中
  8. 在内存中对p2进行修改
  9. 将p2修改操作记录到redo log buffer中
  10. 将p2修改记录流水记录到binlog cache中

mysql收到commit指令

将redo log buffer携带trx_id=10写入到redo log文件,持久化到磁盘,这步操作叫做redo log prepare,内容如下

代码语言:javascript
复制
1.start trx=10;
2.写入rb1
3.写入rb2
4.prepare trx=10;

注意上面是prepare了,不是之前说的end了。
  1. 将binlog cache携带trx_id=10写入到binlog文件,持久化到磁盘
  2. 向redo log中写入一条数据:end trx=10;表示redo log中这个事务完成了,这步操作叫做redo log commit
  3. 返回给客户端更新成功

我们来分析一下上面过程可能出现的一些情况:

步骤10操作完成后,mysql宕机了

宕机之前,所有修改都位于内存中,mysql重启之后,内存修改还未同步到磁盘,对磁盘数据没有影响,所以无影响。

步骤12执行完毕之后,mysql宕机了

此时redo log prepare过程是写入redo log文件了,但是binlog写入失败了,此时mysql重启之后会读取redo log进行恢复处理,查询到trx_id=10的记录是prepare状态,会去binlog中查找trx_id=10的操作在binlog中是否存在,如果不存在,说明binlog写入失败了,此时可以将此操作回滚

步骤13执行完毕之后,mysql宕机

此时redo log prepare过程是写入redo log文件了,但是binlog写入失败了,此时mysql重启之后会读取redo log进行恢复处理,查询到trx_id=10的记录是prepare状态,会去binlog中查找trx_id=10的操作在binlog是存在的,然后接着执行上面的步骤14和15.

做一个总结

上面的过程设计比较好的地方,有2点

日志先行,io顺序写,异步操作,做到了高效操作

对数据页,先在内存中修改,然后使用io顺序写的方式持久化到redo log文件;然后异步去处理redo log,将数据页的修改持久化到磁盘中,效率非常高,整个过程,其实就是 MySQL 里经常说到的 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。

两阶段提交确保redo log和binlog一致性

为了确保redo log和binlog一致性,此处使用了二阶段提交技术,redo log 和binlog的写分了3步走:

携带trx_id,redo log prepare到磁盘

携带trx_id,binlog写入磁盘

携带trx_id,redo log commit到磁盘

上面3步骤,可以确保同一个trx_id关联的redo log 和binlog的可靠性。

关于上面2点优秀的设计,我们平时开发的过程中也可以借鉴,下面举2个常见的案例来学习一下。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/10/11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Mysql系列第二十五讲
  • 预备知识
  • mysql确保数据不丢失原理分析
  • 做一个总结
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档