美文网首页
Mysql更新过程

Mysql更新过程

作者: 明翼 | 来源:发表于2020-04-08 09:43 被阅读0次

    此篇也是笔记,加上自己的理解和联系内容。
    此篇涉及Mysql的恢复,binlog日志和redo log 日志,非常重要。

    一 表和数据准备

    简单的建一个测试表如下:

    mysql> create table Test(ID int primary key, c int);
    mysql> insert into test values(1,1) ,(2,2), (3,3);
    

    那么如下的更新语句:

    mysql>update test set c=c+1 where id=2;
    

    的执行过程如何那?


    Mysql逻辑架构图来自互联网

    整个过程和执行查询语句是类似的,还是走上面的流程,表在更新的时候,会把缓存失效,这就是mysql新版本关闭缓存的原因。

    一 执行过程

    1. 首先客户端连接到连接器,连接器进行认证,通过会会查看test表是否存在缓存,如果存在缓存则清空。
    2. 接着分析器进行词法和语法分析,得知是一条更新语句。
    3. 优化器决定使用主键索引
    4. 执行器会查找id为2的数据,使用索引树来找到一行数据,如果内存中有则直接返回数据给执行器;如果没有,会从磁盘中把数据读入内存,然后返回数据给执行器。
    5. 执行器 拿到了需要更改的行数据之后,将n+1生成新行调用存储引擎接口写入这一新行。
    6. 存储引擎将新数据更新到内存中,同时将这个更新操作记录在redo log中,记录为prepare状态,告诉执行器随时可以提交。
    7. 执行器生成这个操作的binlog日志,并把日志写入到磁盘。
    8. 执行器调用存储引擎的提交事务接口,将redo log 更改为commit状态,整个操作更新完成。

    这里面用到了两类非常重要的日志redo log 和bin log日志。

    二 redo log 日志

    redo log日志是InnoDB这个存储引擎带的,我们看第6步,存储引擎将数据更新到内存,就告诉执行器可以提交了,并没有等数据更新到磁盘上,这类日志叫WAL日志(Write Ahead Log),也就是在数据写入到磁盘之前的日志。如果熟悉solr的朋友,会注意到solr也有类似的日志叫Tlog日志,也是WAL日志,通过这种方式可以提升数据更新的速度,并且也存在着Tlog日志做数据恢复的情况。所以redo log的日志和Tlog日志类似,是具有系统奔溃恢复数据的能力,crash-safe能力。

    至于数据什么时候真正被写到磁盘上,有两个时间点就是数据库空闲的时候,或者redo log空间不够的时候。redo log是一种循环日志,它的空间是受限的。
    可以通过以下命令来查看redo log的大小配置:

    mysql> show variables like 'innodb_log_file%';
    +---------------------------+----------+
    | Variable_name             | Value    |
    +---------------------------+----------+
    | innodb_log_file_size      | 50331648 |
    | innodb_log_files_in_group | 2        |
    +---------------------------+----------+
    2 rows in set, 1 warning (0.00 sec)
    

    默认的才48MB 存储一个redo log文件,一组有两个文件,所以一共才有96MB文件。对应每秒有200-300TPS的数据库来说,设置为4G大小是比较合理的,主要可以减少因为redo log满了而去刷新数据到磁盘上的操作。但是文件过大,也容易造成数据库恢复慢,那谁,你还记得我们solr的Tlog日志过大,导致solr半个多小时都没有恢复吗,似曾相识的场景。
    可以更改my.cnf配置,比如添加:

    innodb_log_file_size =1G
    innodb_log_files_in_group=4
    

    redo log 有两个标志,一个是write pos,是开始写日志的位置;check point 是当前开始清理日志的位置,它们之间的空余空间是redo log日志可以写的空间,快满的话会停下来,擦除一部分记录,腾出空间。
    如下图:


    图来自极客时间MySQL课程

    redo log 也是有缓存的,写redo log也不是直接写到磁盘上,也有一个写入到磁盘的时机问题,通过如下参数来控制:

    mysql> show variables like 'innodb_flush_log_at_trx_commit%';
    +--------------------------------+-------+
    | Variable_name                  | Value |
    +--------------------------------+-------+
    | innodb_flush_log_at_trx_commit | 1     |
    +--------------------------------+-------+
    1 row in set, 1 warning (0.00 sec)
    
    mysql>
    

    如果值为1 表示每次提交事务的时候,就会将redo log的log buffer数据刷新到磁盘上,这种方式不会丢数据,但是每次交互都写磁盘,IO性能差。
    设置为0 表示大概1s刷新一次磁盘,有可能数据库会丢失1s的数据。
    设置为2 表示每次提交写log buffer,但是每秒强制写磁盘。
    如下图表示:


    图片来自互联网

    图中 fsync 才强制写磁盘,OS buffer 是写系统文件缓存而已。
    数据不重要的情况下,设置为2 是更优的策略:

    set @@global.innodb_flush_log_at_trx_commit=2;    
    

    当然如果是批量插入数据的话,将commit最后一次提交的话,性能会更好。

    三 bin log 日志

    有了redo log 日志,为什么还需要bin log日志。 原因有:

    1. redo log 日志是InnoDB 引擎带的,其他引擎的没有,bin log 日志,是Mysql的服务层带的,所有的引擎都可以用。
    2. redo log 日志是个循环日志,不能做长期保存,日志会被覆盖掉,无法做数据的恢复和备份,而bin log日志正是用于数据的归档和恢复的。
      bin log 和redo log的不同点还有,redo log记录的是物理日志,而bin log记录的是逻辑日志;所谓物理日志,是记录在哪个数据页上做xx 更改;而bin log 日志记录的是类似把特定行数据+1 ,有的格式的bin log日志记录的直接是sql,这个和它的名字不一致,感觉bin log 日志更应该是物理日志,哈哈。
      查看bin log的日志类型:
    mysql> show variables like 'binlog_format%';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | binlog_format | ROW   |
    +---------------+-------+
    

    MySQL5.1 以后引入了binlog_format参数,该参数有三种可选值:statement、row和mixed:

    • statement就是之前的格式,基于SQL语句来记录。
    • row记录的则是行的更改情况,但是row格式有一个不好的地方就是当修改的行数很多时,生成的binlog占用很大的空间,占用大量空间的同时还会耗费大量IO资源,因此MySQL又提供了一种折中的方案——mixed。
    • 在mixed模式下,MySQL默认仍然采用statement格式进行记录,但是一旦它判断可能会有数据不一致的情况发生,则会采用row格式来记录。

    查看bin log列表:

    mysql> show master logs;
    +----------------------------+-----------+
    | Log_name                   | File_size |
    +----------------------------+-----------+
    | DESKTOP-0PNJ0VF-bin.000028 |     20361 |
    | DESKTOP-0PNJ0VF-bin.000029 |       178 |
    | DESKTOP-0PNJ0VF-bin.000030 |       178 |
    | DESKTOP-0PNJ0VF-bin.000031 |       178 |
    | DESKTOP-0PNJ0VF-bin.000032 |       178 |
    | DESKTOP-0PNJ0VF-bin.000033 |       178 |
    | DESKTOP-0PNJ0VF-bin.000034 |       178 |
    | DESKTOP-0PNJ0VF-bin.000035 |       178 |
    | DESKTOP-0PNJ0VF-bin.000036 |    565052 |
    | DESKTOP-0PNJ0VF-bin.000037 |       178 |
    | DESKTOP-0PNJ0VF-bin.000038 |    132422 |
    +----------------------------+-----------+
    

    查看正在写的binlog日志

    mysql> show master status;
    +----------------------------+----------+--------------+------------------+-------------------+
    | File                       | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
    +----------------------------+----------+--------------+------------------+-------------------+
    | DESKTOP-0PNJ0VF-bin.000038 |   132422 |              |                  |                   |
    +----------------------------+----------+--------------+------------------+-------------------+
    

    查看最近的内容:

    show binlog events in 'DESKTOP-0PNJ0VF-bin.000038';
    

    如图:


    bin log日志

    如果是语句格式,里面看到很多sql语句内容。

    四 二阶段提交

    刚才说到redo log 让数据库是crash safe的,也就是数据库挂了,所做的更改不会丢失,我们在更新数据的时候,数据在内存中被更新,在redo log里面被记录,如果这时候数据库挂掉了,那么redo log里面的数据还可以用来恢复。数据是不是真的需要恢复那,比如一个事务执行一半还未提交,如果数据库按照redo log 来恢复的话,结果客户端认为数据没提交,所以是老的数据,mysql却把它恢复了,这个就产生了不一致,所以redo log 恢复的是提交的事务。

    为了保证数据恢复所以mysql 在执行更新的时候,需要在redo log中记录两次,一次是prepare 状态日志,表示,准备好了事务可以提交了;另外一次是bin log写入完成后,写的redo log的commit 状态日志。这就是两阶段提交。

    4.1 只提交一次redo log产生情况

    如果我们只提交一次redo log 日志,就会有两种情况:

    1. redo log 在第6步更新完内存数据之后,直接提交为commit 状态日志,再做bin log日志的记录。
    2. 更新完内存数据之后,先写bin log 日志,再写redo log日志。
      第一种情况下: 更新内存数据-----> 写redo log commit状态 日志---> 写bin log 日志---->提交完成。
      如果数据库崩溃发生在写redo log commit 状态日志之后,写binlog 日志之前,那么数据库重启的时候,发现redo log 有记录commit 状态日志就会把数据恢复; 但是我们在做Mysql 主备同步或恢复数据库的时候,使用的是bin log日志和备份来恢复的,这时候因为bin log 中没有这次事务的数据,所以不会恢复,这就造成了数据的不同步。

    第二种情况下: 更新内存数据----> 写bin log 日志--->写redo log 日志--->提交完成。
    如果数据库崩溃 发生在写bin log 日志之后,写redo log日志之前,那么数据库重启的时候,内存中更新丢失,redo log里面没有内容,所以不会恢复;但是在做同步 或异常恢复数据库的时候,使用的是bin log日志,这时候发现bin log有数据,又会把数据恢复,这就造成了数据不同步。

    也就是两次redo log的目的是为了主库和备份是一致的,数据恢复是通过数据库的文件备份和bin log 日志结合起来恢复的。

    Mysql的InnoDB 引擎还包括undo log 日志,各种写日志的操作还是比较复杂的,暂时就写到这里吧。

    相关文章

      网友评论

          本文标题:Mysql更新过程

          本文链接:https://www.haomeiwen.com/subject/bzxophtx.html