Mysql更新过程

2020-04-08  本文已影响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:

查看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 日志,各种写日志的操作还是比较复杂的,暂时就写到这里吧。

上一篇下一篇

猜你喜欢

热点阅读