前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >[MYSQL] mysql主从延迟案例(有索引但无主键)

[MYSQL] mysql主从延迟案例(有索引但无主键)

原创
作者头像
大大刺猬
发布2025-01-21 13:58:13
发布2025-01-21 13:58:13
18500
代码可运行
举报
文章被收录于专栏:大大刺猬大大刺猬
运行总次数:0
代码可运行

导读

mysql的主从延迟问题还是很常见的, 通常都是没得索引或者数据量太大导致的. 如果有索引,选择性不好,还是会导致主从延迟增大. 本文主要分享一个 表有索引(where使用了的),但无主键 导致主从延迟增大的案例,并附2种解决方法.

模拟

环境准备

5.7和8.0都可以, 搭建一套主从环境, 参数如下:

代码语言:txt
复制
# hash_scan有BUG(hash碰撞),所以很多客户使用的table_scan
slave_rows_search_algorithms='TABLE_SCAN,INDEX_SCAN'

# 开启binlog中sql记录, 方便观察
binlog_rows_query_log_events = ON

数据准备

准备如下表, 即无主键, 但是存在索引, 而索引字段是日期, 每天数据量在10W左右.

代码语言:sql
复制
create table db1.t20250121(
id int,
name varchar(200),
startdate date,
key(startdate)
);

使用Python模拟表中的数据, 本次模拟1000W行数据(大概耗时2分半),分布很均匀(实际生产肯定没这么均匀的)

代码语言:python
代码运行次数:0
复制
import datetime
import pymysql
conn = pymysql.connect(
	host='127.0.0.1',
	port=3308,
	user='root',
	password='123456',
	)
startdate = datetime.datetime.strptime('2024-11-11','%Y-%m-%d')
datediff = datetime.timedelta(days=1)
idvalue = 0
for i in range(100):
	startdate += datediff
	for j in range(100):
		cursor = conn.cursor()
		sql = 'insert into db1.t20250121 values'
		for k in range(1000):
			idvalue += 1
			sql += f'({idvalue},"ddcw","{startdate.strftime("%Y-%m-%d")}"),'
		_ = cursor.execute(sql[:-1])
		_ = cursor.fetchall()
		conn.commit()

然后等待主从数据同步(也可以重建主从,就看愿不愿意等了)

模拟延迟

本次模拟删除5天的数据量, 即50W行. 主库直接走范围索引, 应该会非常快.

代码语言:sql
复制
-- 主库删除数据
delete from db1.t20250121 where startdate>='2024-12-01' and startdate <= '2024-12-05';

观察

查看从库主从延迟. 理论上是非常慢, 大概率跑不出来(太久). 为了方便观察, 我们可以使用如下脚本来查看延迟.

代码语言:shell
复制
while true;do sleep 1; echo -n "`date`    ";mysql -h127.0.0.1 -uroot -P3308 -p123456 -e 'show slave status\G' 2>/dev/null | grep Seconds_Behind_Master;done

现象

主库跑了2秒半, 但从库一直没有跑出来.

这种情况其实可以不用等了, 即使花半个月跑完了, 这种类型的SQL大概率每过几天就会来一次的.所以延迟基本上就不可能追上来了.

解决方案

方案1

加主键然后重建主从. 这是最稳妥的方法, 如果数据量太大的话, 也可以选择只重建某张表. 基础操作了,就不演示了.

注意:直接加自增列有数据不一致的风险(今天刚看到有大佬文章在讲), 加完后重建主从就没那么多问题了.

方案2

还有种花里胡哨的方法, 就是设置回放的算法为hash_scan, 这样每次扫描就会快很多. 区区10W行不在话下. 而且最主要的是不需要重建, 只需要重启复制进程即可.(甚至都不需要重启mysql)

代码语言:sql
复制
-- 停止主从进程 (会回滚到delete之前)
stop slave;
-- 验证数据量
select count(*) from db1.t20250121;
-- 修改参数
set global slave_rows_search_algorithms='index_scan,hash_scan';
-- 启动主从
start slave;

然后我们观察延迟, 发现很快就降下来了(1分钟就跑完了,虽然没得主库快, 但也是能接受的)

如果主从延迟非常大, 也就是从库已经跑了很多数据了, 那么回滚的时候会比较慢. 可参考:我这里延迟接近2小时,回滚耗时3.6秒.

说明hash_scan确实快.

那么代价是什么呢? hash存在hash碰撞(虽然概率低, 但目前有好几个客户都遇到了), 也就是数据可能不一致. 所以等待延迟下来后, 我们得再把参数修改回去.

代码语言:sql
复制
-- 停止主从
stop slave;
-- 修改参数
set global slave_rows_search_algorithms='index_scan,hash_scan';
-- 启动主从
start slave;

最后再校验下数据库的数据是否一致(仅校验无主键的表即可.) 我这里就简单使用checksum table来校验了

数据当然是一致的啦(hash碰撞的概率非常低的, 而且hash_scan是8.0的默认选择)

总结

mysql的表都建议加上主键/唯一键, 实在没得选的, 可以整联合主键, 还是选不上的, 就普通索引吧, 但前提是选择性好一点的. 那种一个key对应10W+的在主库上可能没啥影响, 但从库回放的时候就暴漏出性能问题了. 这种的话, 把date改成datetime. 让数据更分散一点,应该会好很多, 但就是得业务侧配合了.

没事多看些mysql的文档, 起码常见参数还是要搞懂的, 不常见的有个大概映像就行.

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • 模拟
    • 环境准备
    • 数据准备
    • 模拟延迟
    • 观察
    • 现象
  • 解决方案
    • 方案1
    • 方案2
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档