如何发现及处理 MySQL 主从延迟问题

在 Percona MySQL 支持团队中,我们经常看到客户抱怨复制延迟的问题。当然,这对 MySQL 用户来说并不是什么新鲜事,多年来我们在 MySQL 性能博客上发表过一些关于这个主题的文章(过去有两篇特别受欢迎的文章:"Reasons for MySQL Replication Lag" 和 “Managing Slave Lag with MySQL Replication"),两篇文章均由 Percona 首席执行官 Peter Zaitsev 撰写)。

译者注:Percona 公司是做 MySQL 发行版的,MySQL 有三大发行版,MySQL、MariaDB、Percona,《高性能 MySQL》这本神作就是出自 Percona 的专家团队。

在今天的文章中,我将分享一些发现复制延迟的新方法 - 包括从服务器滞后的可能原因 - 以及如何解决这个问题。

如何发现复制延迟

MySQL 复制有两个线程:IO_THREAD 和 SQL_THREAD。IO_THREAD 连接到 master,从 master 读取 binlog 事件,并将其复制到名为 relay log 的本地日志文件中。另一方面,SQL_THREAD 在从节点上读取 relay log,然后尽可能快地处理这些日志。每当复制出现延迟时,首先要弄清延迟发生在 IO_THREAD 还是 SQL_THREAD。

通常情况下,I/O 线程不会造成巨大的复制延迟,因为它只是从主服务器读取 binlog。不过,这取决于网络连接、网络延迟…即服务器之间的速度有多快。Slave 的 I/O 线程可能会因为带宽拥塞而变慢。通常,当 Slave IO_THREAD 能够足够快地读取 binlog 时,就容易在 Slave 上堆积 relay log – 此时表明 Slave IO_THREAD 是没问题的。

另一方面,如果是 Slave SQL_THREAD 导致延迟,大概率是因为来自 replication stream 的 queries 在 Slave 上执行的时间太长。可能的原因包括 Master、Slave 之间的硬件不同、索引不同、工作负载不同。此外,Slave OLTP 工作负载有时会因为“锁”而导致复制延迟。例如,对 MyISAM 表的长久读请求会阻塞 SQL 线程,或对 InnoDB 表的任何事务都会创建 IX 锁并阻塞 SQL 线程中的 DDL。此外,还要考虑到在 MySQL 5.6 之前,slave 是单线程的,这也是导致 Slave SQL_THREAD 出现延迟的另一个原因。

MySQL 复制延迟的例子

让我通过 master status / slave status 示例向您展示,以确定 Slave 延迟问题到底是由于 IO_THREAD 还是由于 SQL_THREAD。

mysql-master> SHOW MASTER STATUS;
+------------------+--------------+------------------+------------------------------------------------------------------+
| File | Position  | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                                                |
+------------------+--------------+------------------+------------------------------------------------------------------+
| mysql-bin.018196 | 15818564     |                  | bb11b389-d2a7-11e3-b82b-5cf3fcfc8f58:1-2331947                   |
+------------------+--------------+------------------+------------------------------------------------------------------+

mysql-slave> SHOW SLAVE STATUSG\G
*************************** 1. row ***************************
Slave_IO_State: Queueing master event to the relay log
Master_Host: master.example.com
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.018192
Read_Master_Log_Pos: 10050480
Relay_Log_File: mysql-relay-bin.001796
Relay_Log_Pos: 157090
Relay_Master_Log_File: mysql-bin.018192
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB: 
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 5395871
Relay_Log_Space: 10056139
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 230775
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 2
Master_UUID: bb11b389-d2a7-11e3-b82b-5cf3fcfc8f58:2-973166
Master_Info_File: /var/lib/mysql/i1/data/master.info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Reading event from the relay log
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: bb11b389-d2a7-11e3-b82b-5cf3fcfc8f58:2-973166
Executed_Gtid_Set: bb11b389-d2a7-11e3-b82b-5cf3fcfc8f58:2-973166,
ea75c885-c2c5-11e3-b8ee-5cf3fcfc9640:1-1370
Auto_Position: 1

这清楚地表明,Slave IO_THREAD 滞后,显然 Slave SQL_THREAD 也因此滞后,从而导致复制延迟。正如你所看到的,Master 日志文件是 mysql-bin.018196(来自 SHOW MASTER STATUS),而 Slave IO_THREAD 在 mysql-bin.018192(来自 Slave status 的 Master_Log_File)上,这表明 Slave IO_THREAD 正在从该文件读取数据,而在 Master 上,它正在写入 mysql-bin.018196,因此 Slave IO_THREAD 落后了 4 个 binlog。与此同时,Slave SQL_THREAD 正在读取同一个文件,即 mysql-bin.018192(Slave status 中的 Relay_Master_Log_File),这表明 Slave SQL_THREAD 正在以足够快的速度应用事件,但它也滞后了,这可以从显示 Slave status 输出中的 Read_Master_Log_Pos 与 Exec_Master_Log_Pos 之间的差值观察到。

show slave status 的输出中 Master_Log_File 和 Relay_Master_Log_File 值相同,我们可以根据 Read_Master_Log_Pos - Exec_Master_Log_Pos 计算 Slave SQL_THREAD 的滞后时间。这样就能大致了解 Slave SQL_THREAD 应用事件(apply event)的速度。如上所述,如果 Slave IO_THREAD 滞后,那么 Slave SQL_THREAD 当然也会滞后。有关显示 Slave 状态输出字段的详细说明,请点击此处

此外,Seconds_Behind_Master 显示了以秒为单位的巨大延迟。不过,这可能会产生误导,因为它只度量最近执行的 relay log 与最近被 IO_THREAD 下载的 relay log 条目之间的时间戳差异。如果 Master 上有更多的 relay log,Slave 并不会将它们计入 Seconds_behind_master 的计算中。 你可以使用 Percona 工具包中的 pt-heartbeat 更准确地测量 Slave 日志的滞后情况。至此,我们学会了如何检查复制延迟 – 无论是 Slave IO_THREAD 还是 Slave SQL_THREAD。现在,让我来提供一些提示和建议,看看到底是什么原因导致了这种延迟。

提示和建议 - 导致复制延迟的原因及可能的修复方法

通常,Slave IO_THREAD 滞后是因为主/从之间的网络速度太慢。大多数情况下,启用 Slave 压缩协议(slave_compressed_protocol)有助于缓解 Slave IO_THREAD 的滞后。还有一个建议是禁用 Slave 上的 binlog 记录,因为它也是 IO 密集型的,除非你需要它来进行时间点恢复。

要尽量减少 Slave SQL_THREAD 的滞后,重点是优化查询。我的建议是启用配置选项 log_slow_slave_statements,这样 Slave 执行的耗时超过 long_query_time 的查询就会被记录到慢日志中。为了收集更多有关查询性能的信息,我还建议将配置选项 log_slow_verbosity 设置为"full”。

这样,我们就能看到是否有 Slave SQL_thread 执行的查询需要很长时间才能完成。关于如何在特定时间段内使用上述选项启用慢查询日志,你可以点击这里查看我之前的文章。需要提醒的是,log_slow_slave_statements 变量是在 Percona Server 5.1 中首次引入的,现在从 5.6.11 版起已成为 Vanilla MySQL 的一部分。在上游版本的 MySQL 中,log_slow_slave_statements 被作为命令行选项引入。详情请点击此处,而 log_slow_verbosity 是 Percona Server 的特定功能。

如果使用基于行的 binlog 格式,在 Slave SQL_THREAD 上出现延迟的另一个原因是:如果任何数据库表缺少主键或唯一键,就会在 Slave SQL_THREAD 上扫描表的所有行进行 DML,从而导致复制延迟,因此要确保所有表都有主键或唯一键。有关详细信息,请查看此错误报告 MySQL Bugs: #53375: RBR + no PK => High load on slave (table scan/cpu) => slave failure 您可以在 Slave 上使用以下查询来确定哪些数据库表缺少主键或唯一键。

mysql> SELECT t.table_schema,t.table_name,engine
FROM information_schema.tables t INNER JOIN information_schema .columns c
on t.table_schema=c.table_schema and t.table_name=c.table_name
GROUP BY t.table_schema,t.table_name
HAVING sum(if(column_key in ('PRI','UNI'), 1,0)) =0;

在 MySQL 5.6 中,针对这种情况进行了一项改进,在使用内存散列的情况下,slave_rows_search_algorithms 可以解这个问题。

请注意,当我们读取巨大的 RBR 事件时,Seconds_Behind_Master 并没有更新,因此 “滞后” 可能仅仅与此有关 – 我们还没有完成对事件的读取。例如,在基于行的复制中,庞大的事务可能会导致 Slave 端出现延迟,比如,如果你有一个 1000 万行的表,而你执行了 DELETE FROM table WHERE id < 5000000 操作,500 万行将被发送到 Slave 端,每一行都是单独的,速度会慢得令人痛苦。因此,如果必须不时地从庞大的表中删除最旧的行,那么使用分区可能是一个不错的选择,在某些工作负载中,使用 DROP 旧分区可能比使用 DELETE 更好,而且只有语句会被复制,因为这将是 DDL 操作。

为了更好地解释这个问题,假设分区 1 保存的行的 ID 从 1 到 1000000,分区 2 的 ID 从 1000001 到 2000000,以此类推,所以与其通过语句 DELETE FROM table WHERE ID<=1000000 进行删除,不如执行 ALTER TABLE DROP partition1。有关更改分区操作,请查阅手册 - 也请查阅我的同事 Roman 的这篇精彩文章,其中解释了复制延迟的可能原因。

pt-stalk 是 Percona 工具包中最优秀的工具之一,它可以在出现问题时收集诊断数据。你可以按如下方式设置 pt-stalk,这样只要出现 Slave 滞后,它就能记录诊断信息,我们随后就可以对这些信息进行分析,看看到底是什么原因导致了滞后。

------- pt-plug.sh contents
#!/bin/bash

trg_plugin() {
mysqladmin $EXT_ARGV ping &> /dev/null
mysqld_alive=$?

if [[ $mysqld_alive == 0 ]]
then
seconds_behind_master=$(mysql $EXT_ARGV -e "show slave status" --vertical | grep Seconds_Behind_Master | awk '{print $2}')
echo $seconds_behind_master
else
echo 1
fi
}
# Uncomment below to test that trg_plugin function works as expected
#trg_plugin
-------

-- That's the pt-plug.sh file you would need to create and then use it as below with pt-stalk:

$ /usr/bin/pt-stalk --function=/root/pt-plug.sh --variable=seconds_behind_master --threshold=300 --cycles=60 --notify-by-email=muhammad@example.com --log=/root/pt-stalk.log --pid=/root/pt-stalk.pid --daemonize

你可以调整阈值,目前是 300 秒,结合 -cycles 选项,这意味着如果 seconds_behind_master 值大于等于 300,持续 60 秒或更长时间,pt-stalk 就会开始捕获数据。添加 --notify-by-email 选项后,pt-stalk 捕获数据时就会通过电子邮件通知。你可以相应调整 pt-stalk 的阈值,这样它就会在问题发生时触发采集诊断数据。

结论

滞后 Slave 是一个棘手的问题,但也是 MySQL 复制中的常见问题。在这篇文章中,我试图涵盖 MySQL 复制 Slave 延迟的大多数方面。如果你知道复制延迟的其他原因,请在评论区与我分享。

本文翻译自:How to Identify and Resolve MySQL Replication Lag - Percona

推荐阅读:

<think>嗯,用户想解决MySQL主从同步延迟问题,需要排查和解决。首先,我得回想一下主从复制的基本原理,然后考虑可能导致延迟的各种原因。根据之前学过的知识,主从复制延迟可能有多个方面,比如硬件资源不足、配置问题、大事务、慢查询、主库高并发等等。 首先,用户可能需要检查硬件资源,比如主从库的CPU、磁盘IO、网络带宽是否足够。如果主库的磁盘IO慢,或者从库的硬件较差,都可能导致延迟。比如,如果主库使用SSD而从库用机械硬盘,那从库处理日志的速度可能跟不上主库。 然后,配置方面,尤其是MySQL的版本。低版本可能只支持单线程复制,而高版本支持多线程复制(比如基于库、表或行的并行复制)。需要确认用户使用的MySQL版本,如果版本较低,升级到支持多线程的版本可能有效。比如,引用[1]提到低版本MySQL单线程复制的问题,换成高版本支持多线程可以解决。 接着,复制线程的状态和延迟情况,可以用show slave status命令查看Seconds_Behind_Master参数。如果这个值很大,说明延迟严重。同时,检查IO线程和SQL线程是否正常运行,有没有错误信息。如果有错误,比如主键冲突或者网络中断,需要解决这些错误才能继续复制。 另外,大事务或者长事务也是一个常见原因。比如,用户执行了一个大量更新的操作,比如没有索引的DELETE语句,或者一次性插入大量数据,这些事务在从库上执行时会阻塞后续的事务,导致延迟。引用[2]提到,如果一条SQL执行20秒,从库就会延迟20秒,所以需要优化慢查询,拆分大事务。 慢查询日志也是需要检查的,找出执行时间长的SQL进行优化。比如,使用pt-query-digest分析慢日志,或者用show full processlist查看当前执行的查询。优化索引,减少锁竞争,分批次处理数据都可能有效。 主库的高并发写入也是一个因素。如果主库并发太高,从库处理不过来,可以考虑使用分片或者读写分离架构,分散压力。引用[3]提到业务层面是否能接受一定延迟,可能需要调整架构,比如将实时性要求高的查询仍然指向主库,其他查询走从库。 还有主从之间的网络延迟问题,比如跨机房同步可能会有较高的网络延迟,需要检查网络状况,使用ping或traceroute工具测试,或者考虑专线连接。 除此之外,一些参数配置也可能影响复制速度,比如sync_binlog、innodb_flush_log_at_trx_commit等。主库如果配置为双1(安全性高),可能会影响性能,而从库可能可以调整这些参数以提高复制速度,但需要权衡数据安全性。 另外,从库上的查询压力过大也可能导致延迟。如果从库同时处理大量读请求,可能会占用资源,影响SQL线程的应用速度。这时候可能需要增加从库数量,分摊读负载,或者升级从库硬件。 总结下来,排查步骤可能包括:检查硬件资源、查看复制状态、分析大事务和慢查询、优化配置、调整架构等。解决方案可能涉及升级MySQL版本、优化SQL、拆分事务、调整参数、增加从库或改善网络等。 用户可能需要分步骤来处理,先确定延迟的原因,再针对性地解决。比如,先检查Seconds_Behind_Master,然后看IO/SQL线程状态,再检查是否有慢查询或大事务,接着评估硬件和配置是否合理,最后考虑架构调整。每一步都需要具体的命令或工具支持,比如show slave status、查看进程列表、分析慢日志等。 可能还需要考虑是否使用了并行复制,以及并行复制的配置是否正确。例如,在MySQL 5.6之后支持基于库的并行复制,而5.7及更高版本支持基于逻辑时钟的并行复制,可以更高效地处理事务。 总的来说,解决主从延迟需要多方面的分析和调整,用户可能需要结合监控工具和日志分析来定位问题,然后逐步优化。</think>### 排查和解决MySQL主从复制延迟问题的步骤 #### 一、排查延迟原因 1. **检查主从复制状态** 执行 `SHOW SLAVE STATUS\G`,关注以下关键字段: - `Seconds_Behind_Master`:表示从库落后主库的时间(秒)。若为0则表示无延迟,若为`NULL`可能表示复制中断[^3]。 - `Slave_IO_Running` 和 `Slave_SQL_Running`:确认两个线程是否正常运行。 - `Last_Error`:检查是否有复制错误(如主键冲突、表不存在等)。 2. **分析硬件和网络资源** - **主从库硬件性能**:从库硬件配置(CPU、磁盘IO)若低于主库,可能导致应用日志速度慢[^1]。 - **网络延迟**:通过 `ping` 或 `traceroute` 检测主从库之间的网络延迟。跨机房同步时,建议使用专线或优化网络路径。 3. **识别大事务和慢查询** - 主库执行 `SHOW FULL PROCESSLIST`,检查是否有长时间运行的事务或锁等待。 - 分析从库的慢查询日志(`slow_query_log`),优化执行时间长的SQL语句[^2]。 - 大事务(如无索引的批量删除)会阻塞复制,需拆分为小事务[^2]。 --- #### 二、针对性解决方案 1. **升级MySQL版本** 低版本仅支持单线程复制,高版本(如MySQL 5.6+)支持多线程复制(参数 `slave_parallel_workers`),可显著提升从库处理效率。 2. **优化慢查询和大事务** - 通过 `EXPLAIN` 分析慢查询,添加索引或重构SQL。 - 将批量操作拆分为多次提交(例如:`INSERT INTO ... VALUES (...), (...);` 改为分批次插入)。 3. **调整复制配置** - **启用并行复制**(MySQL 5.7+): ```sql SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK'; SET GLOBAL slave_parallel_workers = 4; -- 根据CPU核心数调整 ``` - **减少主库写入压力**: 主库设置 `sync_binlog=0` 和 `innodb_flush_log_at_trx_commit=2`(牺牲部分安全性换取性能)。 4. **读写分离与架构优化** - 实时性要求高的查询直连主库,其他查询走从库。 - 增加从库数量,分摊读负载。 5. **监控与告警** 使用工具(如Percona Monitoring、Prometheus)监控 `Seconds_Behind_Master`,设置阈值告警。 --- #### 三、操作示例 1. **查看复制延迟** ```sql SHOW SLAVE STATUS\G -- 关注 Seconds_Behind_Master, Last_Error ``` 2. **优化慢SQL** ```sql -- 开启慢查询日志 SET GLOBAL slow_query_log = 'ON'; -- 分析慢查询 pt-query-digest /var/lib/mysql/slow.log ``` 3. **拆分大事务** ```sql -- 原语句(删除100万条数据) DELETE FROM logs WHERE create_time < '2020-01-01'; -- 优化为分批删除 DELETE FROM logs WHERE create_time < '2020-01-01' LIMIT 1000; -- 循环执行直至完成 ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜莺开源监控

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值