数据库备份最佳实践:从mysqldump锁表故障说起

简介: 从一次凌晨3点的真实故障出发,拆解mysqldump锁表引发主从延迟和应用超时的完整链路,附七步排查法、xtrabackup替代方案与生产环境调参建议

大家好,我是数据库小学妹 👋

上周凌晨3点,被一通电话吵醒。运维说从库CPU飙了,应用在超时,让我赶紧看看。

我爬起来打开电脑,迷迷糊糊登录服务器,top一看——mysqldump。

当时心里就咯噔一下,备份怎么会搞出这么大动静?

排查下来才发现,事情一环扣一环:备份锁表,主从延迟飙升,应用跟着超时。从3点05分告警到3点52分恢复,整整47分钟,我那天基本没怎么睡。

今天把这次排查过程整理出来。

故障现场

告警群在3点05分炸的,消息一条接一条。从库CPU 100%,主从延迟从0飙到300秒,应用开始报超时。

我登录从库服务器,top一看,mysqldump进程CPU占用80%多,IO也在高位。再看从库状态:

SHOW SLAVE STATUS

Seconds_Behind_Master已经300多了,而且还在涨。

备份为什么会锁表

mysqldump备份时会执行FLUSH TABLES WITH READ LOCK(FTWRL),获取全局读锁。这个锁会阻塞所有写操作。

为什么mysqldump要加这个锁?因为它需要拿到一个一致性位点(consistent snapshot)。备份过程中如果有写操作,备份出来的数据就不一致了——比如你备份到一半,某个表被truncate了,备份出来的数据就缺了一块。

FTWRL的执行分三步:

  1. Close tables — 关闭所有已打开的表,把脏页刷到磁盘。这一步会等所有正在执行的语句结束
  2. Acquire global read lock — 拿到全局读锁,之后所有写操作被阻塞
  3. Get binlog position — 记录当前binlog位点,作为一致性快照的参照

问题就出在第一步。如果有大查询在跑,比如一个跑了20分钟的报表JOIN,Close tables会一直等它结束。关键是,这期间FTWRL已经开始阻塞新的写操作了——锁已经在排队,但还没拿到,新的写请求全卡在那。

更坑的是,如果你用的是MyISAM引擎,--single-transaction参数根本没用。这个参数只对InnoDB生效,通过开启一个一致性读事务来避免FTWRL。MyISAM没有事务机制,只能老老实实加全局锁。

我那个场景,从库上有个报表查询在跑,执行了20多分钟。FTWRL等这个查询结束,期间所有写操作都卡住了。

备份锁表为什么会影响主库

备份锁表影响的是从库,但为什么会波及主库?这个问题当时我也想了好一会。

主从复制的基本流程:主库执行写操作 → 写入binlog → 从库IO线程拉取binlog → 从库SQL线程回放。从库锁表后,回放被阻塞,binlog在从库这边就堆积起来了。

半同步复制下问题更严重。 如果你配置了半同步复制(semi-sync replication),主库提交事务后要等至少一个从库确认收到binlog才返回客户端。从库锁表导致回放卡住,binlog确认也卡住,主库的写操作直接被拖慢。我遇到的场景就是半同步,主库的TPS从3000掉到了个位数。

binlog堆积会撑爆磁盘。 主库的binlog要等从库确认接收后才能清理。从库一直不确认,主库的binlog就一直涨。我遇到过一次,binlog涨了50多G,磁盘直接告警。如果你配了expire_logs_days,MySQL会尝试清理过期binlog,但如果还有从库没读完,它不敢删,磁盘空间就这么被撑满。

并行复制场景下影响更大。 如果从库开了MTS(Multi-Threaded Slave),多个worker线程并行回放binlog。FTWRL一锁,所有worker线程全卡住,延迟蹭蹭往上涨。我见过延迟从0飙到3000秒的,就是因为MTS的协调线程在等全局锁。

还有个容易忽略的点:从库延迟变大,如果有读写分离配置,应用读到的是旧数据。用户下单成功,刷新页面发现订单没出来,投诉就来了。

应用为什么会超时

从库锁表、主从延迟,应用为什么会超时?

如果应用配置了读写分离,读请求会发到从库。从库锁表后,读请求被阻塞,等待锁释放。

应用有超时设置,比如30秒。从库锁表时间超过30秒,应用就报超时。

超时后应用可能会重试,重试又打到从库,又被阻塞。大量请求堆积,连接池被耗尽,应用开始报"连接池已满"。

我那个场景,应用配置了3次重试。每次重试都等30秒,一个请求就等90秒。并发一上来,连接池很快就满了。

排查过程

排查这个故障用了大概20分钟。说实话,刚看到CPU 100%的时候,我第一反应是磁盘满了或者内存泄漏,完全没想到是备份的锅。

第一步:看系统资源,排除硬件问题。

top -c
iostat -x 1 3
df -h

top一看,mysqldump进程CPU占用80%多。iostat的%util也在高位,说明IO也在打满。磁盘空间还有余量,排除磁盘满的可能。

到这一步我基本确认是mysqldump的问题了,但还得往下查影响面。

第二步:看从库复制状态,确认延迟程度。

SHOW SLAVE STATUS\G

重点看这几个字段:

Seconds_Behind_Master: 347
Slave_SQL_Running: Yes
Slave_IO_Running: Yes
Last_Error: (空)

SQL线程和IO线程都在跑,没有报错,说明不是复制本身出了问题,而是回放被阻塞了。Seconds_Behind_Master 347秒,而且还在涨,这个延迟量级不是小问题。当时心里就凉了半截。

第三步:看从库进程,找阻塞原因。

SHOW PROCESSLIST;

输出里看到大量这样的状态:

State: Waiting for table flush

我愣了一下——等了几秒才反应过来,这是FTWRL的特征。说明有全局读锁在排队。

第四步:找备份进程,确认根因。

ps aux | grep mysqldump

果然,凌晨3点的定时备份任务在跑。命令大概是:

mysqldump --all-databases --master-data=2 > /data/backup/full.sql

注意这里没加--single-transaction,所以会走FTWRL。

第五步:确认锁的范围。

SHOW OPEN TABLES WHERE In_use > 0;
SELECT * FROM performance_schema.metadata_locks
WHERE OBJECT_TYPE = 'GLOBAL';

SHOW OPEN TABLES可以看到哪些表被锁了。如果你用的是MySQL 5.7+,performance_schema.metadata_locks能更精确地看到锁的类型和持有者。

第六步:评估主库影响。

SHOW BINARY LOGS;

binlog数量在涨,说明从库确认跟不上,主库的binlog清理不了。

SHOW GLOBAL STATUS LIKE 'Rpl_semi_sync_master_no_tx';

这个值在涨,说明半同步复制已经在退化成异步了——主库等不到从库确认,开始降级。

第七步:看应用日志,评估业务影响。

应用日志里大量Connection pool exhaustedQuery timeout。这里我走了一小段弯路——一开始以为是应用代码bug,查了半天才发现是从库锁表传导过来的。

定位到问题后,KILL了备份进程。主从延迟大概5分钟开始下降,应用在10分钟左右恢复正常。

解决方案

这次故障后,我做了几个改进。

用xtrabackup替代mysqldump

xtrabackup是Percona的物理备份工具,最大的优势是不需要FTWRL。它的工作原理分两个阶段:

备份阶段: 直接拷贝InnoDB的数据文件(.ibd),同时持续监听并拷贝redo log。因为拷贝的是物理文件,不需要锁表来保证一致性——后续的修改都记录在redo log里。整个过程只加一把轻量级的backup lock(MySQL 8.0是LOCK INSTANCE FOR BACKUP),不会阻塞DML操作。

Prepare阶段: 备份完成后,用redo log对数据文件做crash recovery,把备份期间的修改回放进去,得到一个一致性快照。这一步在备份结束后离线做,不影响线上业务。

# 备份
xtrabackup --backup --target-dir=/data/backup/ \
  --user=backup_user --password=xxx

# Prepare(应用redo log,保证一致性)
xtrabackup --prepare --target-dir=/data/backup/

还有一个容易被忽略的好处:xtrabackup支持增量备份。第一次全量备份后,后续只拷贝变化的数据页,备份速度快很多,对IO的影响也小。

# 增量备份(基于上次全量)
xtrabackup --backup --target-dir=/data/backup/inc1 \
  --incremental-basedir=/data/backup/full

如果只能用mysqldump怎么办

有些场景你可能没法装xtrabackup,那mysqldump也不是不能用,关键是要加对参数:

mysqldump --single-transaction --routines --triggers \
  --master-data=2 --flush-logs --all-databases > /data/backup/full.sql

--single-transaction会开启一个一致性读事务,对InnoDB表不需要FTWRL。但有两个前提条件:

  1. 备份期间不能有DDL操作(ALTER TABLE、CREATE INDEX等),DDL会隐式提交事务,破坏一致性
  2. MyISAM表还是会锁,如果你还有MyISAM表的话

备份窗口规划

备份时间避开业务高峰,晚上10点到凌晨2点是备份窗口,其他时间不备份。这个看起来简单,但很多团队的定时任务是随便写的,凌晨3点恰好是某些报表任务的高峰期。

建议在cron里用nice和ionice限流,避免备份打满IO:

nice -n 19 ionice -c2 -n7 xtrabackup --backup --target-dir=/data/backup/

这样备份进程的CPU和IO优先级都是最低的,不会抢占业务资源。

监控前置

在主从延迟超过10秒时就告警,而不是等到300秒。我后来用PMM(Percona Monitoring and Management)搭了一套监控,设了三级告警:

  • 延迟 > 10秒:钉钉通知
  • 延度 > 60秒:电话告警
  • 延迟 > 300秒:自动触发应急预案

另外备份进程也要监控。写个简单的脚本,备份开始和结束都打个时间戳到监控系统,如果备份超过预设时间(比如2小时)还没结束,也要告警。

长期架构改进

如果业务量继续增长,从库的压力会越来越大。几个方向可以考虑:

  1. 专用备份从库 — 单独起一个从库专门做备份,不承担业务读流量。备份延迟不影响业务
  2. 延迟从库 — 配置CHANGE MASTER TO MASTER_DELAY=N,故意延迟N小时。万一主库误删数据,可以从延迟从库捞回来
  3. 逻辑备份+物理备份结合 — 物理备份做全量恢复用,逻辑备份做跨版本迁移和单表恢复用,两种工具各有各的场景,别只备一种

避坑清单

  1. 能用xtrabackup就别用mysqldump,物理备份走redo log保证一致性,不需要全局锁。如果只能用mysqldump,一定加--single-transaction,但注意这个参数对MyISAM无效
  2. 备份前先SHOW PROCESSLIST看一眼从库有没有大查询在跑,我那次就是没检查,FTWRL卡了20多分钟才拿到锁。备份窗口也别随便定,凌晨3点看起来冷门,可能是报表高峰期
  3. 从库锁表会波及主库,尤其是半同步复制场景,主库TPS可能直接掉到个位数。主从架构一定要提前评估备份的影响面,业务量大的话建议搭专用备份从库,把备份压力和业务读流量隔离开

你们线上备份用的什么方案?有没有踩过类似的坑?评论区聊聊 👇

我是数据库小学妹,咱们下篇见 👋

相关文章
|
28天前
|
存储 关系型数据库 MySQL
MySQL索引底层原理:B+树能存多少数据?页分裂与回表机制详解
数据库小学妹带你深入B+树底层:为何选它而非二叉树或哈希?揭秘页分裂/合并机制、聚簇与二级索引差异、回表代价及磁盘I/O优化逻辑。3层B+树可存约800万数据,查询仅需3次I/O!
|
28天前
|
Prometheus 监控 Cloud Native
MySQL 性能监控实战:从零搭建 Prometheus + Grafana 监控告警体系(附排查 SOP)
数据库小学妹带你从零学监控!本文详解MySQL五大核心指标维度(资源、连接、查询、InnoDB、主从),手把手配置PMM/Prometheus+Grafana监控栈,设置关键告警规则,并提供SQL快照脚本与三步排障SOP。新手友好,即装即用,让性能问题无所遁形!
|
28天前
|
SQL 监控 关系型数据库
数据库三大日志深度解析:Redo Log、Binlog、Undo Log 如何守护你的数据
本文由“数据库小学妹”带你厘清MySQL三大核心日志:Redo Log(引擎层物理日志,保障crash-safe)、Undo Log(支撑回滚与MVCC)和Binlog(Server层逻辑日志,用于复制与恢复),详解WAL机制与两阶段提交原理,助你真正理解事务安全底层逻辑。
|
28天前
|
安全 JavaScript 前端开发
《ZAKU渗透论:卓伊凡的2026渗透工程》第四章:Web攻击原理(下)——XSS、CSRF、文件上传漏洞
本章详解XSS、CSRF与文件上传三大Web漏洞:XSS通过注入恶意脚本窃取Cookie;CSRF伪造已登录用户请求执行非自愿操作;文件上传漏洞则因校验缺失致服务器被控。三者共性——过度信任用户输入。(239字)
341 10
|
28天前
|
安全 NoSQL Java
《ZAKU渗透论:卓伊凡的2026渗透工程》信息收集——黑客怎么找到你?
本章详解渗透测试中至关重要的信息收集环节:占全程50%以上工作量。涵盖被动(搜索引擎、GitHub、社交媒体、Whois、历史快照)与主动(DNS查询、子域名枚举、端口扫描、目录探测)两大策略,并聚焦2026年新趋势——供应链踩点。目标是绘制精准“攻击地图”,找到阻力最小的突破口。(239字)
252 2
|
9天前
|
Prometheus 运维 监控
数据库监控最佳实践:Prometheus+Grafana巡检体系搭建
讲述数据库巡检体系的搭建过程,包括巡检指标设计、Prometheus+Grafana实战、告警阈值设置、故障预测,附避坑清单
|
28天前
|
SQL 关系型数据库 MySQL
【第一阶段总结】MySQL基础20天 —— 知识地图与避坑复盘
本文是MySQL基础20天学习的系统复盘,涵盖架构、索引、SQL优化、事务、锁五大模块,提炼核心知识地图与高频避坑点(如无索引导致全表锁、RR下快照读与当前读差异等),并附面试考点与自测清单,助力夯实底层原理,平稳进阶。(239字)
142 1
|
28天前
|
存储 运维 安全
服务器数据恢复—DL380服务器RAID5阵列断电损坏的数据恢复案例
本次故障设备为某品牌DL380系列服务器,设备内配置6块硬盘组建RAID5磁盘阵列,阵列中存放企业内部办公文件及重要机密业务数据。
|
28天前
|
SQL 监控 关系型数据库
MySQL连接数管理最佳实践:从Too many connections应急到长期治理
本文详解MySQL“Too many connections”故障的应急排查与根治方案:30秒快速评估、杀Sleep连接止血、动态调参救急;深入分析慢查询、连接泄漏、短连接冲击、配置过小四大根因;附诊断脚本、监控告警规则及连接池最佳实践,助你从容应对生产危机。
|
3月前
|
SQL 存储 关系型数据库
【第1天】每天一个MySQL知识点,百日打怪升级
本系列以“每天一个知识点”形式,系统讲解MySQL核心原理。首日聚焦Client/Server通信流程,详解三层架构(连接层→SQL层→存储引擎层)、连接管理、查询解析优化及执行计划,直击面试高频考点,助你从CRUD进阶到懂原理、能排障。(239字)
259 0