二进制日志

来自泡泡学习笔记
跳到导航 跳到搜索

二进制日志包含描述数据库更改的“事件”,例如表创建操作或对表数据的更改。除非使用基于行的日志记录,否则还包含可能会进行更改的语句的事件(例如,没有匹配行的删除操作)。二进制日志还包含有关每个更新数据的语句所用时间的信息。二进制日志有两个重要目的:

  • 对于复制,复制源服务器上的二进制日志提供了要发送到副本的数据更改记录。源将其二进制日志中包含的信息发送到其副本,副本通过重新执行这些事务来进行相同的数据更改,就像源上所做的一样。
  • 某些数据恢复操作需要使用二进制日志。在还原备份之后,会重新执行在备份创建后记录的二进制日志中的事件。这些事件将数据库从备份时的状态更新到最新状态。

二进制日志不用于不修改数据的语句,例如 SELECT 或 SHOW。要记录所有语句(例如,以识别问题查询),请使用一般查询日志。

在启用二进制日志的情况下运行服务器会稍微降低性能。然而,二进制日志在设置复制和进行还原操作方面的好处通常超过了这个轻微的性能损失。

二进制日志对意外停机是弹性的。只有完整的事件或事务才会被记录或读取回来。

写入二进制日志的语句中的密码将被服务器重新编写,以免以明文形式出现。

从MySQL 8.0.14开始,可以对二进制日志文件和中继日志文件进行加密,以帮助保护这些文件中潜在敏感数据的不当使用,并防止存储它们的操作系统的用户未经授权的查看。您可以通过将 binlog_encryption 系统变量设置为ON来在MySQL服务器上启用加密。


以下讨论描述了一些影响二进制日志操作的服务器选项和变量。

二进制日志默认启用(log_bin系统变量设置为ON)。例外情况是,如果您使用mysqld通过使用--initialize或--initialize-insecure选项手动初始化数据目录,那么二进制日志默认禁用,但可以通过指定--log-bin选项启用。

要禁用二进制日志,您可以在启动时指定--skip-log-bin或--disable-log-bin选项。如果指定了其中任何一个选项,并且还指定了--log-bin选项,则后面指定的选项优先。

--log-slave-updates和--slave-preserve-commit-order选项需要二进制日志。如果禁用了二进制日志,则省略这些选项,或者指定--log-slave-updates=OFF和--skip-slave-preserve-commit-order。当指定--skip-log-bin或--disable-log-bin时,MySQL默认禁用这些选项。如果同时指定--log-slave-updates或--slave-preserve-commit-order和--skip-log-bin或--disable-log-bin,则会发出警告或错误消息。

--log-bin[=base_name]选项用于指定二进制日志文件的基本名称。如果不提供--log-bin选项,MySQL将使用binlog作为二进制日志文件的默认基本名称。为了与早期版本兼容,如果使用空字符串或不带字符串的--log-bin选项,基本名称将默认为host_name-bin,其中host_name是主机机器的名称。建议您指定一个基本名称,这样如果主机名更改,您可以轻松地继续使用相同的二进制日志文件名称。如果在日志名称中提供了扩展名(例如,--log-bin=base_name.extension),则扩展名将被默默删除并忽略。

mysqld会在二进制日志基本名称后附加一个数字扩展名,以生成二进制日志文件名称。每次服务器创建一个新的日志文件时,该数字都会增加,从而创建一个有序的文件系列。服务器在发生以下任何事件时,都会在系列中创建一个新文件:

  • 服务器启动或重新启动
  • 服务器刷新日志
  • 当前日志文件的大小达到max_binlog_size

如果使用大型事务,二进制日志文件可能会比max_binlog_size更大,因为事务将以一段完整的方式写入文件,不会在文件之间分割。

为了跟踪已使用的二进制日志文件,mysqld还会创建一个二进制日志索引文件,其中包含二进制日志文件的名称。默认情况下,它的基本名称与二进制日志文件相同,后缀为'.index'。您可以使用--log-bin-index[=file_name]选项更改二进制日志索引文件的名称。在mysqld运行时,请勿手动编辑此文件;这样做会使mysqld混乱。


术语“二进制日志文件”通常表示包含数据库事件的个别编号文件。术语“二进制日志”总体上指的是一组编号的二进制日志文件以及索引文件。

二进制日志文件和二进制日志索引文件的默认位置是数据目录。您可以使用--log-bin选项指定备用位置,通过在基本名称前添加绝对路径名来指定不同的目录。当服务器从跟踪已使用的二进制日志文件的二进制日志索引文件中读取条目时,它会检查条目是否包含相对路径。如果包含相对路径,则使用--log-bin选项设置的绝对路径替换路径的相对部分。二进制日志索引文件中记录的绝对路径保持不变;在这种情况下,必须手动编辑索引文件以启用新的路径。二进制日志文件基本名称和任何指定的路径都可以作为log_bin_basename系统变量使用。

在MySQL 5.7中,启用二进制日志时必须指定服务器 ID,否则服务器将无法启动。在MySQL 8.0中,默认情况下将服务器ID系统变量设置为1。当启用二进制日志时,服务器可以使用此默认ID启动,但如果您没有使用server_id系统变量明确指定服务器ID,则会发出信息消息。对于在复制拓扑中使用的服务器,必须为每个服务器指定唯一的非零服务器ID。

具有足够权限来设置受限会话系统变量的客户端可以使用SET sql_log_bin=OFF语句禁用其自己的语句的二进制日志记录。

默认情况下,服务器记录事件的长度以及事件本身,并使用这些信息验证事件是否正确写入。您还可以通过设置binlog_checksum系统变量使服务器为事件写入校验和。在从二进制日志读取时,默认情况下源使用事件长度,但如果可用,可以通过启用系统变量source_verify_checksum(从MySQL 8.0.26开始)或master_verify_checksum(在MySQL 8.0.26之前)来使用校验和。复制I/O(接收器)线程在复制中还会验证从源接收的事件。您可以通过启用系统变量replica_sql_verify_checksum(从MySQL 8.0.26开始)或slave_sql_verify_checksum(在MySQL 8.0.26之前)来使复制SQL(应用)线程在从中继日志读取时使用校验和(如果可用)。


记录在二进制日志中的事件格式取决于二进制日志格式。支持三种格式类型:基于行的日志记录、基于语句的日志记录和混合基于日志记录。所使用的二进制日志格式取决于MySQL版本。

服务器以与--replicate-do-db和--replicate-ignore-db选项相同的方式评估--binlog-do-db和--binlog-ignore-db选项。

复制使用系统变量log_replica_updates(从MySQL 8.0.26开始)或log_slave_updates(在MySQL 8.0.26之前)默认启用。这意味着复制将从源接收到的任何数据修改写入其自己的二进制日志。必须启用二进制日志才能使用此设置。此设置使复制可以充当其他复制的源。

您可以使用RESET MASTER语句删除所有二进制日志文件,或使用PURGE BINARY LOGS语句删除其中的一部分。

如果正在使用复制,请在确保没有副本仍需要使用旧日志之前,不要删除源上的旧二进制日志文件。例如,如果副本永远不会落后于三天,您可以每天在源上执行mysqladmin flush-logs binary,然后删除超过三天的任何日志。您可以手动删除这些文件,但最好使用PURGE BINARY LOGS,该命令还会为您安全地更新二进制日志索引文件(并且可以带有日期参数)。

您可以使用mysqlbinlog实用程序显示二进制日志文件的内容。当您想要重新处理日志中的语句进行恢复操作时,这可能非常有用。例如,您可以按照以下方式从二进制日志更新MySQL服务器:

$> mysqlbinlog log_file | mysql -h server_name


mysqlbinlog也可以用于显示副本上的中继日志文件的内容,因为中继日志文件使用与二进制日志文件相同的格式进行编写。

在语句或事务完成后,但在任何锁被释放或进行任何提交之前,会立即执行二进制日志记录。这确保日志按照提交顺序记录。

对于非事务性表的更新会立即存储在二进制日志中。

在未提交的事务中,所有更改事务性表(如InnoDB表)的更新(UPDATE、DELETE或INSERT)都会被缓存在服务器接收到COMMIT语句之前。在此时,mysqld会在执行COMMIT之前将整个事务写入二进制日志。

对于非事务性表的修改无法回滚。如果要回滚的事务包括对非事务性表的修改,则整个事务将以ROLLBACK语句的形式记录,以确保这些表的修改被复制。

当处理事务的线程启动时,它会分配一个大小为binlog_cache_size的缓冲区来缓存语句。如果一个语句的大小超过了此值,线程将打开一个临时文件来存储事务。线程结束时,临时文件将被删除。从MySQL 8.0.17开始,如果服务器上启用了二进制日志加密,临时文件将被加密。

Binlog_cache_use状态变量显示使用该缓冲区(以及可能的临时文件)来存储语句的事务数量。Binlog_cache_disk_use状态变量显示其中有多少事务实际上需要使用临时文件。这两个变量可用于调整binlog_cache_size的值,使其足够大,以避免使用临时文件。

max_binlog_cache_size系统变量(默认为4GB,也是最大值)可用于限制用于缓存多语句事务的总大小。如果事务超过了这么多字节,则会失败并回滚。最小值为4096。


如果您正在使用二进制日志和基于行的日志记录,对于CREATE ... SELECT或INSERT ... SELECT语句,同时插入将转换为普通插入。这样做是为了确保在备份操作期间,通过应用日志可以重新创建表的完全副本。如果您正在使用基于语句的日志记录,则原始语句将被写入日志。

二进制日志格式具有一些已知限制,可能会影响从备份进行恢复。


请注意,由于复制功能的增强,MySQL 8.0中的二进制日志格式与MySQL的早期版本不同。

如果服务器无法写入二进制日志、刷新二进制日志文件或将二进制日志同步到磁盘,复制源服务器上的二进制日志可能会变得不一致,并且副本可能会失去与源的同步。binlog_error_action系统变量控制在遇到此类错误时采取的操作。

默认设置为ABORT_SERVER,会使服务器停止二进制日志记录并关闭。此时,您可以识别和纠正错误的原因。重新启动后,恢复将继续进行,就像发生意外服务器停止的情况一样。

IGNORE_ERROR设置提供与旧版MySQL的向后兼容性。使用此设置,服务器将继续进行进行中的事务并记录错误,然后停止二进制日志记录,但继续执行更新。此时,您可以识别和纠正错误的原因。要恢复二进制日志记录,必须再次启用log_bin,这需要重新启动服务器。只有在需要向后兼容性的情况下,并且二进制日志对于此MySQL服务器实例非必需时才使用此选项。例如,您可能仅将二进制日志用于间歇性的审计或服务器调试,并且不将其用于从服务器进行复制或依赖于它进行时间点恢复操作。

默认情况下,二进制日志在每次写入时与磁盘同步(sync_binlog=1)。如果未启用sync_binlog,并且操作系统或机器(不仅仅是MySQL服务器)崩溃,可能会丢失二进制日志的最后一些语句。为了防止这种情况发生,可以启用sync_binlog系统变量,以在每个N提交组后将二进制日志与磁盘同步。sync_binlog的最安全值为1(默认值),但这也是最慢的。


在早期的MySQL版本中,即使将sync_binlog设置为1,如果发生崩溃,表内容和二进制日志内容之间仍存在不一致的可能性。例如,如果您正在使用InnoDB表,并且MySQL服务器处理了一个COMMIT语句,它会按顺序将许多预提交的事务写入二进制日志,将二进制日志同步,然后将事务提交到InnoDB。如果服务器在这两个操作之间意外退出,InnoDB会在重新启动时回滚事务,但事务仍存在于二进制日志中。此类问题在早期版本中通过启用InnoDB对XA事务的两阶段提交的支持来解决。在MySQL 8.0中,InnoDB对XA事务的两阶段提交的支持始终处于启用状态。

InnoDB对XA事务的两阶段提交的支持确保了二进制日志和InnoDB数据文件的同步。但是,MySQL服务器还应该在提交事务之前将二进制日志和InnoDB日志同步到磁盘上。InnoDB日志默认情况下已经同步了,并且sync_binlog=1确保了二进制日志的同步。隐式的InnoDB对XA事务的两阶段提交的支持和sync_binlog=1的效果是,在崩溃后的重新启动过程中,在回滚事务后,MySQL服务器扫描最新的二进制日志文件以收集事务的xid值,并计算二进制日志文件的最后有效位置。然后,MySQL服务器告诉InnoDB完成成功写入二进制日志的任何准备提交的事务,并将二进制日志截断到最后的有效位置。这确保了二进制日志反映了InnoDB表的确切数据,因此副本保持与源的同步,因为它不会接收已回滚的语句。

如果MySQL服务器在崩溃恢复时发现二进制日志比应有的长度要短,那么它至少缺少一个成功提交的InnoDB事务。如果sync_binlog=1并且磁盘/文件系统在被要求进行实际同步时执行(有些不执行),那么不应该发生这种情况,因此服务器会打印一个错误消息,指出二进制日志文件_file_name的长度小于预期长度。在这种情况下,该二进制日志不正确,复制应该从源数据的新快照重新启动。

以下系统变量的会话值将被写入二进制日志,并在解析二进制日志时由副本进行处理:

  • sql_mode
  • foreign_key_checks
  • unique_checks
  • character_set_client
  • collation_connection
  • collation_database
  • collation_server
  • sql_auto_is_null