问题:
数据库是读写分离架构,事务中的读操作没有强制走主库,这个时候是不是会有读延迟,读的操作比写操作慢,导致没有及时读到其他事务写的数据
总结
- 读写分离和主从架构是一个概念,主库写,从库读,主从同步
- 主从同步分成异步、半同步两种方式
- 产生事务时,会强制走主库,事务中的读操作和写操作都会强制走主库
https://blog.csdn.net/qq_46371374/article/details/149513968
https://www.cnblogs.com/wxg1990/articles/19465535
https://www.jianshu.com/p/8b43bbf52dc6
https://zhuanlan.zhihu.com/p/29166694
在读写操作处于同一事务时,读写分离通过事务绑定主库 和强制读主库策略运作,确保事务的原子性和一致性。以下是具体运作机制和解决方案:
一、核心运作机制
- 事务绑定主库
当事务开始时,所有读写操作(包括SELECT查询)均路由到主库执行。例如:- 用户提交订单(写操作)和查询订单状态(读操作)在同一个事务中,系统会强制将查询请求也发送到主库,避免从库数据延迟导致的不一致。
- 事务结束后,后续非事务性读操作可重新路由到从库。
- 主从数据同步
主库执行完事务后,通过异步复制 (默认)或半同步复制 (可选)将数据变更同步到从库:- 异步复制:主库写入binlog后立即返回成功,不等待从库确认。性能高,但可能丢失少量数据(如主库崩溃时未同步的日志)。
- 半同步复制:主库至少等待一个从库确认收到binlog后才返回成功。牺牲少量性能,但提升数据安全性。
二、事务中的读写分离解决方案
方案1:强制读主库(推荐)
- 适用场景:对数据一致性要求极高的业务(如支付、订单状态查询)。
- 实现方式 :
-
代码层面 :通过注解或框架配置,强制事务中的读操作走主库。例如:
java`@Transactional // 开启事务 public void submitOrder(Order order) { orderMapper.insert(order); // 写操作(主库) Order result = orderMapper.selectById(order.getId()); // 强制读主库 }` -
中间件层面:使用ShardingSphere等中间件,自动识别事务中的读操作并路由到主库。
-
方案2:同步复制(牺牲性能保一致)
-
适用场景:金融交易等绝对不允许数据丢失的场景。
-
配置示例 (MySQL):
ini`# 主库配置 sync_binlog=1 # 每次事务提交前将binlog刷盘 innodb_flush_log_at_trx_commit=1 # 每次事务提交前将redo log刷盘 # 从库配置(半同步复制) rpl_semi_sync_master_enabled=1 rpl_semi_sync_master_wait_for_slave_count=1` -
缺点:主库响应时间增加,吞吐量下降。
方案3:延迟读取(折中方案)
- 适用场景:对实时性要求不高,但需避免主从延迟的业务(如用户评论展示)。
- 实现方式 :
- 写操作后,延迟一段时间(如500ms)再执行读操作,给从库留出同步时间。
- 通过缓存(如Redis)暂存写操作结果,读操作优先从缓存获取。
三、关键问题与对策
- 主从延迟导致的数据不一致
- 问题:事务提交后,从库未同步完成,此时读从库会得到旧数据。
- 对策 :
- 强制读主库(方案1)。
- 使用**GTID(全局事务标识)**追踪事务同步状态,确保读操作只发生在已同步的从库上。
- 事务跨主从库的分布式事务问题
- 问题:若事务涉及多个主库或跨库操作,需保证ACID特性。
- 对策 :
- 使用XA协议 (两阶段提交)或TCC模式(补偿事务)实现分布式事务。
- 避免跨库事务,通过业务设计将操作限制在单个库内。
四、实际案例
电商平台订单支付场景:
- 用户提交订单(写主库)。
- 系统扣减库存(写主库)。
- 查询订单状态(强制读主库,避免从库延迟)。
- 支付成功后,异步更新从库数据(如用户积分、商品销量)。
技术栈:
- 数据库:MySQL(主从复制+半同步)。
- 中间件:ShardingSphere(读写分离+事务路由)。
- 缓存:Redis(暂存热点数据,减轻数据库压力)。