凌晨两点被磁盘告警叫醒,登上机器一看数据目录占了三百多 G,第一反应就是 binlog 攒太多了。结果ls翻了半天没看到熟悉的 mysql-bin 开头文件——这就是“mysql binlog 在哪里”这个问题最典型的现场。很多人以为这是个一条命令就能回答的小问题,真到要恢复数据、要排查主从延迟、要清理磁盘的时候,才发现路径、文件名、索引文件、过期策略这几件事是缠在一起的,少搞明白一环就会踩坑。我前后在几种不同形态的环境里都折腾过这件事:包管理器装的、二进制包自己编译的、容器里跑的、云上托管的,路径规则完全不一样。
这篇内容就围绕“mysql binlog 在哪里”这一个问题往下挖。会讲清楚 binlog 到底是什么、它决定了文件落在哪个目录、怎么用几条命令把真实路径钉死、拿到文件之后怎么解析和做数据恢复、以及怎么防止它把磁盘吃满。刚接触 MySQL 的同学可以照着命令一条条敲,能直接看到结果;已经做了几年运维或者后端开发、需要处理线上事故的朋友,可以重点看配置反查、主从环境下的清理红线、以及解析 ROW 格式的那几段,那些地方最容易翻车。
先把结论性的东西摆出来:binlog 的物理位置不是一个固定路径,它由datadir和log-bin(对应系统变量log_bin_basename)两个东西共同决定。默认情况下只给文件名前缀,文件就落在数据目录里;如果配置里写的是绝对路径,那就跟着绝对路径走。听起来简单,但真正找不着文件的时候,原因往往藏在配置文件加载顺序、容器挂载、权限这几个地方。
1. 先把这个问题拆成三个具体问题
1.1 问“在哪里”,可能问的是三件完全不同的事
同一句话,不同的人问出来指向的东西差别很大。第一种是刚学 MySQL 的朋友,在教程里看到mysqlbinlog命令,想知道文件到底躺在哪个文件夹,好去cat一下看看长什么样。第二种是运维或者 DBA 接手了一套陌生环境,要做数据恢复,需要确认这台机器上到底有没有开 binlog、文件保存在哪块盘上、是不是独立挂载的卷。第三种是开发同学,线上出了误删事故,被要求配合找回数据,需要知道从哪台实例、哪个文件、哪个位置点开始回放。
这三种诉求对应的操作路径完全不同。第一种只需要一条SHOW VARIABLES;第二种得结合配置文件、挂载点、磁盘容量一起看;第三种除了路径,还得搞清楚主库还是从库、GTID 模式还是位点模式、全备是什么时候做的。所以别急着敲命令,先想清楚自己属于哪一类,能省掉后面一半的返工。
我见过最常见的误区是:在一台从库上找 binlog,找了半天发现目录里空空如也。原因很简单,从库默认log_bin是关的,它记的是 relay log 而不是 binlog。这个后面会专门讲,先记住这个区别。
1.2 决定文件落点的只有两个参数
MySQL 里跟 binlog 位置直接相关的系统变量就那几个,核心是下面这一组:
| 变量名 | 含义 | 典型输出 |
|---|---|---|
log_bin | binlog 是否开启 | ON/OFF |
log_bin_basename | binlog 文件的完整路径前缀 | /var/lib/mysql/mysql-bin |
log_bin_index | 索引文件的完整路径 | /var/lib/mysql/mysql-bin.index |
datadir | 数据目录 | /var/lib/mysql/ |
log_bin_basename是把datadir和配置里的log-bin拼起来得到的。配置里写log-bin=mysql-bin,那结果就是datadir加上mysql-bin;配置里写log-bin=/data/binlog/mysql-bin,那不管你datadir在哪,文件都往/data/binlog/跑。这个规则在所有版本里都成立,包括 5.7 和 8.x。
这里有个细节值得说清楚:log-bin这个名字在配置文件里是带连字符的写法,在 SQL 里查系统变量的时候是带下划线的log_bin。MySQL 对配置项名字里的连字符和下划线是等价处理的,但你在my.cnf里写成log_bin也能生效,只是不符合惯例。习惯上配置文件用连字符,SQL 里用下划线,别搞混就行。
另外,log-bin只给一个纯文件名(不带路径)的时候,它是相对datadir的。这个“相对”是相对服务器进程的工作目录还是datadir?答案是datadir。这一点在新手那里容易理解偏,因为直觉上会觉得是相对启动目录。
1.3 不同安装形态下默认落在哪
默认路径这件事,跟你怎么装的 MySQL 强相关。我把常见的几种整理成表,方便你对照:
| 安装方式 | 典型默认数据目录 | binlog 默认位置 |
|---|---|---|
| 主流 Linux 发行版的包管理器安装 | /var/lib/mysql/ | /var/lib/mysql/mysql-bin.000001 |
| 官方通用二进制包解压安装 | 取决于初始化时指定的--datadir | 同上,跟着datadir走 |
| 容器镜像运行 | 容器内/var/lib/mysql | 容器内同路径,但是否持久化取决于挂载 |
| 源码编译安装 | 编译时的-DDEFAULT_DATADIR | 同上 |
| 桌面系统上的安装包 | 数据目录通常在系统盘下的ProgramData目录 | 同数据目录 |
表里最需要留意的是容器这一行。镜像里默认数据目录就是/var/lib/mysql,binlog 也在里面,但如果你没把宿主机目录挂载进去,容器一删,binlog 跟着一起没。我见过有人为了“方便”直接用匿名卷,出事故之后想找回 binlog,结果容器已经被重建了,文件自然也没了。用容器跑数据库,数据目录一定要显式绑定到宿主机路径,这一点没有商量余地。
2. 五条命令把 binlog 位置钉死
2.1 一条 SQL 拿到目录和开关状态
最直接的办法就是登进实例查变量。下面这几条按顺序敲,基本就能定位清楚:
SHOW VARIABLES LIKE 'log_bin'; SHOW VARIABLES LIKE 'log_bin_basename'; SHOW VARIABLES LIKE 'log_bin_index'; SHOW VARIABLES LIKE 'datadir';第一条返回ON说明 binlog 开着,返回OFF说明压根没开,后面的都不用查了,先去改配置。第二条返回的路径就是 binlog 文件的完整前缀,比如/var/lib/mysql/mysql-bin,那么实际文件就是这个前缀加上.000001这样的序号。第三条是索引文件的位置,通常跟前缀在同一目录,后缀是.index。第四条是数据目录,用来交叉验证。
这套命令的好处是它查的是运行时生效值,不是配置文件里写的值。配置文件可能被覆盖、可能有多份、可能根本没被读到,但运行时变量一定反映真实状态。我做排查的时候永远是先查运行时,再回头对配置,顺序反了容易先入为主。
2.2 用日志清单确认文件真的存在
拿到路径前缀不代表文件一定在。可能是目录权限不对,可能是文件被手工挪走了,也可能是索引文件和实际文件对不上。这时候用日志清单命令验证:
SHOW BINARY LOGS;这条命令会列出当前实例认得的全部 binlog 文件、每个文件的大小。如果这里列出来的文件名,你去目录里ls不到,那说明索引文件和物理文件不同步了,这是后面第 6 章要讲的问题。
想确认“当前正在写哪个文件”,用这条:
SHOW MASTER STATUS;返回结果里有File、Position、Binlog_Do_DB、Binlog_Ignore_DB这些列。File就是当前活跃的 binlog 文件名,Position是当前的写入位置。做数据恢复的时候,这两个值是起点。
注意版本差异:较新的版本里这条命令改名成了SHOW BINARY LOG STATUS,老的写法还在但已经不推荐。如果你敲SHOW MASTER STATUS报语法错误,换成新名字试试。反过来,如果新名字不认,说明版本还没到,用老写法。
提示:
SHOW BINARY LOGS的权限要求是REPLICATION CLIENT或者SUPER。用普通业务账号查会直接被拒,别以为是命令写错了。
2.3 从配置文件反查,别只盯着一个文件
运行时查完之后,很多时候还要知道配置是从哪来的,尤其是要改参数或者做迁移的时候。MySQL 读配置文件的顺序是有讲究的,在 Linux 上大致是这么个优先级(后面的覆盖前面的):
/etc/my.cnf/etc/mysql/my.cnf- 编译时指定的
SYSCONFDIR下的my.cnf $MYSQL_HOME/my.cnf- 启动时用
--defaults-file显式指定的文件 ~/.my.cnf
实际排查的时候不用背这个顺序,用工具直接问服务器:
mysqld --verbose --help 2>/dev/null | grep -A 1 "Default options"输出里会列出Default options are read from the following files in the given order:,后面跟着真实的读取列表。这比你猜要靠谱得多。
还有一个小工具专门用来打印某个程序读到的配置项:
my_print_defaults mysqld它会把最终合并后的配置项打出来,包含log-bin、datadir这些。如果输出里根本没有log-bin,那说明 binlog 是编译时默认或者根本没配,结合 8.0 默认开启 binlog 这个事实,就能判断出文件走的是默认路径。
2.4 操作系统层面兜底:find 和 lsof
SQL 层查不到、或者怀疑有多个实例混在一起的时候,就得下沉到操作系统。最粗暴有效的一招:
find / -name "mysql-bin.0000*" -type f 2>/dev/null把这行拆开看:-name匹配的是默认前缀,如果你改过log-bin的名字,比如改成了binlog,那就得换成binlog.0000*。2>/dev/null是把权限不足的报错丢掉,不然输出会被刷屏。-type f保证只找文件不找目录。
如果机器上跑了很多实例,想精确定位某个进程用的是哪个目录,用lsof或者直接看进程的打开文件:
lsof -p $(pgrep -x mysqld) | grep -i binlog或者更稳一点,从/proc里看:
ls -l /proc/$(pgrep -x mysqld)/fd | grep -i binlog这两种方式看到的是进程真正打开的文件句柄,比任何配置都权威。我遇到过一次配置文件里写了两份log-bin,一份在[mysqld]下一份在文件末尾,最终生效的是后面那份,全靠lsof才确认下来。
2.5 容器和托管环境的特殊处理
容器里跑 MySQL,路径问题会多一层。容器内的路径是固定的/var/lib/mysql,但你得确认这个路径映射到了宿主机哪里:
docker inspect <容器名> --format '{{json .Mounts}}'输出里找Destination是/var/lib/mysql的那条,对应的Source就是宿主机上的真实路径,binlog 就在那个目录下。如果用docker compose,去看volumes配置段。
托管型数据库又是另一回事。这类服务通常不给你文件系统访问权限,binlog 要么在控制台上提供下载入口,要么提供 API 拉取,要么干脆只能在同账号下的只读实例上消费。遇到这种环境,纠结“文件在哪”意义不大,重点是搞清楚 binlog 保留多久、能不能导出、导出的格式是什么。有些平台导出的 binlog 文件名是带时间戳的,跟原生命名规则不一样,解析的时候要注意。
3. 文件名、序号和索引文件的组织规则
3.1 mysql-bin.000001 这套命名是怎么来的
binlog 文件名由两部分组成:前缀加序号。前缀来自log-bin配置,序号是六位数字,从000001开始递增。序号满了会进位,六位数字能撑到999999,超过之后会变成七位。这个细节平时用不上,但做长期归档的时候得考虑,脚本里如果按固定长度截取文件名,遇到位数变化就会出错。
序号递增的时机是日志切换。触发切换的情况有几种:当前文件写满max_binlog_size、执行了FLUSH LOGS、实例重启、执行了RESET MASTER(这个会清空所有文件重新从 1 开始)。切换的时候不会把正在执行的事务劈成两半,一定会等当前事务提交完再切,这一点很关键,它保证了一个事务的完整性。
3.2 index 文件才是真正的“目录”
很多人只关注mysql-bin.000001这类文件,忽略了同目录下的mysql-bin.index。这个文件是纯文本,一行一个 binlog 文件的路径,服务器靠它知道有哪些日志可用。你可以直接cat它:
cat /var/lib/mysql/mysql-bin.index输出大概长这样:
/var/lib/mysql/mysql-bin.000001 /var/lib/mysql/mysql-bin.000002 /var/lib/mysql/mysql-bin.000003这个文件是理解很多“灵异现象”的钥匙。比如你在目录里明明看到了mysql-bin.000004,但SHOW BINARY LOGS里没有它,那多半是 index 文件里没登记。反过来,SHOW BINARY LOGS里有某个文件,但目录里找不到,那就是文件被手工删了但 index 没更新。这两种情况处理方式完全不同,前者要重建索引,后者要清理索引或者从备份恢复。
注意:不要手工编辑 index 文件。要修正索引状态,用
FLUSH LOGS让服务器自己重建,或者用RESET MASTER(极端手段,会丢全部日志,慎用)。
3.3 一个事务到底落在哪个文件
这个问题在恢复数据的时候特别重要。结论是:一个事务的所有事件一定在同一个 binlog 文件里,不会跨文件。原因就是前面说的,日志切换发生在事务边界,不会在事务中间切。
但这个结论有个例外场景:超大事务。比如一条UPDATE影响了上千万行,这个事务产生的 binlog 体量可能远超max_binlog_size。这种情况下文件会被撑大,超过 1G 也照样写,因为不能切。所以你会看到某个 binlog 文件有 3G、5G,别惊讶,这就是大事务干的。想避免这种情况,业务侧要把大事务拆小,分批提交。
知道了这个规则,恢复的时候就有依据:按位置点回放的时候,起始位置和结束位置必须在同一个文件内,或者至少起始文件在前、结束文件在后且中间连续。跨文件回放是可以的,mysqlbinlog支持一次传多个文件,但位点参数的语义会变得复杂,容易出错。
3.4 binlog、redo log、relay log 三者别搞混
这是新手最容易混淆的地方,我干脆列个表对比:
| 日志类型 | 归属层 | 记录内容 | 写入方式 | 主要用途 |
|---|---|---|---|---|
| binlog | Server 层 | 逻辑变更事件 | 追加写,文件不覆盖 | 主从复制、数据恢复、审计 |
| redo log | InnoDB 层 | 数据页的物理修改 | 循环写,固定大小 | 崩溃恢复 |
| relay log | Server 层(从库) | 从主库拉来的 binlog 内容 | 追加写 | 从库回放 |
从位置上看,redo log 一般在datadir下的#innodb_redo目录(8.0)或者ib_logfile0、ib_logfile1(5.7),跟 binlog 是两套东西。relay log 的位置由relay_log_basename决定,默认也在datadir下,文件名通常是主机名-relay-bin.000001。
弄清楚这个区别的实际意义在于:在从库上找不到 binlog 文件的时候,你要找的可能是 relay log;磁盘被日志占满的时候,要看的是 binlog 而不是 redo log,因为 redo log 大小是固定的,不会无限增长。这两个判断错了,排查方向就全歪了。
4. 拿到文件之后怎么解析和恢复
4.1 mysqlbinlog 最小可用命令
binlog 是二进制文件,直接cat只会看到乱码,得用专用工具解析。最小可用的命令长这样:
mysqlbinlog /var/lib/mysql/mysql-bin.000012 > /tmp/binlog_012.sql生成的 SQL 文件里包含大量注释,形如/*!40019 SET @@session.max_insert_delayed_threads=0*/;,还有以#开头的元信息注释,里面写着时间戳、服务器 ID、位置点。真正的可执行语句被这些注释包裹着。
一个必须提醒的点:不要直接把这个输出扔给mysql执行。里面可能包含SET INSERT_ID、SET TIMESTAMP这类会话变量设置,直接执行可能产生意外结果,尤其是在有自增主键的表上。正确做法是先看清楚内容,再用位点或者时间范围精确截取。
4.2 按时间和位置切片
完整的 binlog 动辄几百兆,直接全量解析又慢又难读。常用的过滤参数有四个:
mysqlbinlog \ --start-datetime="2024-03-01 10:00:00" \ --stop-datetime="2024-03-01 11:00:00" \ /var/lib/mysql/mysql-bin.000012 > /tmp/slice.sqlmysqlbinlog \ --start-position=1234 \ --stop-position=5678 \ /var/lib/mysql/mysql-bin.000012 > /tmp/slice_pos.sql时间参数和位置参数不能混用,二选一。时间参数的时区跟服务器时区一致,不是 UTC,这一点在跨时区部署的时候容易出问题,建议先用date对一下服务器时间。
位置上有个经验:起始位置一般从SHOW BINARY LOGS里看到的偏移量往前推一点,多回放几条无害,但少回放一条可能就丢数据。宁可多回一点,回头再人工核对。
4.3 解码 ROW 格式
8.0 默认的 binlog 格式是 ROW,这种情况下mysqlbinlog默认输出会把行变更编码成 base64,看起来像这样:
### INSERT INTO `test`.`t1` ### SET ### @1=1 ### @2='abc'想在输出里看到列名而不是@1、@2,加两个-v:
mysqlbinlog --base64-output=DECODE-ROWS -vv /var/lib/mysql/mysql-bin.000012-v一次输出列注释,两次输出列类型信息。--base64-output=DECODE-ROWS是把 base64 解码成可读的行变更描述。
这里有个坑必须说明:带###前缀的行是注释,不能拿去执行。它们只是给人类看的描述,不是可执行 SQL。如果你把整份输出导入数据库,这些行会被当成注释忽略掉,实际上什么都没执行。要真正做恢复,得把--base64-output=DECODE-ROWS去掉,让mysqlbinlog输出可执行的BINLOG语句或者用--apply的方式。
4.4 一次完整的误删恢复演练
光讲命令太干,我把一次典型的恢复流程走一遍。假设场景是:某天下午两点二十,有人执行了不带WHERE条件的DELETE,需要把数据找回来。
第一步,确认全备的恢复点。假设最近一次全备是当天凌晨两点做的,备份文件里记录了备份完成时的 binlog 位点,比如mysql-bin.000010的位置154。
第二步,把全备恢复到一台临时实例上,不要直接在生产库上操作。
第三步,找到误删操作发生的位置。全备恢复完之后,用mysqlbinlog从备份位点开始解析,一直解析到误删之后:
mysqlbinlog \ --start-position=154 \ /var/lib/mysql/mysql-bin.000010 \ /var/lib/mysql/mysql-bin.000011 \ /var/lib/mysql/mysql-bin.000012 > /tmp/recover_all.sql第四步,在输出里搜DELETE FROM,定位到误删语句,记下它前面那个事务结束的位置点。这一步是整件事的核心,找错了位置就会把误删再执行一遍。
第五步,用--stop-position截取到误删之前,导入临时实例:
mysqlbinlog \ --start-position=154 \ --stop-position=88421 \ /var/lib/mysql/mysql-bin.000010 \ /var/lib/mysql/mysql-bin.000011 \ /var/lib/mysql/mysql-bin.000012 | mysql -u root -p第六步,校验数据。对比行数、关键字段,确认恢复结果符合预期,再导回生产环境。
整个流程里最容易出问题的是第五步的文件跨度和位点设置。如果误删跨越了多个 binlog 文件,--stop-position只对最后一个文件有效,前面的文件会全量回放。这个语义得提前搞清楚,不然截取范围会出错。
提示:生产环境做恢复前,先把要用到的 binlog 文件复制一份到临时目录,用副本操作。万一解析出错,原始文件还在,不会造成二次损失。
5. 容量治理:别让 binlog 把盘吃满
5.1 过期参数:一个老参数和一个新参数
binlog 不会自己消失,必须有清理机制。控制保留时长的参数有两个:
| 参数 | 单位 | 默认值 | 状态 |
|---|---|---|---|
expire_logs_days | 天 | 0(不过期) | 已弃用 |
binlog_expire_logs_seconds | 秒 | 2592000(30 天) | 推荐使用 |
新版本里binlog_expire_logs_seconds是主推的,精度到秒,能配出比一天更细的粒度。但这里有个非常隐蔽的坑:如果expire_logs_days被设置成了非零值,它会覆盖binlog_expire_logs_seconds的设置。也就是说你两个都配了,最终生效的是老参数。
我见过一次事故就是这么来的:配置里既有历史遗留的expire_logs_days=7,又有新加的binlog_expire_logs_seconds=86400,运维以为保留一天,实际保留了七天,磁盘被撑爆。教训就是这两个参数只能留一个,改配置的时候要全局搜一遍,把废弃的那个彻底删掉。
设置的时候用运行时命令验证:
SET GLOBAL binlog_expire_logs_seconds = 604800; SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';注意SET GLOBAL只是临时生效,重启就没了。要持久化还是得写进配置文件。
5.2 手工清理的正确姿势
参数控制的是自动清理,紧急情况下需要手工删。正确的命令是PURGE BINARY LOGS:
PURGE BINARY LOGS TO 'mysql-bin.000020'; PURGE BINARY LOGS BEFORE '2024-03-01 00:00:00';第一种写法是删掉指定文件之前的所有文件,000020本身保留。第二种是按时间删,删掉指定时间点之前的所有文件。
绝对禁止的操作是直接rm文件。手工删除会导致 index 文件和物理文件不一致,服务器在切换日志或者读取日志列表的时候会报错,错误信息大概是“Could not find first log file name in binary log index file”。修复起来要重建索引,麻烦得很。
还有一点:PURGE不能删除当前正在写的那个文件。就算你写了PURGE BINARY LOGS TO 'mysql-bin.000020',而当前正在写的就是000020,命令也不会把它删掉。这是保护机制,合理。
5.3 max_binlog_size 不等于文件绝对大小
max_binlog_size是每个 binlog 文件的目标上限,默认 1G。但它只是个软限制,实际文件大小可能超过它,原因前面提过:大事务不能跨文件。所以你会看到一个 2G、3G 的文件,这是正常的。
这个参数能配的范围从 4096 字节到 1073741824 字节(正好 1G)。别想着配得特别小来节省空间,文件太小会导致频繁切换,每次切换都涉及文件创建和索引更新,反而增加开销。1G 是个合理的默认值,除非有特殊需求,不建议动。
真正决定磁盘占用的是“保留时长 × 写入速率”,不是单文件大小。写入速率高的库,一天产生几十 G 很正常,这时候该调的是过期时长,而不是文件大小。
5.4 主从环境下的清理红线
有从库的环境,清理 binlog 要格外小心。从库靠拉取主库的 binlog 来同步,如果主库上的 binlog 在从库消费完之前就被删了,从库会报错,错误码 1236,大致意思是“Could not find first log file name”。这个错误一旦出现,从库就得重建,代价很大。
判断能不能删的依据是从库的消费进度。在主库上执行:
SHOW REPLICA STATUS;看Replica_Log_File这一列(老版本叫Slave_Log_File,或者从库上SHOW SLAVE STATUS里的Master_Log_File),它表示从库当前读到了主库的哪个 binlog 文件。清理的时候,保留的文件至少要比这个进度早一点,留足余量。
我一般会留出比最慢从库进度再多两个文件的缓冲。如果从库有延迟告警,清理动作直接暂停,等延迟恢复再说。这件事没有捷径,宁可多占点磁盘,也不要冒重建从库的风险。
6. 常见问题与排查技巧实录
6.1 路径查出来是空的,log_bin 是 OFF
最直接的情况就是 binlog 根本没开。查SHOW VARIABLES LIKE 'log_bin'返回OFF,那自然找不到文件。这时候去配置文件里加:
[mysqld] log-bin = mysql-bin server-id = 1server-id在 8.0 里如果开了 binlog 是必须的,不配启动会失败。5.7 里有些场景可以不配,但不建议省。改完重启,目录里就会出现mysql-bin.000001和mysql-bin.index。
6.2 权限被拒,看不见文件
SHOW BINARY LOGS报权限错误,说明当前账号没有REPLICATION CLIENT或SUPER权限。这是权限问题,不是配置问题,别去翻配置文件。授权语句:
GRANT REPLICATION CLIENT ON *.* TO 'someuser'@'%';另一种权限问题发生在操作系统层。用普通用户去ls /var/lib/mysql,如果目录权限是700且属主是 mysql,会直接报 Permission denied。这时候用sudo或者切到有权限的账号。binlog 文件本身的权限通常是640,属主属组都是 mysql,这是合理的,别去改成 644,binlog 里可能有敏感数据。
6.3 文件在但列表里没有,索引对不上
这种情况前面提过,症状是ls能看到文件,SHOW BINARY LOGS里没有。原因多半是有人手工删过文件,或者做过文件拷贝但没更新索引。
处理方式要看具体方向。如果是多了文件但索引没登记,可以先FLUSH LOGS触发一次切换,让服务器重建索引看看。如果索引里有但文件没了,情况更麻烦,需要检查是不是被误删,能不能从备份恢复。实在恢复不了,只能RESET MASTER清空重来,但这一步会丢掉所有现有日志,做之前必须确认没有从库在依赖它们。
6.4 时间对不上,序号跳跃
解析出来的时间跟预期不一致,先查时区。服务器时区、会话时区、mysqlbinlog解析时用的时区,三者可能不同。用SELECT @@global.time_zone, @@session.time_zone;确认,必要时在解析时加--start-datetime之前先换算好。
序号跳跃一般是日志切换造成的,比如执行过FLUSH LOGS、重启过实例、或者某个时间点写入量突增导致频繁切换。序号跳跃本身不是问题,但如果跳过的文件在索引里还登记着但物理文件不存在,就会出问题,按 6.3 的方式处理。
6.5 常见问题速查表
| 现象 | 可能原因 | 排查命令 | 处理方式 |
|---|---|---|---|
| 找不到任何 binlog 文件 | log_bin未开启 | SHOW VARIABLES LIKE 'log_bin'; | 配置log-bin和server-id后重启 |
| 查到的路径是相对路径 | 配置只给了文件名 | SHOW VARIABLES LIKE 'log_bin_basename'; | 拼接datadir得到完整路径 |
SHOW BINARY LOGS权限报错 | 账号缺权限 | SHOW GRANTS FOR CURRENT_USER(); | 授予REPLICATION CLIENT |
| 文件存在但列表里没有 | index 未同步 | cat mysql-bin.index | FLUSH LOGS重建索引 |
| 列表有但文件不存在 | 手工删过文件 | ls -l对比 | 从备份恢复或清理索引 |
| 解析输出全是 base64 | ROW 格式未解码 | 查看binlog_format | 加--base64-output=DECODE-ROWS -vv |
| 从库报 1236 错误 | 主库 binlog 被清理 | SHOW REPLICA STATUS; | 保留更多日志或重建从库 |
| 磁盘持续增长 | 过期参数未生效 | SHOW VARIABLES LIKE '%expire%'; | 统一只保留一个过期参数 |
| 文件大小超过 1G | 存在大事务 | SHOW BINARY LOGS; | 业务侧拆小事务 |
7. 几个只有踩过才知道的细节
7.1 别在业务低峰期之外做 FLUSH LOGS
FLUSH LOGS会强制切换 binlog 文件,看起来是个轻量操作,实际上它会短暂阻塞写入。在高并发场景下,这个阻塞可能导致连接堆积。我一般只在维护窗口做这件事,或者在清理磁盘的紧急情况下才用。日常的日志切换交给max_binlog_size自动触发就行。
7.2 备份脚本里要显式记录位点
做了全备但没记录备份对应的 binlog 位点,这份备份的恢复价值会大打折扣。标准的做法是备份前FLUSH TABLES WITH READ LOCK拿到一致性快照,记录SHOW MASTER STATUS的输出,然后再 dump 数据。这样恢复的时候才知道从哪个文件的哪个位置开始接 binlog。用物理备份工具的话,它们通常会自动记录,但也要确认一下记录在哪个文件里。
7.3 binlog 不是万能的,DDL 也会记进去
很多人以为 binlog 只记录 DML,其实 DDL 也会记。这意味着回放 binlog 的时候,表结构变更也会被重放。如果恢复的目标实例上表结构已经被改过,回放就会失败。做恢复演练的时候要把 DDL 的影响考虑进去,必要的话先回放到一个干净实例,再迁数据。
7.4 云环境的 binlog 保留策略要主动查
托管服务的 binlog 保留时长通常不是你能随便改的,有的是固定时长,有的按套餐分级。出事故的时候如果超出保留期,日志就找不回来了。我现在的习惯是接手一个托管实例先确认三件事:binlog 开没开、保留多久、能不能导出。这三个问题在控制台上找一找,找不到就问技术支持,别等到需要的时候才发现没有。
我个人的体会是,“mysql binlog 在哪里”这个问题,真正的难点从来不在路径本身,而在你找它的时候往往是在压力最大的时刻——磁盘满了、数据丢了、从库挂了。平时花十分钟把这几条命令敲一遍,把配置里的参数理清楚,把恢复流程在一个测试实例上走通,真出事的时候能省下的是几个小时甚至一整晚。最后再分享一个小习惯:我会在每台实例的运维笔记里记下log_bin_basename的实际值和磁盘挂载点,交接的时候直接给对方,比让他自己查要快得多。