由于网络、CPU资源等原因,读写分离的延迟不可避免。所以,在引入读写分离方案的时候,要优先考虑数据延迟对业务是否有影响。下面主要讨论对有影响的情况下,有哪些方式可以尽量减少影响。
主从延迟可以很小,也可能很大,这是我们无法控制的。如果只用选择一个方案来解决延迟问题,就要考虑到最极端的情况,势必会让方案的成本超出预期。所以,最好是根据业务的重要程度,选择不同的方案。
对于一致性要求高的地方,这是最简单的方式,读主库就没有数据延迟问题。所以,引入读写分离之后,一定要对业务进行合理的划分。在功能实现上,如果用的ShardingPhere框架,默认在事务里的查询会默认走主库,一般就是写库。打开spring.shardingsphere.props.sql-show=true
配置就能打印SQL在哪个库执行。
Actual SQL: master ::: Insert Into ...
Actual SQL: slave0 ::: Select * From ...
大多数情况下,延迟时间在1秒之内。写入成功后,主动等待1秒后再查询,可以确保大多数情况能查到数据。这种方式实现简单,对业务侵入性低。看似很low,但能解决对一致性有要求的情景。当然,无脑等1秒在延迟很低的情况下,1秒就是白等。
GTID全局事务ID是Mysql5.6引入,我们可以通过获取主库提交事务后的GTID,在从库里判断GTID是否已经执行。已经执行则代表主库的事务已经同步到从库。
先通过SHOW VARIABLES LIKE 'gtid_mode'
命令查看GTID是否开启,返回ON表示开启。
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| gtid_mode | ON |
+---------------+-------+
未开启的话要在my.cnf配置文件里开启。
gtid_mode = ON
enforce_gtid_consistency = ON
在提交事务后,通过SHOW VARIABLES LIKE 'gtid_executed';
或者SELECT @@GTID_EXECUTED;
查询到已经提交的事务ID集合,可能会返回ff782504-9b09-11ee-a4c3-0050568c4224:1-3
这样的格式,然后我们处理一下,获取最后一个事务IDff782504-9b09-11ee-a4c3-0050568c4224:3
。
在从库通过*wait_for_executed_gtid_set*(gtid_set [, timeout])
方法可以查询指定的事务ID是否已经执行成功,方法返回0表示已经执行;返回1表示执行超时;返回其他数值表示失败。
5f4cea4d-38b5-11ec-8814-0800272d6057:1,5f4cea4d-38b5-11ec-8814-0800272d6057:3
或连续的话用5f4cea4d-38b5-11ec-8814-0800272d6057:1-5
这样的格式slave_net_timeout
** 变量的值,默认是60秒。例如,我们执行select *wait_for_executed_gtid_set*('5f4cea4d-38b5-11ec-8814-0800272d6057:3', 1);
返回了0,我们就知道数据同步完成,就可以继续查询数据。如果返回非0,就直接查主库。
要判断主从严格一致的成本是很高的,用到GTID来判断主从延迟,对代码的侵入性很高,而且要额外的查询GTID和判断GTID是否同步,性能也会受到影响。如果业务场景对一致性要求很高,为了简化编程,还不如直接查主库。如果对一致性要求不高,直接读从库,或者等待1秒也是不错的方式。