MySQL技术内幕-InnoDB存储引擎读书笔记(MySQL日志文件)

MySQL日志文件主要包含错误日志,慢查询日志,查询日志,binlog、与事务日志(redo,undo)。
本文为了突出重点,将日志文件分成如下两类:普通日志、重点日志(binlog,redo,undo)
1、普通日志
1.1、错误日志
错误文件,默认为 ${mysql_home}/data/主机名.err
 配置日志文件的参数为log_error
 show variables like '%log_error%';
1.2、慢查询日志
        MySQL判断语句执行慢的参考如下:
        1)执行时间不能超过 long_query_time(单位为秒,支持到微妙,通过小数点表示)
        2)是否使用了索引开关( log_queries_not_using_indexes  如果设置为1或on,表示如果没有使用索引的语句认为是慢查询 )
        3)本次查询扫描的行数如果小于min_examined_row_limit ,如果本次查收扫描的行数小于该配置值,则不写
             入慢查询日志。
        4)如果没有使用索引,可以控制每分钟出现多少次后才写入一条到慢查询日志中。
参数为: log_throttle_queries_not_using_indexes,默认为0表示不做限制
官方文档关于慢查询日志链接:https://dev.mysql.com/doc/refman/5.6/en/slow-query-log.html
根据使用经验,可以使用如下命令查看与慢查询相关的配置属性:
MySQL技术内幕-InnoDB存储引擎读书笔记(MySQL日志文件)

MySQL技术内幕-InnoDB存储引擎读书笔记(MySQL日志文件)

MySQL技术内幕-InnoDB存储引擎读书笔记(MySQL日志文件)

MySQL技术内幕-InnoDB存储引擎读书笔记(MySQL日志文件)

1.3、查询日志
      查询日志官方说明:https://dev.mysql.com/doc/refman/5.6/en/query-log.html
      查询日志,详细的记录了所有查询语句对应mysql通信协议语句类型(COM_QUERY)相关的语句执行情况,非常适合定位问题,但生产环境下必须禁用该属性,不然会严重影响性能。查询日志相关的两个属性如下:
MySQL技术内幕-InnoDB存储引擎读书笔记(MySQL日志文件)
既然是COM_QUERY类型的,也包含update,insert语句。

2、binlog、redo、undo
接下来重点讨论binlog、redo、undo日志
基于InnoDB存储引擎来讨论binlog,redo,undo。
2.1 binlog
mysql二进制日志,在mysql服务层面记录mysql数据的变化轨迹,其存储格式为基于SQL语句(statement)、基于数据行变化(row)、混合方式(mixed),其作用主要用来做主从同步。横向类比(Redis主从复制)。
写入时机:下文与redo时一起讨论。
2.1.1 binglog相关参数与解读
binlog相关的配置参数如下:
log-bin=mysql-bin  (指定binlog的日志名,默认路径为${mysql_home}/data)
binlog_format 指定binglog的存放格式,statement,row,mixed
max_binlog_size
binlog_cache_size
sync_binlog
涉及文件操作,一般的做法是先缓存(内存中),然后隔一段时间将缓存写入到文件中。binlog的写入同样如此,mysql会为binlog提供一个写缓存区,每个会话将binglog,首先会写入到写缓存区,然后按某种机制刷写到磁盘。通过max_binlog_size设置单个binlog文件的大小,默认为1G,一个文件达到上限后,再新建一个文件,.index记录了所有的binlog文件。binlog_cache_size,是会话级别的参数,设置binlog的内存缓存区大小,默认为32K。如果一个会话内的产生的binlog日志超过32K后,mysql会将binglog写入临时的日志文件。mysql提供两个运行时变量binlog_cache_use、binlog_cache_disk_use来记录利用内存缓存和文件缓存的次数。sync_binlog用来设置输入缓存的频率,表示多少次写缓存区后刷新到磁盘。我们知道,在事务允许过程中,首先会将产生的binlog写入到会话内的缓存区(内存缓存区),然后在事务提交后,将内存缓存区的数据写入到mysql binlog的写缓存区,然后刷写到磁盘,sync_binlog=1表示事务提交时(发出commit之前会先写binlog,但不一定会落盘,InnoDB1.2之后,提交阶段写redo,binlog日志为两阶段提交,见下文详解),将缓存区中的数据立即刷写到磁盘(落盘)。生产环境下sync_binlog设置为1,保证其数据一致性。但还存在这样一种情况,如果发出事务comit操作,binlog已经被写入,但事务提交过程中,mysql服务器down了,此时会引起主从数据不一致性,该怎么办呢?innodb存储引擎提供了innodb_support_xa=1来解决binlog与innodb存储引擎日志文件不一致的问题(二阶段提交),下午redo,undo部分再详细解读。
binlog-do-db          指定需要同步的数据库(schema)
binlog-ignore-db    指定需要同步(记录binlog)日志的数据库(schema)
log-slave-update    设置从服务器是否也写binglog日志,默认是关闭的,log-slave-update=1表示开启。

2.1.2 binglog工具
mysql提供了官方的binlog查看工具,mysqlbinlog

2.2 redo与undo日志
redo与undo是mysql存储引擎级别的日志,俗称事务日志,与事务的实现息息相关。那我们从事务的ACID属性开始说起:
原子性(Atomicity)
    事务内的操作要么全部执行,要么全部不执行。
一致性(Consistency)
    在事务开始之前和事务开始之后,数据库的完整性约束没有被破坏。
隔离性
    两个事务的执行互不干扰,sql定义了4种隔离级别(读未提交、读已提交、可重复读、串行话读)
持久性(Durability)
    事务成功执行后,该事务所对数据库所做的变更持久的保存在数据库之中。
redo 重做日志,主要用来保证事务的持久性。
undo 回滚日志,在InnoDB中,将undo看成是数据,并不是日志,存放在数据文件中(innodb表空间文件中ibdata1)。

2.2.1 redo,undo日志基础
1)undo 取消操作,用于事务回滚,将数据恢复到修改之前的状态,InnoDB的多版本并发控制(MVCC)就是基于undo来实现的。undo严格意义上来说,InnoDB将undo信息当成数据,存放在数据文件。数据文件相关的参数如下:
     innodb_data_home_dir :数据文件的路径,在my.cnf中对应 data_dir
     innodb_data_file_path:数据文件名(默认表空间文件),默认为ibdata1:12M:autoextend
     innodb_file_per_table : 支持每个表单独一个表空间文件(数据文件)
     innodb_tmpdir           :临时表空间路径
     innodb_temp_data_file_path:临时表空间文件,默认为ibtmp1:12M:autoextend
     innodb_undo_directory : undo独立目录(undo独立表空间)
     innodb_undo_log_truncate:默认为off,undo日志是否支持压缩,删除已经落盘的信息。
     innodb_undo_logs:128,默认为128个undo段,其中0号回滚段存在于默认表空间中,1-32号表空间位于临时表
                                          空间。
     innodb_undo_tablespaces:默认为0,表示不开启独立的undo表空间。
2)redo日志
     重做日志,用来实现服务器异常时的数据恢复,实现事务的持久性。
     redo日志的核心思想是使用顺序IO(事务提交时主要写redo日志,操作一个顺序的联系的IO文件)来代替每个事务提交时大量的随机IO去更新数据文件。也就是对数据的修改不会立马刷写到数据文件,而是写入redo文件,然后使用一定的机制,将内存中的脏页异步写入到数据文件,提高事务的执行效率与并发能力。
    redo相关的配置参数:
    innodb_log_buffer_size : 一般根据页的大小(8K,16K)设置为8M,16M,写redo日志的缓冲区大小
    innodb_log_file_size:单个redo日志文件的大小,默认为48M
    innodb_log_files_in_group:默认为2,表示一个redo日志组中包含多少个文件,默认为2个。进行循环覆盖写
    innodb_log_write_ahead_size,每次追加写到redo日志文件的大小,默认为页的大小。8K或16K
    innodb_log_group_home_dir:redo日志文件目录,默认为数据文件路径${mysql_home}\data

2.2.2 binlog、undo、redo日志写入时机
redo,重做日志,其引入的目的就是能够回复InnoDB的事务,达到恢复数据的目的。(持久性)
undo回滚日志,其引入的目的就是事务内的回滚(rollback)。(原子性)
redo日志引入的技术性原理分析:
首先,数据库存储其底层是文件系统,也就是数据库的数据最终存放在文件中,称之为数据文件。其持久性最通俗的要求就是事务执行后变更要能反馈到数据文件中即可。
 操作数据库数据的逻辑一般是这样的:
    首先,从数据库文件中找到记录,文件一般是按照一种特定的格式存放,比如页,然后加载整页数据到内存,在内存中进行数据的操作(脏页)然后将脏页同步回文件,一般不会立即将脏页刷回到磁盘,这样会产生大量的随机IO操作,性能低下,如果不立即刷回磁盘,那么当服务器挂掉后,存放在内存中的数据会丢失,造成数据的不一致性,也就无从持久化。为了解决这一矛盾,引入了redo日志。
    redo日志的引入核心一个思想是采用对redo日志文件的顺序IO来替换频繁的随机IO(更新数据文件),redo文件使用循环覆写方式。redo一般会有两个文件ib_log0与ib_log1。引入redo日志后,数据库修改数据的通常逻辑为,从数据文件中找到数据页并加载到内存,在内存中修改数据,在事务提交之前先写redo日志,确保redo日志落盘,然后提交事务。数据库中的脏页使用一定的机制,统一刷写到数据文件。这样就将每次事务提交的时候,将随机访问数据文件变成顺序写redo日志。这样就提高了效率。

接下来我们以示例来分析,redo,undo是如何写入的。
1)update语句
我们知道,InnoDB使用多版本控制并发控制协议(MVCC)来实现无锁的并发读控制,MVCC,就是借助于undo日志。根据MVCC的实现方式,update语句执行逻辑为,先复制一份老的数据,请删除版本号设置为当前的事务ID,然后插入一条新的数据,更新版本号为当前事务。这里复制老的数据这一个步骤完全与undo的引入非常吻合,可以利用undo来恢复数据。那么MVCC结合undo关于update的实现:
a、首先将数据(数据行)拷贝一份,将其删除版本号设置为当前事务ID,申请回滚段(undo),记录undo日志,undo日志的存储方式是一条链表(双链表)并且按照事务ID后排序。因为InnoDB把undo日志当成是数据,存放于表空间中,故在写undo之前,需要先写redo日志,redo日志将包含undo信息(可以根据redo日志重建undo日志链),也就是undo是受redo日志保护的。

b、然后用插入一条数据,将数据行的更新版本号设置为当前事务。
此时继续写redo日志。
然后提交事务,此时redo日志落盘,undo日志与数据文件可能还保存在内存中,innodb会按照一定的策略将undo日志和数据脏页写入到磁盘。
事务内的查询可见性机制:
    我想在事务内根据主键ID查询一条数据应该是先从数据文件中加载该数据,然后判断根据版本号与当前事务ID判断其可见性,如果不可见,则需要从undo链去找,数据文件行中存储了该记录所在的回滚段与偏移量。沿着回滚链找到向前找到第一个符合可见性的记录。
   binlog、redo日志的写入关系。
在innodb_support_xa=1与sync_binlog=1时,,innodb会使用二阶段协议来写binlog与redo日志。[innodb_flush_log_at_trx_commit=1]
在mysql服务层发出commit命令后:
prepare阶段:先写redo日志并落盘(redo日志信息包含undo信息),状态为prepare。
commit阶段:写binlog文件并落盘
                       写redo日志并落盘(commit信息)
MySQL技术内幕-InnoDB存储引擎读书笔记(MySQL日志文件)
状态1: Redo Log里存在,Binary Log里也存在 --正常情况,crash恢复时需要commit
状态2: Redo Log里存在,Binary Log里不存在 --prepare完毕后发生crash,恢复时需要rollback
状态3: Redo Log里不存在,Binary Log里也不存在 --提交失败,无需处理