暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

线上环境MySQL复制延迟一例

GrowthDBA 2021-11-13
1577
是因为快到年底的原因吗,公司MySQL线上环境告警窜动,主从同步问题频率较高。昨天在快下班之际,收到一个MySQL延迟告警,我知道,活儿来了,文章素材有了。

01


环境概况 & 初步排查.



问题环境概况及部分参数

MySQL Version:5.7.27同步方式:ROW+GTID+增强半同步关键参数:rpl_semi_sync_master_wait_point=AFTER_SYNCslave_parallel_type=LOGICAL_CLOCKslave_parallel_workers=8binlog_transaction_dependency_tracking=COMMIT_ORDER

初步排查

  • 查看主从同步状态:

show slave status\G;

现象:通过结果可以看出Slave_IO_Running、Slave_SQL_Running状态都为Yes,证明IO_Thread和SQL_Thread是正常的,但是Seconds_Behind_Master却在一直增长,很奇怪,所以首先想到的是大事务导致的。

  • 主库,查看所有进程:

show processlist;

看起来没有什么问题,正常连接到数据库的操作用户也都为Sleep,主库上没有执行大事务。

  • 从库,查看所有进程:

诶!?发现了一些端倪。通过上述MySQL环境概览和参数我们可以确定从库开启了并行复制,指定了8个worker线程,但是目前只有一个worker线程的State为:Executing event,其余worker线程都处于空闲状态,并且这个worker线程的Time执行时间几乎和Seconds_Behind_Master延迟时间一致。
通过初步定位到的结果判断,得出的结论:SQL_Thread只有一个worker线程在工作,并行复制模式已经退化成了单线程模式,延迟的原因应该就在此,所以我们再深入定位具体问题。



02


延迟原因定位.



通过上面的初步判断得到的结论是从库处于Executing event状态,SQL_Thread正在且只有一个worker线程在工作,并行复制模式已退化成单线程模式了。所以,再次看一下主从同步状态:

show slave status\G;

多次执行后发现:

从库在回放到主库mysql-bin.000006文件996411549这个位置时不刷新卡在这里了。
根据提示到主库解析一下binlog:
/usr/local/mysql3307/bin/mysqlbinlog -v --base64-output=decode-rows --start-position=996411549 /mysql/mysql3307/mysql-bin.000006 > mysqlbinlog.log

发现这个表正执行删除操作,看一下这张表的表结构和数据量:
SHOW CREATE TABLE `demo`.`ke_consumer`;SELECT COUNT(*) FROM `demo`.`ke_consumer`;

至此,导致延迟的原因定位到了:执行DELETE操作的表`demo`.`ke_consumer`没有主键、没有索引,同时表数据量很大,因worker线程在执行这个删除操作时发生了延迟



03


问题处理 & 原理浅析.



问题处理

定位到问题后,剩下的事情就好办了。
1、和对应开发人员确认此库使用情况

数据库是用来存放监控数据的,并且是非业务且只用于主备切换的备库。监控程序是直接使用的Kafka Eagle,库表结构是工具本身定义好的,所以导致了没有主键的情况。
2、确保我们操作对业务无影响后,停掉同步
stop slave;

3、确定出条件选择性好的字段:timespan

SELECT * FROM `demo`.`ke_consumer` limit 10;SELECT COUNT(DISTINCT(timespan)),COUNT(*) FROM `demo`.`ke_consumer`;

4、设置Session级别不记录binlog,为条件选择性好的字段添加唯一索引
SET sql_log_bin=0;ALTER TABLE `demo`.`ke_consumer` ADD UNIQUE INDEX(timespan);
5、还原记录binlog设置,开启同步
SET sql_log_bin=1;start slave;

6、多次检查同步状态:

show slave status\G;

7、确认Exec_Master_Log_Pos是否还会发生不变的情况。如发生同样使用解析binlog文件和position的方式定位并重复上述步骤。并最终确定同步状态完全正常。

# 解析binlog日志确定大事务所在语句/usr/local/mysql3307/bin/mysqlbinlog -v --base64-output=decode-rows --start-position=33077322 /mysql/mysql3307/mysql-bin.000007 > mysqlbinlog_1.log/usr/local/mysql3307/bin/mysqlbinlog -v --base64-output=decode-rows --start-position=140003442 /mysql/mysql3307/mysql-bin.000007 > mysqlbinlog_2.logmore mysqlbinlog_1.logmore mysqlbinlog_2.log# 停止同步stop slave;# 设置执行SQL不记录binlogset sql_log_bin=0;# 条件选择性好的字段添加唯一索引ALTER TABLE `demo`.`ke_logsize` ADD UNIQUE INDEX(timespan);ALTER TABLE `demo`.`ke_consumer_bscreen` ADD UNIQUE INDEX(timespan);# 还原执行SQL记录binlogset sql_log_bin=1;# 启动同步start slave;# 确认同步状态show slave status\G;

至此,同步延迟问题解决。
小提示
Q-1:为什么在从库创建索引前,需要停掉同步?
A-1:因为创建索引是DDL语句,如果从库的worker线程正在对表进行删除回放,创建索引操作可能会被MDL锁(MetaData Lock,元数据锁)阻塞。
Q-2:为什么在创建索引前,要设置执行SQL不记录binlog?
A-2:为了防止binlog记录创建索引的操作记录,我们只是单一想加快从库的回放速度,所以只在从库层面添加一个索引而防止加索引操作被同步到别的库。
Q-3:创建索引后,会影响同步吗?
A-3:不会,我们只是添加了一个快速查得数据的物理目录结构,没有对原表的字段进行修改,所以不会影响同步,存在的差异在于从库该表的物理大小要稍大于主库。
Q-4:如何判断主从同步无延迟?
A-4:show slave status\G;  --关注以下状态
Slave_IO_Running: Yes(IO_Thread状态正常)
Slave_SQL_Running: Yes(SQL_Thread状态正常)
Seconds_Behind_Master: 0(从库落后于主库的时间:单位秒)
Retrieved_Gtid_Set = Executed_Gtid_Set(从库接收到的Gtid集合等于从库已经执行完成的Gtid集合)

原理浅析

为什么没有主键的表在同步的时候会产生延迟呢?因为MySQL主从复制是基于行的复制,假设主库执行一个'DELETE FROM T;'的SQL(表T中有10000行记录)这时MySQL会把这个SQL按照每条记录,拆分成10000条DELETE SQL(刚才我们解析出的binlog文件截图也已经验证)在备库上执行,MySQL这么做的目的也是最大程度的保证同步数据的可靠性

但是可靠性的提升伴随而来的便是日志量的增多,同步过程会占用大量带宽

其次,表即无主键,也没有索引。所以在从库回放过程中每执行一次DELETE SQL都要做全表扫描才能定位到要删除的那一条数据如果数据量很大,每次执行一条SQL,全表扫描一次,操作成本和时长可想而知同时没有主键,并行复制也可能会退化为单线程模式,就好比100件事情1人做和100件事情好多人做一样。




04


小结.



其实在数据库开发规范中最为重要的一条就是:创建表时,必须使用InnoDB存储引擎、且必须要定义主键。诶,等等,数据库开发规范?哈哈,,写作素材又有了,有时间我会把我收集的数据库开发规范整理一下出一篇文章,等我!~
通过今天这个案例,更加知道了开发规范的重要性,规范制定好并遵守,其实可以规避90%以上的问题。每天进步一点点,今天就到这里吧。



end


文章转载自 GrowthDBA,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论