MySQL日志查看实战:错误日志、慢查询、Binlog排查技巧

📅 发布时间:2026/10/19 5:46:07
MySQL日志查看实战:错误日志、慢查询、Binlog排查技巧
在MySQL的日常运维里查看日志这件事听起来简单做起来却总让人血压升高。很多朋友一遇到问题就到处翻日志但面对一屏幕密密麻麻的记录又不知道看哪个、怎么定位。我在前几年接手过一个某电商中台项目的数据库维护光是排查一次主从延迟就折腾了小半天后来才发现问题不是出在SQL上而是我把日志类型看错了。这篇文章就专门聊MySQL查看日志的完整思路和实操方法把常见的日志类型、文件位置、查看命令、分析思路都理一遍也分享一些我在真实环境里踩过的坑希望能帮你少走弯路。1. 先搞清楚日志到底指什么MySQL日志家族的分类与文件位置很多人在命令行里输入几个常见命令发现没输出就以为日志没开或者查错了其实是没弄明白MySQL有哪几类日志。这一点必须在开头说清楚MySQL的日志不是一个文件而是一整套记录体系每类日志记录的内容、目的、格式完全不同查看方式也各不相同。按功能来分MySQL主要维护这几类日志日志类型记录内容默认状态典型文件位置错误日志Error Log启动、停止、运行时的错误和警告开启datadir目录下通常为hostname.err通用查询日志General Query Log所有客户端连接、SQL语句记录默认关闭可指定文件如general.log二进制日志Binary Log即Binlog所有更改数据的SQL操作语句或行格式取决于配置datadir下如mysql-bin.000001慢查询日志Slow Query Log执行时间超过阈值的SQL默认关闭可指定文件如slow.log中继日志Relay Log从服务器记录主服务器的Binlog事件取决于复制拓扑数据目录下的relay-log文件还有redo log和undo log这两类InnoDB存储引擎的日志它们属于引擎内部日志主要用于崩溃恢复和事务回滚一般不用人工查看但偶尔也要通过工具间接了解它们的行为。我常用的排查对象主要就是前四类。文件位置并不固定最好的方式是直接在MySQL里查询参数来确认。连接上MySQL后执行SHOW VARIABLES LIKE log_error; SHOW VARIABLES LIKE general_log_file; SHOW VARIABLES LIKE slow_query_log_file; SHOW VARIABLES LIKE log_bin_basename;这几个变量会直接显示当前实例实际使用的日志文件路径。如果某类日志没开启查询到的值可能是空或off。所以每次排查日志前第一步不是打开文件而是先确认这个日志开了没有、文件在哪。这两个问题不解决后面全是白费力气。2. 错误日志真正需要第一时间看的文件以及怎么从一堆记录里快速定位遇到MySQL连接不上、服务异常停止、主从切换失败这些问题我永远第一反应就是打开错误日志。因为错误日志记录了从实例启动到运行期间的系统级关键事件包括启动参数问题、存储引擎初始化失败、连接数打满、主从中断原因等等它的优先级比慢查询和Binlog都高。查看错误日志的方式有两种一种是在系统层面直接读文件另一种是在MySQL里用SQL查看。直接读文件很简单用tail或grep就行。典型环境都是Linux我常用的命令组合是# 先看当前用的哪个错误日志文件 mysql -e SHOW VARIABLES LIKE log_error; # 查看最近50行错误日志 tail -n 50 /var/log/mysql/error.log # 实时跟踪新写入的内容排查运行中问题 tail -f /var/log/mysql/error.log # 按关键字过滤错误 grep -i error /var/log/mysql/error.log注意一个细节错误日志里很多行不是Error级别而是Warning级别比如Aborted connection ... to db ... (Got timeout reading communication packets)这类光看关键字小心把你带偏。我更推荐按错误号来过滤比如搞不清连接问题时找1040too many connections、2002cant connect这些数字关键比泛泛地找Error字样更高效。在MySQL 8.x以后错误日志也支持结构化输出可以通过performance_schema里的表来查看。比如SELECT * FROM performance_schema.error_log ORDER BY LOGGED DESC LIMIT 20;这种方式的优势是可以按时间倒序、按错误级别过滤不用登录服务器敲命令看文本。我一般在权限允许的情况下优先用这个SQL查询因为可以直接在客户端输出排查问题的时候复制粘贴也更方便。还有一个经常被忽略的点错误日志默认会按一定规则轮转不会无限增长。如果在系统层面找不到日志文件看一下配置里的log_error是否指向了stderr在systemd管理模式下的MySQL经常会把日志输出到journald。这时用journalctl -u mysqld -f就能看到实时输出。我有一次在某个公司服务器上怎么也找不到mysql.err最后发现日志全在journald里白费了不少时间。3. 慢查询日志打开它之前先想清楚我要查什么以及如何让日志真正反映问题慢查询日志可以说是压测、优化、日常巡检中最常依赖的一份数据。很多人只知开启慢查询日志却不管阈值和记录条件结果要么日志文件大得惊人要么压根抓不到真正需要慢SQL。这里我重点讲讲打开它之前的思考和配置。3.1 参数组合与阈值设定开启慢查询日志核心就三个系统变量slow_query_log、slow_query_log_file、long_query_time。其中long_query_time的单位是秒默认值10但在实际业务里10秒的SQL已经算事故级别了我一般把阈值设为1秒或者更小。还有两个参数容易遗漏log_queries_not_using_indexes记录未走索引的查询和log_slow_admin_statements记录慢管理语句如ALTER TABLE。常用的临时开启方式如下SET GLOBAL slow_query_log ON; SET GLOBAL slow_query_log_file /data/mysql/logs/slow.log; SET GLOBAL long_query_time 1; SET GLOBAL log_queries_not_using_indexes ON;当然这样重启后会失效如果想持久化要到my.cnf里配置。注意修改long_query_time只对新进来的会话生效所以刚修改完如果你这个会话里马上执行一条慢SQL记录里不见得立刻出现。3.2 查看慢日志的几种姿势直接tail文件当然可以但慢日志往往很长我更喜欢用mysqldumpslow工具来做聚合。这个工具是MySQL自带的专门用来统计慢日志里的SQL模式比如# 按执行次数排序前10条 mysqldumpslow -s c -t 10 /data/mysql/logs/slow.log # 按平均耗时排序 mysqldumpslow -s at -t 20 /data/mysql/logs/slow.log # 按锁定时间排序 mysqldumpslow -s al -t 10 /data/mysql/logs/slow.logmysqldumpslow有个很聪明的设计会把SQL里的数字参数替换成N、字符串替换成S这样结构相同但参数不同的SQL会被自动归类成同一条模式统计出来的才是真正有代表性的慢查询类型而不是几十条长得差不多的重复记录。这一点在分析大量慢SQL时特别实用。另外一个思路是通过performance_schema里的events_statements_summary_by_digest表查询历史聚合数据。比如SELECT SCHEMA_NAME, DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1000000000 AS avg_ms, MAX_TIMER_WAIT/1000000000 AS max_ms FROM performance_schema.events_statements_summary_by_digest ORDER BY avg_ms DESC LIMIT 20;这个方案不需要开启慢日志也能了解哪些SQL的整体消耗高在一些没来得及开启慢日志的老实例上很有价值。不过要注意这个表记录的是累计值需要在确认时间窗口后自己算增量否则累计数据会掩盖短时间内的突发慢查询。4. Binlog从不敢碰到会看关于二进制日志的查看与解析方法Binlog是MySQL所有日志里格式最特殊、信息量最大的一份。它记录的是所有可能改变数据内容的操作包括INSERT、UPDATE、DELETE以及DDL但不记录SELECT。它是主从复制、数据恢复、审计溯源的基础。很多人不敢碰Binlog是因为打开后全是乱码。实际上Binlog是二进制格式用普通文本工具看当然看不懂需要配合专门的工具。4.1 确认Binlog开启状态和文件列表SHOW VARIABLES LIKE log_bin; SHOW BINARY LOGS;SHOW BINARY LOGS会列出当前实例已有的Binlog文件列表包括每个文件的大小。如果返回为空说明没开启Binlog。在MySQL 8.0里开启Binlog需要设置log_bin/path/to/mysql-bin然后重启实例这一点和有些版本允许动态开启不同。生产环境通常早就开好了但如果是自己搭的测试环境要留意这个重启要求。4.2 用mysqlbinlog解析Binlog查看Binlog的标准工具是mysqlbinlog基本用法是# 查看某个binlog文件内容 mysqlbinlog /data/mysql/logs/mysql-bin.000023 # 指定时间范围减少干扰 mysqlbinlog --start-datetime2025-11-01 10:00:00 --stop-datetime2025-11-01 12:00:00 /data/mysql/logs/mysql-bin.000023 # 指定位置范围 mysqlbinlog --start-position157 --stop-position980 /data/mysql/logs/mysql-bin.000023 # 基于GTID过滤MySQL 5.6常用 mysqlbinlog --include-gtidsuuid:1-5 /data/mysql/logs/mysql-bin.000023如果不加任何参数直接输出里面会有大量的伪SQL。这些伪SQL看着像普通SQL但其实BINLOG XXXX形式的注释才是真正的二进制事件我们只需要关注紧跟其后的SQL或行记录即可。常用技巧是加-v或--verbose参数可以把基于行格式的Binlog事件转换成伪SQL展示这样对于UPDATE和DELETE操作能看到具体哪个字段被改成了什么值。比如mysqlbinlog -v --base64-outputdecode-rows /data/mysql/logs/mysql-bin.000023 | grep -A 5 UPDATE加--base64-outputdecode-rows的作用是让行事件不要以base64形式输出而用可读的伪SQL展示。这条命令在我核对某条数据为什么被改了的场景里屡试不爽。4.3 Binlog查看实战定位一条误操作举一个我经历过的例子。某系统的后台管理员误更新了整张商品表的某个状态字段工单报过来时已经过了半小时。我们当时的处理路径是这样的第一步确认误操作的大概时间点比如下午3点10分左右。第二步用SHOW BINARY LOGS确认Binlog文件因为半小时内大概率还没轮转通常就是当前的最后一个文件。第三步用mysqlbinlog解析时间窗口mysqlbinlog --start-datetime2025-11-01 15:00:00 --stop-datetime2025-11-01 15:30:00 --base64-outputdecode-rows -v /data/mysql/logs/mysql-bin.000066 /tmp/check_binlog.sql然后用grep找到对应表名再定位到具体的UPDATE事件。可以看到每一行在binlog里对应两个事件一个是before_image一个是after_image从这两个值就能还原出修改前、修改后的完整数据用于后续反向补偿。整个过程里没有用任何其他工具就靠mysqlbinlog。还有一个大家常问的问题能不能用SQL直接查BinlogMySQL没有内置的面向用户的SELECT FROM binlog方式。不过MySQL 8.0里有一个performance_schema中的binary_log相关内容但实用性和mysqlbinlog比差远了我建议还是用命令行工具脚本化也方便。5. 通用查询日志什么时候要用什么时候千万别打开通用查询日志记录的是所有到达MySQL的SQL语句和连接事件相当于数据库层面的全量访问记录。这个日志平时默认是关闭的因为在高并发下会产生巨大的磁盘开销和性能损耗。我在生产环境基本不开也不建议任何人没事就开着跑。但在两种场景下开它非常有效一是排查有某个SQL根本没走到预期路由这类业务层问题二是追踪到底是哪个连接在反复短连接的诡异现象。开启方式和慢日志一样SET GLOBAL general_log ON; SET GLOBAL general_log_file /data/mysql/logs/general.log;一旦开启所有客户端的所有SQL都会被记下来包括只读操作。它的格式也最简单每行记录是一条日志包含时间、线程ID、连接来源、命令类型和SQL内容。查看通用查询日志几乎不用什么技巧tail -f就行tail -f /data/mysql/logs/general.log但这里有一个非常关键的教训通用查询日志千万不能在业务高峰期长时间开启。某次我自己在排查一个连接泄漏问题时就吃过亏开着general_log跑了不到半小时日志文件就从几百KB涨到2GB把磁盘差点塞满。从那以后我给自己定了个规矩开启时间不超过10分钟查完立刻关闭。而且一定要先确认general_log_file所在分区的可用空间足够大再用SET GLOBAL general_logOFF关掉。同时也要提醒通用查询日志里记录的是明文SQL如果库里存在敏感字段值日志文件就成了敏感信息库。处理这类日志时权限控制、定期清理和加密保存都得做到位。6. 日志打不开、看不懂、日志没更新一道完整的排查链路我在社区里经常看到有人问日志文件为什么没有内容路径明明对为什么报权限拒绝日志怎么一直不变这类问题。这些问题表面上五花八门实际上90%都能按同一套逻辑排查。这里我按完整的链路写下来供你复现。6.1 第一步确认参数与实际输出无论哪类日志先看参数和实际值是否对应SHOW VARIABLES LIKE slow_query_log; SHOW VARIABLES LIKE slow_query_log_file;如果返回slow_query_log为OFF就说明你tail的文件根本不会被写入。这是最基础的确认。6.2 第二步检查文件权限和运行用户MySQL大部分日志文件由mysqld进程以mysql或专门的系统用户创建如果你用root或另一个用户查看可能能读但有些情况下路径不对或权限过严就看不到。用ls -l看文件所有者用ps -ef | grep mysqld确认mysqld的运行身份。只要运行身份对目录没有写权限日志就会写不进去而且启动时不一定报错只会记录在错误日志中一条警告。这个坑特别隐蔽。6.3 第三步验证日志真的在写最耳熟能详的方法就是动静结合手动执行一条能触发日志的SQL然后立刻看文件大小变化。# 查看文件大小 ls -lh /data/mysql/logs/slow.log # 执行一条明显很慢的查询 SELECT SLEEP(3);如果long_query_time设的是1秒这条SLEEP(3)就会被记录。执行后再看文件大小是否变化。如果文件大小没变回到第一步检查参数有没有保存、是否生效。还有一种情况是日志文件被轮转或删除后MySQL进程仍然持有旧的文件句柄导致我们看不到实时写入。Linux环境下用lsof会发现mysqld还占着旧文件的句柄。解决方法是正确执行日志轮转后的reopen比如调用FLUSH LOGS;让MySQL重新打开日志文件。很多人以为删掉日志文件就完事了结果磁盘空间没有释放就是因为这个原因。6.4 第四步区分时间造成的误解错误日志和Binlog中的时间戳默认是系统时区而慢查询日志里记录的时间也受系统时区影响。如果你在跨时区服务器上处理问题会发现日志里时间和业务时间对不上。MySQL 8.0里可以通过log_timestamps参数来设置日志中的时间使用UTC还是系统时间。排查时建议统一用log_timestampsSYSTEM避免自己换算出错。再补充一点在查看Binlog时如果用mysqlbinlog查看并且加了--start-datetime这个时间也受到系统时区转换影响。我一般在脚本里加上--datetime之前先date -d确认好目标时间的UTC值再执行不然容易把窗口找偏。7. 基于日志定位问题的两个真实场景演练上面讲了各日志的查看方式这一节我想通过两个真实场景把完整链路串起来。你在自己排查问题时可以直接借鉴这套思路。7.1 场景A某晚上8点整数据库CPU飙升怎么定位CPU飙高时我通常不会先开工具去看进程而是先看慢查询日志和错误日志。晚上8点整这个时间点很关键大概率是定时任务触发。直接看慢日志的聚合mysqldumpslow -s c -t 5 /data/mysql/logs/slow.log | head -30如果慢日志里正好有一批集中在8点左右的高耗时SQL就会立刻浮出水面。如果慢日志没记录那就要结合通用查询日志或者performance_schema的events_stages_current来看是不是存在锁等待。实际那次排查里我们最终定位到的不是一条SQL而是某个定时脚本在8点整执行了全表DELETE产生了大量行锁导致并发查询全被阻塞。DELETE走的慢查询日志只记录了一条但其他被阻塞的查询也都变慢了所以从聚合结果看会出现一堆相似等待的记录。关键在于看到慢日志里大量INSERT/SELECT的等待耗时都很长就要反向怀疑是不是有DDL或大事务堵着而不是只盯着单条SQL本身。7.2 场景B主从复制为什么停了好几分钟遇到同步中断我的第一反应是看错误日志第二反应才是看Binlog。主从中断通常会有明确错误码比如1236从库读取Binlog异常、1032从库找不到匹配行这些错误码在错误日志里一定会有痕迹。先执行SHOW SLAVE STATUS\G查看Last_SQL_Error和Last_IO_Error两个字段。如果提示是Could not execute Delete_rows event on table xxx; Cant find record in xxx说明从库执行Binlog里的DELETE事件时找不到对应行。这种问题多半是主库有数据被人工改掉或者从库有额外写入造成数据不一致。然后我们就可以拿着对应的时间点去从库上用我前面说的mysqlbinlog --base64-outputdecode-rows解析Binlog确认具体是被哪个DELETE语句涉及的哪一行再去和主库核对。这种错误日志定位阶段 Binlog定位具体数据的组合是我处理复制问题最高频的动作。8. 我个人在日志查看上几乎必踩的坑与收尾经验日志这块说到底是工具习惯的问题工具操作方法上面都讲了最后再聊聊几个我踩了很多次才明白的细节。不要用Vim直接打开大的日志文件。不管是慢日志还是通用日志文件超过几百MB后Vim又会卡又会耗内存。用less配合G跳到末尾或者用tail、grep、awk都更合适。如果一定要在文件里搜索一个关键字先grep -n 目标 file拿到行号再sed -n x,yp查看上下文。打开日志之前先考虑我只想要最近多少行/哪段时间的这比直接打开整个文件高效十倍。记得给日志加轮转和磁盘监控。很多人只关注应用本身不关注日志文件大小。我遇到过一次事故就是通用日志瞬间把磁盘写满直接导致业务全部挂起。后来我给所有MySQL日志统一配置了按大小和天数的轮转策略并给数据目录所在分区加了磁盘空间告警超过80%就会收到通知。日志轮转一定要用MySQL支持的方式比如调用mysqladmin flush-logs或登录后执行FLUSH LOGS这样能让mysqld重新打开新的日志文件。查看Binlog也好、慢日志也好别只看一次结果最好连续观察两个时间窗口。临时的问题可能只在某一个文件里出现比如某些Binlog只有特定时间点有大的事务。只看一次很容易得出当前没有风险的错误判断连续观察能减少误判概率。最后一个小建议在本地环境把上面提到的命令都练熟特别是mysqlbinlog的--base64-outputdecode-rows -v组合这个命令我几乎每次做数据回滚、误操作溯源都要用到。熟练之后你会发现MySQL日志其实没有你想象中那么难难的是先理清思路、选对工具再从海量记录里提炼出真正的问题线索。希望这篇基于我个人实操经验的梳理能让你在下次遇到问题时少一点慌乱多一点底气。