# 日志

mysqld 可以产生多种日志,不过默认只启用了错误日志。

# Error Log

:错误日志。用于记录 mysqld 启动、停止、报错的日志。

  • 相关配置:
    [mysqld]
    log_error    = /var/lib/mysql/mysql-error.log   # 错误日志的保存路径。默认为 log_error=stderr ,即输出到终端
    
  • 日志内容示例:
    2020-01-12T09:09:17.376824Z 0 [Note] Server socket created on IP: '0.0.0.0'.                  # 创建 Socket
    2020-01-12T09:09:17.385746Z 0 [Note] Event Scheduler: Loaded 0 events
    2020-01-12T09:09:17.385880Z 0 [Note] mysqld: ready for connections.                           # mysqld 启动成功,允许被连接
    Version: '5.7.26-29-log'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  Percona Server (GPL), Release 29, Revision 11ad961
    2020-01-12T09:34:51.166819Z 5 [Note] Access denied for user root@'localhost' (using password: YES)    # 客户端连接出错(连接成功则不会在该日志中记录)
    
  • 相关命令:
    flush logs;           -- 关闭并重新打开所有类型的日志文件,这会将内存中的日志缓冲立即写入磁盘
    flush binary  logs;   -- 关闭当前打开的 binlog 文件,并创建一个新的 binlog 文件供写入
    flush engine  logs;
    flush error   logs;
    flush general logs;
    flush slow    logs;
    flush replay  logs;
    

# General Query Log

:通用查询日志。用于记录 mysqld 执行过的所有 SQL 命令。

  • 相关配置:
    [mysqld]
    general_log       = on                          # 是否启用。默认禁用,因为会拖慢 MySQL 的处理速度
    general_log_file  = /var/lib/mysql/mysql.log
    
  • 日志内容示例:
    Time                            Id  Command   Argument
    2020-01-12T09:10:00.371447Z     2   Connect   root@localhost on  using Socket     # 客户端建立连接
    2020-01-12T09:10:00.371642Z     2   Query     SELECT @@version_comment LIMIT 1
    2020-01-12T09:10:24.049055Z     2   Query     SHOW variables LIKE '%time_zone%'   # 执行一条 SQL 命令
    2020-01-12T09:34:34.644737Z     2   Query     SHOW tables
    2020-01-12T09:34:43.278005Z     2   Quit                                          # 客户端断开连接
    
    • Id 表示这是 mysql 启动之后,第几次建立的客户端连接。

# Slow Query Log

:慢查询日志,简称为慢日志。用于记录耗时较久的查询操作,方便用户排查问题、进行优化。

  • 相关配置:

    [mysqld]
    slow_query_log=on                     # 是否启用慢日志。默认禁用
    long_query_time=10                    # 慢查询的阈值,默认为 10 秒,超过该时长的查询操作才会被记录
    log_queries_not_using_indexes=off     # 是否记录未使用索引的查询操作
    slow_query_log_file='/var/lib/mysql/mysql-slow.log'   # 日志文件的保存路径,默认为 <hostname>-slow.log
    
    • long_query_time 的最小值为 0 ,表示记录所有查询操作,但记录大量慢日志会降低 MySQL 的性能。
  • 也可以在 MySQL 终端中配置,当 MySQL 重启时就会失效:

    mysql> SET global slow_query_log=on;
    mysql> SET global long_query_time=3;
    
  • 直接查看慢日志文件比较麻烦,可以用 mysqldumpslow 命令进行筛选:

    mysqldumpslow <slow.log>
                  -s            # 指定排序方式,默认为 at
                      c         # count ,访问次数
                      l         # lock time ,锁定时间
                      r         # rows sent ,返回的记录数
                      t         # query time ,查询时间
                      al        # average rows sent
                      ar        # average query time
                      at        # average lock time
                  -r            # 倒序排列
                  -t 10         # 只显示前 10 条
                  -g PATTERN    # 通过正则匹配进行筛选
    

    例:

    mysqldumpslow -s r -t 10 slow.log
    

# Binary Log

:二进制日志。用于记录 MySQL 执行过的所有 DDL、DML 语句。默认禁用。

  • 优点:可用于拷贝数据、备份数据。
  • 缺点:会让 MySQL 执行事务时稍微变慢。
  • 一个事务可能包含一组事件(event),比如 INSERT、UPDATE 。因此 binlog 记录日志的基础单位是 event 。
  • mysqld 会将 binlog 保存为磁盘上的多个文件。
    • binlog 文件中,每个 event 有确定的字节偏移量 start-position、stop-position ,而事务也是根据 position 来定位。

# 相关配置

[mysqld]
server_id         = 1                 # 启用 binlog 时,必须指定服务器的唯一 ID ,以支持主从同步
log_bin           = mysql-bin         # 启用 binlog ,并指定其保存路径,默认在 datadir 目录下。实际保存时会加上时间字符串作为后缀
# log_bin_index   = mysql-bin.index   # binlog 的索引文件的保存路径,默认为 ${log_bin}.index

# binlog_format     = row       # binlog 的记录格式
# binlog_row_image  = full      # binlog 采用 row 记录格式时,每修改一行数据,记录哪些列。默认为 full ,表示记录所有列。改为 minimal ,则只记录被修改的部分列
# binlog_cache_size = 32k       # binlog 的内存缓冲区大小,单位为 bytes 。超出缓冲区的事务会存储到磁盘的临时文件中
sync_binlog       = 0           # 每提交几次事务就写入 binlog 文件。默认为 1 ,安全性最高,但性能最差。取值为 0 则由文件系统自动写入,性能最好
expire_logs_days  = 7           # binlog 文件的保存天数,过期后会自动删除。默认为 0 ,即不自动删除
# max_binlog_size = 1G          # 每个 binlog 文件的最大大小,超过则创建一个新的文件,文件编号递增。不过一个事务总是会整个写入一个 binlog 文件中,因此一个 binlog 文件可能超过最大大小
gtid_mode         = on          # 是否启用 gtid ,默认为 off
enforce-gtid-consistency = on   # 是否让 master 只执行支持 gtid 的事务,默认为 off

# binlog-do-db    = db1         # 记录该数据库(其它的默认不记录)
# binlog-do-db    = db2         # 可以多次配置该参数
# binlog-ignore-db= db1         # 不记录该数据库(其它的默认记录)
# binlog-ignore-db= db2
  • binlog 有三种记录格式:
    • statement
      • :记录 MySQL 执行的每条写操作的 SQL 命令.
      • 优点:日志量较少。
      • 缺点:还原数据时,SQL 命令的执行结果不一定与原来一致。比如重复执行 UPDATE tb1 SET datetime=now() WHERE id=1; 时,now() 函数的返回值不同。
    • row
      • :记录 MySQL 对每行数据做出的实际修改,记录成 Write_rows、Update_rows、Delete_rows 事件。
      • 假设一条 UPDATE 语句修改了 5 条数据,则 statement 格式只会记录一条 SQL 命令,而 row 格式会记录 5 条被修改的数据内容。
      • 优点:还原数据时,能保证与原来的数据一致。
      • 缺点:日志量较多。且只能记录 INSERT、UPDATE、DELETE 三种 DML 语句修改的数据,而 DDL 语句依然以 statement 格式记录,记录成 create table、alter table、drop table 等日志。
    • mixed
      • :一般的操作用 statement 格式记录,状态可能变化的操作用 row 格式记录。
  • binlog 根据 position 定位每个事务,因此主从复制时,各个事务必须按顺序传输。
    • 启用 gtid 时,会给每个已提交事务分配一个全局事务 ID(Global Transaction ID ,GTID),格式为 server_uuid:transaction_id
      • 此时不需要根据 position 定位事务,因此不需要按顺序传输事务,还可以避免 slave 重复执行同一事务。

# 相关命令

  • 显示 binlog 的配置:

    mysql> SHOW variables LIKE '%log_bin%';
    +---------------------------------+--------------------------------+
    | Variable_name                   | Value                          |
    +---------------------------------+--------------------------------+
    | log_bin                         | ON                             |    -- 说明是否启用了 binlog
    | log_bin_basename                | /var/lib/mysql/mysql-bin       |
    | log_bin_index                   | /var/lib/mysql/mysql-bin.index |
    | log_bin_trust_function_creators | OFF                            |
    | log_bin_use_v1_row_events       | OFF                            |
    | sql_log_bin                     | ON                             |    -- 是否为当前 session 启用 binlog
    +---------------------------------+--------------------------------+
    
  • 显示 binlog 的内容:

    mysql> SHOW binlog events in 'mysql-bin.000060' LIMIT 7;
    +------------------+-----+----------------+-----------+-------------+----------------------------------------------------------------------+
    | Log_name         | Pos | Event_type     | Server_id | End_log_pos | Info                                                                 |
    +------------------+-----+----------------+-----------+-------------+----------------------------------------------------------------------+
    | mysql-bin.000060 |   4 | Format_desc    |         1 |         125 | Server ver: 8.0.25-15, Binlog ver: 4                                 |  -- binlog 固定格式的开头
    | mysql-bin.000060 | 125 | Previous_gtids |         1 |         196 | f75d3723-1c3e-11ec-a666-0242c0a8f002:1-7754                          |
    | mysql-bin.000060 | 196 | Gtid           |         1 |         275 | SET @@SESSION.GTID_NEXT= 'f7343723-8di7-11ec-aj66-0r36c0a8f002:7455' |
    | mysql-bin.000060 | 275 | Query          |         1 |         370 | BEGIN                                                                |  -- 开始一个事务
    | mysql-bin.000060 | 370 | Table_map      |         1 |         449 | table_id: 85 (db1.tb1)                                               |
    | mysql-bin.000060 | 449 | Update_rows    |         1 |         619 | table_id: 85 flags: STMT_END_F                                       |
    | mysql-bin.000060 | 619 | Xid            |         1 |         650 | COMMIT /* xid=351 */                                                 |  -- 结束一个事务
    +------------------+-----+----------------+-----------+-------------+----------------------------------------------------------------------+
    
  • 其它命令:

    SHOW binary logs;                           -- 显示当前所有 binlog 文件
    SHOW binlog events in 'mysql-bin.000001'    -- 显示一个 binlog 的内容
                          FROM 0 LIMIT 2        -- 显示从 position=0 开始的最多 2 个 event
    SHOW master status;                         -- 显示 master 的状态
    
    reset master;                               -- 删除所有 binlog 及其索引,重新开始记录
    purge master logs to 'mysql-bin.000001';    -- 删除指定的 binlog
    
  • 在 shell 中,可用官方提供的 mysqlbinlog 命令解析 binlog 的内容。如下:

    mysqlbinlog mysql-bin.000001
                --database=db1                  # 只显示指定数据库的记录
                --start-position=0
                --stop-position=177
                --start-datetime='2019-12-01 12:00:00'
                --stop-datetime='2019-12-02 12:00:00'
                -vv --base64-output=decode-rows # row 格式的 binlog 只记录了修改的数据内容,可加上该选项,解析成 DML 语句(位于注释中)
    
  • 将 binlog 转换成.sql 文件之后,便可以导入数据库,还原数据。如下:

    mysqlbinlog mysql-bin.000001 > tmp.sql
    mysql -u root -p < tmp.sql
    

# Transaction Log

  • InnoDB 引擎默认启用了事务日志(Transaction Log),分为两种:
    • 重做日志(redo log)
    • 回滚日志(undo log)

# redo log

:属于预写日志(WAL),用于在数据库崩溃之后重启时,重新提交最近一些尚未保存到磁盘的事务。

  • InnoDB 引擎每次提交事务时,会用 redo log 记录相应数据页的变化内容。

    • 新增的事务先记录到内存的 redo log buffer ,缓冲了一定数量才写入磁盘的 redo log file ,并记录此时的 LSN 。
      • redo log file 缓冲了一定数量,才 flush 到磁盘,实际完成事务。
    • redo log file 的大小固定,会从头到尾循环写入。
    • MySQL 重启时,会恢复 redo log file 中尚未 flush 到磁盘的数据页。
  • 新增一个事务时,InnoDB 引擎通过二阶段提交确保将它同时记录到 redo log 和 binary log 。

    1. 先将该事务记录到 redo log buffer ,标记为 prepare 状态,表示预提交。
    2. 然后将该事务记录到 binary log 。
    3. 最后在 redo log buffer 中将该事务标记为 commit 状态,表示已提交。实际上不一定写入了磁盘。
    4. 返回响应给客户端。
  • 相关配置:

    innodb_log_buffer_size         = 16777216   # redo log buffer 的大小,默认为 16M
    innodb_log_file_size           = 50331648   # redo log file 的大小,默认为 48M
    innodb_log_files_in_group      = 2          # redo log file 的数量,默认为 2 个,名为 ib_logfile0、ib_logfile1
    innodb_log_group_home_dir      = ./         # redo log file 的保存目录,默认在数据目录下
    innodb_flush_log_at_trx_commit = 1          # redo log 的 flush 策略,取值如下:
        # 0 :每隔一秒,就将 redo log buffer 新增的事务写入 redo log file ,并 flush 到磁盘
        # 1 :默认值。redo log buffer 每次新增事务时,都写入 redo log file ,并 flush 到磁盘,实现严格的事务
        # 2 :redo log buffer 每次新增事务时,都写入 redo log file ,但每隔一秒才 flush 到磁盘
    
  • 关闭 redo log :

    ALTER INSTANCE DISABLE INNODB REDO_LOG;             -- 关闭 redo log
    SHOW GLOBAL STATUS LIKE 'Innodb_redo_log_enabled';  -- 查看是否启用
    ALTER INSTANCE ENABLE INNODB REDO_LOG;              -- 启用 redo log