前几天处理一个线上问题,业务方反馈某个查询接口突然要好几秒才出结果。我第一反应不是看代码,而是先打开了MySQL的慢查询日志,结果一条全表扫描的SQL被当场抓了出来。这个场景我遇到过太多次,无论是MySQL还是MongoDB,出了问题先看日志永远是最快、最不容易跑偏的排查路径。但“看日志”这三个字说起来简单,真正上手时会发现一大堆困惑:MySQL的binlog、redo log、undo log各管什么?MongoDB的system.profile和journal又是什么?主从延迟和副本集同步的问题该去看哪份日志?这篇就系统梳理MySQL和MongoDB的日志体系,讲清楚每类日志的用途、配置方法和排查思路,同时结合我实际处理过的线上案例,给出可以直接抄的排查步骤。适合刚接触数据库维护的后端开发,也适合需要系统理解两种数据库日志机制的DBA和运维同学。
1. 为什么日志是数据库的第一现场
1.1 日志不是“附加功能”,而是故障恢复和性能分析的起点
很多开发者对日志的第一印象是“排错用的”,这没错,但日志在数据库系统里的地位远不止于此。MySQL和MongoDB的日志至少承担四类职责:记录错误、记录性能瓶颈、保障崩溃恢复、支撑主从/副本集数据同步。如果没有日志,数据库断电重启后可能直接丢数据,主从复制也无法工作。你可以把数据库日志类比成飞机的黑匣子加行车记录仪:平时不起眼,关键时刻能还原现场。
我见过不少团队,数据库用了一年,慢查询日志一直是关闭状态,错误日志也没配置轮转。等到线上出现卡顿或数据丢失时,只能凭感觉猜原因,白白浪费宝贵的恢复时间。事后复盘时大家才意识到,日志的成本极低,收益却极高。这也是我写这篇文章的初衷:把两类主流数据库的日志整理成一套能直接用的清单。
1.2 MySQL和MongoDB日志体系的整体地图
先给出一个全局视图。MySQL以InnoDB存储引擎为核心,日志类型比较丰富,包括错误日志、慢查询日志、通用查询日志、二进制日志(binlog)、事务日志(redo log)和回滚日志(undo log)。MongoDB作为文档型数据库,核心日志包括系统日志(mongod.log)、数据库剖析日志(system.profile)、日志文件(journal)和复制日志(oplog)。
| 数据库 | 日志类型 | 主要作用 | 是否默认开启 |
|---|---|---|---|
| MySQL | 错误日志 | 记录启动、运行、关闭中的错误 | 默认开启 |
| MySQL | 慢查询日志 | 记录执行时间超过阈值的SQL | 默认关闭 |
| MySQL | binlog | 记录数据变更,用于主从复制和恢复 | 需手动开启 |
| MySQL | redo log | 保证崩溃恢复,防止数据丢失 | 默认开启 |
| MySQL | undo log | 支持事务回滚和MVCC | 默认开启 |
| MongoDB | mongod.log | 记录服务运行、告警和错误 | 默认开启 |
| MongoDB | system.profile | 记录超过阈值的慢操作 | 默认关闭 |
| MongoDB | journal | 保证单节点数据持久性 | 默认开启 |
| MongoDB | oplog | 副本集同步与数据恢复 | 默认开启(副本集) |
这张表可以作为快速参考。后面每一类日志我都会拆开讲细节,包括参数配置、查看方法和常见误区。
2. MySQL日志:从必开三件套到崩溃恢复机制
2.1 必开三件套:错误日志、慢查询日志、binlog
有人可能觉得MySQL日志很多很复杂,但真正建议每台实例都要开启的,其实就三样:错误日志、慢查询日志和binlog。这三样利用率和性价比最高,其余日志更多是存储引擎内部行为。
先看错误日志。它负责记录mysqld启动、运行和关闭时产生的错误信息,比如表损坏、连接数打满、权限问题、InnoDB恢复失败等。配置方式很简单:
[mysqld] log_error = /var/log/mysql/error.log log_error_verbosity = 3其中log_error_verbosity表示记录的详细程度,一般设为2或3。3会包含告警和注记信息,平时排障更友好,但日志量会稍大。遇到MySQL启动不了、端口被占用、数据目录权限不对这类问题,第一件事就是看这个文件的末尾。
再看慢查询日志。它对性能排查的重要意义怎么强调都不为过,因为全表扫描、缺索引、锁等待这类问题都会体现在“执行时间超过阈值”的SQL里。典型配置如下:
[mysqld] slow_query_log = ON slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 log_queries_not_using_indexes = ONlong_query_time最好设置为1秒,别设置成0。设置成0会记录所有SQL,对生产环境会产生严重IO和磁盘压力;设置成5秒又太迟钝,很多1到2秒的慢SQL会漏掉。log_queries_not_using_indexes建议开启,这样即使SQL执行很快但没走索引,也会被记录下来,能提前发现潜在问题。
binlog是MySQL实现数据复制和时间点恢复的基石。它记录所有引起数据变更的操作,比如INSERT、UPDATE、DELETE,但不记录SELECT。生产环境强烈建议开启,并且格式选择ROW:
[mysqld] server-id = 1 log_bin = /var/log/mysql/mysql-bin binlog_format = ROW sync_binlog = 1 expire_logs_days = 7binlog_format = ROW意味着记录每一行数据的变化,虽然日志量比STATEMENT格式大,但更安全。STATEMENT记录的是SQL语句本身,主从数据很容易因函数、不确定操作产生不一致。比如DELETE FROM t WHERE id > 100 LIMIT 1在STATEMENT格式下可能因为主从数据顺序差异导致删除行不同,而ROW格式不会。网上很多“binlog导致主从不一致”的案例,绝大多数都可以靠ROW格式规避。
2.2 redo log与undo log:MySQL崩溃恢复的底牌
binlog解决的是数据库层面的复制与恢复,但InnoDB自身的崩溃恢复还依赖另一对日志:redo log和undo log。先打个比方:redo log相当于画家的速写本,画到关键节点先记一笔,画布丢了也能靠速写本复原;undo log则是每画一笔之前拍下的旧照片,方便反悔。理解这对组合,对解释数据库为什么断电不丢数据非常重要。
redo log也被称为“预写日志”,InnoDB对数据页的修改会先在内存中完成,同时把对应的重做记录写入redo log,再在合适的时机把脏页刷新到磁盘。如果数据库在脏页刷盘前崩溃,重启后会读取redo log,把没来得及落盘的数据变更重放一遍,从而保证已提交事务的数据不丢失。磁盘上的redo log是固定大小、循环写入的,所以它不能无限保存历史,只能覆盖最近一段时间的写操作。
MySQL中跟数据持久性最相关的两个参数是innodb_flush_log_at_trx_commit和sync_binlog。前者控制事务提交时redo log刷盘的频率,后者控制binlog的刷盘频率。
| innodb_flush_log_at_trx_commit | 含义 | 风险与性能 |
|---|---|---|
| 0 | 每秒刷一次redo log到磁盘 | 性能最好,但操作系统崩溃可能丢1秒数据 |
| 1 | 每次事务提交都刷盘 | 最安全,性能通常也可接受 |
| 2 | 每次提交写入OS缓存,每秒刷盘 | 数据库重启不丢,操作系统崩溃可能丢1秒数据 |
生产环境我建议保持1,同时sync_binlog = 1,两者配合可以保证即便在崩溃恢复场景下也不会出现数据错位。为了缓解频繁刷盘的性能压力,InnoDB已经引入了组提交机制,多个事务可以合并一次刷盘。实际测试中,开启双1的写入性能和之前的差距并不像想象中那么大,但它对数据安全的保障是实打实的。
undo log则服务于事务回滚和MVCC可见性。每次数据修改前,InnoDB会把修改前的数据写入undo log。事务回滚时用undo log恢复旧值;普通SELECT需要按版本读取旧数据时,也依赖undo log构建快照。它平时由purge线程自动清理,但如果有长事务长期不提交,undo log就会膨胀,这也是常见的磁盘空间告警来源之一。
2.3 慢查询日志实操案例
理论讲完,看一个实际案例。某天接到反馈说订单导出功能变慢,我打开慢查询日志,发现这样一条记录:
# Time: 2025-01-08T10:22:31.123456Z # Query_time: 2.873000 Lock_time: 0.000100 Rows_sent: 200 Rows_examined: 1048576 SET timestamp=1736317351; SELECT id, order_no, status, create_time FROM orders WHERE status = 'PAID' AND create_time BETWEEN '2025-01-01 00:00:00' AND '2025-01-07 23:59:59' ORDER BY create_time DESC LIMIT 200;Query_time长达2.87秒,Rows_examined有104万行,但最后只返回200行,典型就是没走索引导致的全表扫描。执行EXPLAIN确认:
EXPLAIN SELECT id, order_no, status, create_time FROM orders WHERE status = 'PAID' AND create_time BETWEEN '2025-01-01 00:00:00' AND '2025-01-07 23:59:59' ORDER BY create_time DESC LIMIT 200;优化器没有选择任何可用索引,type为ALL。发现问题后直接在create_time字段上建立索引,并把status字段作为冗余查询条件:
ALTER TABLE orders ADD INDEX idx_create_time (create_time);添加索引后,同一个查询的执行时间降到了0.03秒。这里有个细节值得注意:在where中使用BETWEEN并配合ORDER BY create_time DESC,单独索引create_time既能过滤时间范围,又能避免额外的排序操作,一箭双雕。如果先过滤status再排序,就需要考虑联合索引,依赖数据分布来判断。
3. MongoDB日志:从system.profile到Journal与oplog
3.1 mongod.log:最基础但也最容易忽略的日志
MongoDB的服务运行日志默认输出到stdout,也可以在配置文件中通过systemLog.path指定文件路径:
systemLog: destination: file path: /var/log/mongodb/mongod.log logAppend: true logRotate: reopen verbosity: 0logAppend: true表示日志追加而不是覆盖,logRotate: reopen配合外部logrotate命令使用,可以实现不停机轮转。日志级别verbosity从0到5,0是默认,数字越大信息越详细。
有一点要提醒:排查MongoDB问题时,不要只盯着报错信息,mongod.log里的“警告”和“缓慢”类消息同样值得关注。比如出现大量Slow query日志、连接数报警、WT cache压力提示,这些通常是性能问题的早期信号。
3.2 system.profile:MongoDB的慢查询日志
MongoDB没有“慢查询日志文件”的概念,它的对应机制叫数据库剖析,数据写入system.profile集合。启用方法是在admin或目标数据库上执行:
db.setProfilingLevel(1, { slowms: 100 })第一个参数表示级别:0是关闭,1是只记录超过slowms阈值的操作,2是记录所有操作。第二个参数里的slowms和MySQL的long_query_time类似,单位是毫秒。如果只想查看当前状态:
db.getProfilingStatus()开启后,超过100毫秒的操作会自动写入当前数据库的system.profile集合。查询慢操作可以这样写:
db.system.profile.find({ millis: { $gt: 200 } }).sort({ ts: -1 }).limit(20).pretty()我这里要强调一个常见坑:system.profile本身也是一个集合,如果开启级别2记录所有操作,会导致system.profile集合快速增长,并且它默认是一个只有1MB大小的capped集合,写入压力大时可能覆盖早期记录。生产环境建议只开启级别1,slowms从100到200毫秒起步。如果业务对延迟敏感,再适当调低。
排查慢查询时,拿到慢操作列表后,我会先看command字段里是find、aggregate、update还是remove,然后对涉及的集合执行explain。例如:
db.collection.explain("executionStats").find({ user_id: "abc123" })重点看executionStats里的totalDocsExamined和nReturned,如果扫描的文档数比返回结果多出几个数量级,基本可以确认需要建索引。常用索引创建命令是:
db.collection.createIndex({ user_id: 1, create_time: -1 })索引方向1表示升序,-1表示降序,如果排序需求是create_time DESC,对应索引方向就是-1。
3.3 journal与oplog:写入与复制的核心保障
MongoDB的journal日志,作用和MySQL的redo log类似,都是数据库崩溃后的恢复依据。MongoDB 3.2之后默认启用journal,配置项是:
storage: journal: enabled: truejournal记录了每次写入操作在内存视图中的应用过程。服务器异常断电后,mongod重启时会根据journal重放未持久化的写入。它位于dbpath下的journal目录,采用预分配文件的方式,写入时先写journal再更新数据文件。
与journal密切相关的是writeConcern。写入操作指定j: true时,要等journal落盘后才返回确认:
db.collection.insertOne({ name: "test" }, { writeConcern: { j: true } })这个操作会带来额外延时,且与atomicity无关,只决定“写操作什么时候算成功”。对单机MongoDB来说,如果j: false,系统层面崩溃可能造成数据丢失。不过副本集场景下,即使不开j,主节点接受写入后通过oplog同步给从节点,多数情况也能容忍。
oplog是副本集同步的核心,它记录了所有数据变更操作,类似于MySQL binlog加中继日志的合体。每个副本集节点都有本地的oplog.rs集合,它是一个capped collection,只能循环写入限制大小。副本集的主节点负责写入oplog,从节点通过拉取oplog实现数据追赶。如果从节点同步速度跟不上主节点写入,或者某个从节点停机时间过长,等到它的同步位置超出了主节点oplog的覆盖范围,这个从节点就无法再追上主节点,只能全量重建或重新同步。
查看oplog状态和大小:
rs.printReplicationInfo()这条命令会输出oplog的大小、时间范围等关键信息。oplog默认大小在64位系统上是磁盘可用空间的5%,最小为1GB。生产环境中,如果你发现业务高峰期写放大明显,或者其他从节点停机恢复后经常需要全量同步,那多半是oplog太小。修改oplog大小需要先停掉mongod或进入维护模式,然后调整集合大小,比如:
db.adminCommand({ convertToCapped: "oplog.rs", size: 4096 })实际操作时要非常谨慎,因为直接把oplog改小或清空会导致从节点无法同步。推荐的做法是先扩容,比如从原来的几百MB增加到几个GB,确保能覆盖高峰期数小时的写入量,并且要在业务低峰执行。
4. 从日志到排查:两类数据库的对比与典型场景
4.1 日志功能对照表
很多人同时维护MySQL和MongoDB时,经常会混淆两类日志的对应关系。我整理了一张简易对照表,帮助快速建立映射:
| 功能定位 | MySQL | MongoDB |
|---|---|---|
| 运行错误记录 | error log | mongod.log |
| 慢操作记录 | slow query log | system.profile |
| 崩溃恢复 | redo log | journal |
| 回滚/MVCC | undo log | 无对应(文档模型天然不支持事务回滚到任意点,4.0后的事务依赖快照) |
| 主从/集群同步 | binlog | oplog |
| 数据结构化记录文件 | 无直接对应 | oplog.rs(capped collection) |
这个对照关系建立起来后,排查思路就不会乱。MySQL出问题先确认是实例层、SQL层还是复制层,MongoDB出问题先确认是服务层、操作层还是副本集层,对应的日志入口各不相同。
4.2 典型场景一:MySQL主从中断
业务高峰期,主库正常但从库数据明显落后,甚至出现复制中断。排查第一步看从库状态:
SHOW SLAVE STATUS\G重点看这几项:
Slave_IO_Running:IO线程是否正常拉取binlogSlave_SQL_Running:SQL线程是否正常回放中继日志Seconds_Behind_Master:从库落后主库的秒数Last_Errno和Last_Error:最近一次复制错误码和错误信息Relay_Log_Pos:正在执行的中继日志位置
如果Slave_SQL_Running: No,通常说明SQL线程回放binlog时遇到了冲突,比如主键重复、表不存在、数据被手工修改等。调整的常规流程是先跳过指定错误还是重新同步,取决于业务容忍度。临时跳过错误可以这样做:
STOP SLAVE; SET GLOBAL sql_slave_skip_counter = 1; START SLAVE;但这个方法只适合紧急恢复,不能反复使用。如果错误信息是因为Binlog事件格式不兼容或数据差异很大,最稳妥的解法是从主库重新导出并重建从库,别在错误状态上反复斗。
主从延迟高的常见原因还包括:主库并发写入量大而从库是单线程SQL回放、大事务执行时间过长、从库硬件性能较弱。MySQL 8.0已经在尽力推进并行复制,但索引缺失和复杂查询回放仍然可能变成瓶颈。日志层面能做的事是提前开启慢查询日志,看从库是否存在大量慢更新语句。
4.3 典型场景二:MongoDB副本集同步延迟
MongoDB副本集出现同步延迟,第一步先看状态:
rs.status()从输出里找到每个节点的stateStr、health、optime等信息。重点关注optimeDate,它表示该节点已应用到的操作时间。如果某个从节点的optimeDate和主节点差距持续拉大,说明同步跟不上。
oplog从窗口来看,如果主节点的oplog覆盖时间只有几十分钟,而某个从节点停机了几小时,它重连后会直接进入FATAL或需要重建。所以对延迟敏感的场景,oplog大小设计很关键。假设业务高峰期每秒写入1000次操作,每次操作约1KB,一小时写入大约3.6GB,那oplog最好预留8到12小时的覆盖窗口,也就是30GB以上,而不是用默认值。这个估算要吃透业务写入模型,每次更新的oplog大小可以通过打印主节点的oplog.rs集合大小来近似估计。
除去oplog大小外,副本集延迟还常见于从节点上有正在运行的报表查询或聚合任务,它们会占用CPU和磁盘IO,导致同步线程抢不到资源。排查时可以在从节点上开启profiling,或者直接看mongod.log里是否有大量慢查询和repl set相关警告。
4.4 日常检查清单
日志的核心价值在于提前发现隐患,而不是等故障发生后再补救。我把自己在用的检查清单整理了一下,可以作为日常巡检参考:
| 检查项 | 命令/方式 | 预期结果 |
|---|---|---|
| MySQL错误日志 | tail -n 100 error.log | 无新的ERROR级别记录 |
| MySQL慢查询 | 统计最近1小时慢SQL数量 | 数量不异常增长 |
| MySQL主从状态 | SHOW SLAVE STATUS\G | Slave_IO_Running/Slave_SQL_Running均为Yes |
| MongoDB日志 | tail -n 200 mongod.log | 无频繁WARNING |
| MongoDB profiling | db.getProfilingStatus() | 超过慢阈值的操作数量在可接受范围 |
| MongoDB副本集 | rs.status() | 各节点health=1,optimeDate差距小于一定时间 |
巡检不是走过场,每次都要留下记录。发现趋势比单独看一眼更重要,比如慢查询数量从每天100条涨到1000条,通常意味着数据量增长或索引失效。
5. 我踩过的坑与避坑清单
5.1 常见问题速查表
日志系统本身也有坑,下面这些是我或同事真实遇到过的问题,列成速查表:
| 常见问题 | 可能原因 | 处理方法 |
|---|---|---|
| 误删除表后无法恢复 | binlog未开启或过期时间太短 | 以库表+时间范围检索binlog,用mysqlbinlog工具恢复 |
| 主从不一致但无报错 | binlog格式为STATEMENT | 改成ROW格式并重新初始化从库 |
| mongod.log频繁滚动 | 日志轮转配置不当 | 配置systemLog.logRotate和外部logrotate |
| system.profile只保存少量记录 | capped集合容量太小 | 增大system.profile大小,或只对关键库开启 |
| 从节点需要全量重建 | oplog覆盖窗口不足 | 扩容oplog,并排查主库写入量变化 |
| redo log和undo log占满磁盘 | 长事务导致undo膨胀,redo检查点推进慢 | 监控事务运行时间,拆分大事务,及时清理或扩容 |
| binlog文件数量过多导致磁盘告警 | expire_logs_days未设置 | 设置binlog_expire_logs_seconds并按需清理 |
关于误删恢复,我想多说一句。MySQL误删数据时,如果binlog开启了且保存完整,可以通过mysqlbinlog解析binlog,将误删时间点前后的语句提取出来,再进行补偿操作。流程大致是:
mysqlbinlog --no-defaults --start-datetime="2025-01-08 10:00:00" --stop-datetime="2025-01-08 10:30:00" \ /var/log/mysql/mysql-bin.000012 > /tmp/recover.sql这里输出的是包含原生SQL的文件,需要人工根据项目情况补出反向SQL。整个过程对运维的SQL功底要求很高,操作前要先在测试环境演练,并且要保证所有参与操作的同事对业务数据流有共识,否则容易出现二次灾害。
5.2 日志清理与轮转的实用技巧
日志不清理,再大的盘也会被写满。MySQL的错误日志和慢查询日志本身不支持自动轮转,需要借助系统logrotate。一个典型的配置是这样的:
/var/log/mysql/*.log { daily rotate 14 compress delaycompress missingok notifempty create 640 mysql mysql postrotate # 如果MySQL支持,执行flush logs /usr/bin/mysqladmin flush-logs endscript }MongoDB的日志轮转则要特别注意:不能直接删除日志文件,否则mongod进程持有的文件句柄会继续写入,磁盘空间不仅不释放,还会出现文件删除后空间吃紧的怪现象。正确做法是先执行db.adminCommand({ logRotate: 1 })让mongod重新打开日志文件,再交给logrotate按天切割。
binlog的过期管理,MySQL 8.0推荐用binlog_expire_logs_seconds,比如设置成604800表示7天。千万不要直接rm binlog文件,这样会导致主从库的日志索引错乱。正确方式是让MySQL在安全时间点自动清理。
5.3 给新手的几个实操建议
日志体系最容易被忽略的,就是“开启成本”和“维护成本”之间的平衡。第一次接触MySQL和MongoDB日志可以按下面顺序去练:
- 先给自己的本地环境开启error log、slow query log和binlog,修改配置后重启,确认日志文件正常生成。
- 执行几条慢SQL,打开日志看看输出格式,尝试用EXPLAIN分析。
- 在MongoDB里开启profiling,插入一些超过100ms的操作,然后查询system.profile验证。
- 建一个单节点副本集,观察oplog变化,试着用
rs.printReplicationInfo()查看覆盖窗口。 - 最后,尝试模拟一次错误日志排查:比如故意改动数据目录权限,启动MySQL,观察错误日志给出的线索。
这套练习只要一个下午就能完成,但带来的收益是可持续的。我最初自己上手时,也总觉得日志参数越少越好,直到一次线上事故因为慢查询日志没开,导致排查耗了整整一晚上。那次之后,我在任何环境都会第一时间检查日志开关,这已经成了我的条件反射。
6. 日志维度差异:同是数据库,思维模式完全不同
6.1 一条数据写入时的日志路径对比
把日志看作数据写入路径的“轨迹”,MySQL和MongoDB的差异就很明显。MySQL InnoDB写入一条数据的大致路径是:更新内存中的buffer pool页面,写undo log,写redo log,将binlog写入磁盘,最后根据事务提交顺序完成两阶段提交。MongoDB单机写入的路径是:在内存中更新数据视图,写journal日志,按默认60秒或配置的时间间隔将内存数据落盘到数据文件。
有一个容易困惑的点:MySQL的binlog和redo log是两份分开的日志,InnoDB在崩溃恢复时不能只依赖binlog,因为binlog是MySQL Server层的,redo log是存储引擎层的。两者通过两阶段提交来保证数据一致。如果一个事务写入redo log后、写入binlog前发生崩溃,重启后该事务会被回滚;如果binlog已写入且事务标记为prepare状态,重启后会尝试提交。这套机制保证了主从复制和原库恢复的最终一致性。
MongoDB则不一样,它在单机上主要依赖journal保证崩溃恢复。副本集同步时靠oplog,oplog本身是数据,也是日志。可以说MongoDB的设计更统一:写入主节点时,oplog就是后续使用的日志,不需要额外开启其他复制日志。
6.2 慢查询排查思路的差异
MySQL的慢查询日志是文件,分析起来很直接,用pt-query-digest这类工具能对慢SQL做聚合统计。MongoDB的慢操作存在system.profile集合里,虽然也能导出分析,但格式更偏向BSON,需要借助Python或JavaScript脚本做一些转换。比如直接聚合统计超过100ms的操作分布,可以写成这样:
db.system.profile.aggregate([ { $match: { millis: { $gt: 100 } } }, { $group: { _id: "$command.find", count: { $sum: 1 }, avgMillis: { $avg: "$millis" } } }, { $sort: { avgMillis: -1 } }, { $limit: 20 } ])这类聚合输出能看到哪些查询模式最需要优化,搜索维度通常是集合名、谓词条件和排序字段。MySQL里慢SQL的优化更多依赖执行计划分析,索引选型和表结构设计。两类数据库的共性是:慢日志排查必须结合索引状态和数据分布,不要只盯着执行时间本身。
6.3 主从/副本集结构下的日志角色差异
MySQL主从复制中,binlog是主库的日志,中继日志(relay log)是从库拉取binlog后临时存储的文件,SQL线程再根据中继日志回放数据变更。MongoDB副本集同步则更加一体化,从节点直接拉取主节点的oplog,并本地重放。如果oplog配置太小,从节点的复制进度很容易被打断,直接造成节点陷入恢复态,比MySQL“落后多少秒”的问题要严重得多。
实际运维里,MySQL复制中断常见于错误代码和手工干预,MongoDB副本集问题常见于oplog覆盖和全量重同步。理清这些差异后,遇到相关问题会平静很多,因为有清晰的排查路径。
7. 写在最后:我处理日志问题的一些体会
聊了这么多,最想说的一句话是:日志不要等出问题时才想起去看,平时就应该养成读取日志的习惯。我在实际运维中给自己定了条规矩:任何一次数据库变更后,都要去翻对应日志目录,确认没有异常告警,再继续后续操作。把日志当作数据库的“体检报告”而非“事故报告”,很多隐患其实可以提前消化掉。
最后再分享一个小技巧。如果排查MySQL慢查询时拿不准某条SQL是不是索引引起的,可以先把慢查询日志里出现频率最高的SQL列出来,然后统一跑一遍EXPLAIN ANALYZE,它不仅能显示执行计划,还能给出真实执行时间和消耗的详细数据,比纯看Rows_examined靠谱很多。MongoDB这边,遇到聚合管道慢的情况,记得加上explain("executionStats")查看每个阶段的扫描行数,经常能发现是某个$lookup或者$unwind导致了数据量爆炸。两个数据库的日志机制虽然不同,但排查时那股“先看日志、再动数据”的耐心是一样的。希望这篇梳理能帮你少走几步弯路。