数据库主从有延迟怎么处理
数据库主从有延迟怎么处理
面试官提问
“在高并发互联网场景下,数据库主从延迟是高频痛点,会导致用户读到旧数据、业务逻辑异常。请问你在实际项目中是如何系统性解决这个问题的?”
候选人回答 🎯
面试官您好,关于主从延迟问题,我会从根本原因→分层解决方案→监控兜底三个维度展开,结合我在电商订单系统和支付系统的实战经验来分享,核心原则是优先业务层优化,成本最低见效最快。
先搞懂:主从延迟的本质 💡
主从延迟本质是主库并发写能力 > 从库串行重放能力的矛盾。MySQL 主从复制的核心流程如下:
延迟高发点:第 3 步 SQL 线程重放(MySQL 5.6 之前完全单线程),主库每秒可执行数千并发写,从库只能逐个重放,天然存在延迟。
常见触发炸弹:
- 大事务(批量更新 10 万 + 条数据)
- 大表 DDL(千万级表加索引)
- 从库硬件性能弱于主库
- 跨机房网络抖动
分层解决方案(优先级从高到低)✅
我在项目中会严格按照这个优先级落地,避免上来就搞复杂架构。
1. 业务层优化(最优先,成本≈0)
- 强制走主库:仅用于实时性要求极高的核心场景(如支付后订单状态查询、库存扣减后校验)。⚠️ 绝对不能滥用,否则主库会被打垮。
- “写后 N 秒读主库” 策略:写操作后的 N 秒内(N 取主从平均延迟,通常 1-3 秒)读主库,N 秒后读从库。对业务侵入极小,覆盖 80% 以上场景。
- “读自己写” 策略:用户自己写的数据(如发布文章、修改个人信息),10 秒内读主库;其他用户的数据读从库。兼顾实时性和性能。
- 拆分大事务:把批量更新拆成每次 1000-5000 条的小事务,避免 binlog 过大导致从库重放阻塞。
- 规避大表 DDL:使用
pt-online-schema-change或gh-ost工具执行 DDL,MySQL 8.0 + 直接用原生 Online DDL,避免锁表引发雪崩。
2. 数据库层优化(提升复制性能)
升级 MySQL 版本:这是性价比最高的优化
- MySQL 5.6:基于库的并行复制
- MySQL 5.7:基于组提交的并行复制(性能提升 10 倍 +)
- MySQL 8.0:基于写集的并行复制(几乎接近主库并发度)
从库配置调优:
- 关闭从库 binlog(如果不需要从库再做主库)
- 设置
sync_binlog=0、innodb_flush_log_at_trx_commit=2,牺牲少量安全性换取写入性能
一主多从:横向扩展读能力,分散单个从库的读压力,间接降低延迟。
半同步复制:确保主库写操作至少同步到 1 个从库,避免数据丢失。⚠️ 会增加主库写入延迟,仅用于金融等强一致性场景。
3. 架构层优化(从根本上解决问题)
- 引入缓存:把热点数据放入 Redis,读请求优先查缓存。既减少数据库压力,又彻底解决主从延迟问题。⚠️ 需配套解决缓存与数据库的一致性问题。
- 消息队列异步化:把非实时写操作(如日志记录、积分发放、短信通知)放入 MQ 异步消费,削峰填谷,避免主库瞬间压力过大。
- 分库分表:将大表拆分为多个小表,分布在不同实例上,降低单个库的写入压力,从根源上减少延迟。
- 分布式数据库:使用 TiDB、OceanBase 等原生分布式数据库,支持强一致性读写,天生无主从延迟问题。适合对一致性和扩展性要求极高的核心系统。
监控与兜底(必不可少)🚨
无论怎么优化,极端情况(如大促峰值、网络故障)下仍可能出现延迟飙升,必须做好兜底。
- 精准监控延迟:
- 不要只依赖
show slave status的Seconds_Behind_Master(存在误差) - 使用
pt-heartbeat工具,通过主库定时写入心跳数据计算准确延迟 - 告警阈值:延迟 > 1 秒预警,>5 秒触发自动降级
- 不要只依赖
- 自动降级策略:当延迟超过阈值时,自动将核心读请求切换到主库,同时降级非核心功能(如商品推荐、历史订单查询)。
- 定期故障演练:每季度模拟主从延迟飙升场景,验证降级策略的有效性,确保真实故障时能快速响应。
方案对比速查表
| 解决方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 强制走主库 | 支付、库存等核心实时场景 | 简单直接,零延迟 | 增加主库压力,滥用会导致宕机 |
| 写后 N 秒读主库 | 大部分普通读写分离场景 | 成本极低,业务侵入小 | N 值需根据监控动态调整 |
| 升级 MySQL 版本 | 所有 MySQL 项目 | 性能提升显著,无业务侵入 | 存在升级风险,需提前测试 |
| 引入 Redis 缓存 | 热点数据读多写少场景 | 彻底解决延迟,提升系统吞吐量 | 需处理缓存一致性,增加复杂度 |
| 分布式数据库 | 金融、电商核心系统 | 天生无主从延迟,支持水平扩展 | 成本高,学习曲线陡峭 |
总结 📝
处理主从延迟的核心思路是:能用业务手段解决的,绝不改数据库;能改数据库解决的,绝不搞架构重构。
在我负责的电商订单系统中,我们采用了 “写后 1 秒读主库 + 核心场景强制走主库 + Redis 缓存热点数据” 的组合方案,同时配合 MySQL 8.0 并行复制和精准监控,将主从延迟稳定控制在 100ms 以内,即使在双 11 大促峰值也没有出现过因延迟导致的业务异常。
核心代码实现(技术亮点)✨
以下是我在项目中实际使用的核心代码,零业务侵入性,通过 AOP 和动态数据源实现。
1. 基于 ThreadLocal+AOP 的 "写后 N 秒读主库" 实现
技术亮点:自动拦截所有写操作,通过 ThreadLocal 传递写操作时间戳,数据源路由时自动判断是否走主库,业务代码完全无感知。
/**
* 数据源上下文,用于保存当前线程是否需要走主库
*/
public class DataSourceContextHolder {
private static final ThreadLocal<Long> WRITE_TIME_HOLDER = new ThreadLocal<>();
// 写操作后3秒内读主库
private static final long MASTER_READ_DURATION = 3000;
public static void markWrite() {
WRITE_TIME_HOLDER.set(System.currentTimeMillis());
}
public static boolean shouldReadMaster() {
Long writeTime = WRITE_TIME_HOLDER.get();
if (writeTime == null) {
return false;
}
boolean result = System.currentTimeMillis() - writeTime < MASTER_READ_DURATION;
// 超过时间后清除,避免内存泄漏
if (!result) {
WRITE_TIME_HOLDER.remove();
}
return result;
}
public static void clear() {
WRITE_TIME_HOLDER.remove();
}
}/**
* 写操作拦截AOP,自动标记写操作
*/
@Aspect
@Component
public class WriteOperationAspect {
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalPointcut() {}
@AfterReturning("transactionalPointcut()")
public void afterWrite(JoinPoint joinPoint) {
// 事务提交成功后标记写操作
DataSourceContextHolder.markWrite();
}
}/**
* 动态数据源路由
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
if (DataSourceContextHolder.shouldReadMaster()) {
return "master";
}
return "slave";
}
}2. 基于 Redis 的 "读自己写" 策略实现
技术亮点:针对用户维度的读写分离,用户自己写的数据读主库,其他用户的数据读从库,兼顾实时性和性能。
/**
* 用户维度的数据源路由工具类
*/
@Component
public class UserReadWriteRouter {
@Autowired
private StringRedisTemplate redisTemplate;
// 用户写操作后10秒内读主库
private static final long USER_MASTER_READ_DURATION = 10000;
private static final String USER_WRITE_KEY_PREFIX = "user:write:";
/**
* 用户写操作后调用此方法
*/
public void markUserWrite(Long userId) {
String key = USER_WRITE_KEY_PREFIX + userId;
redisTemplate.opsForValue().set(key, "1", USER_MASTER_READ_DURATION, TimeUnit.MILLISECONDS);
}
/**
* 读操作前判断是否需要走主库
*/
public boolean shouldReadMasterForUser(Long userId) {
if (userId == null) {
return false;
}
String key = USER_WRITE_KEY_PREFIX + userId;
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
}3. 基于 Nacos 配置中心的自动降级实现
技术亮点:无需重启服务,动态调整降级策略,支持分级降级,主库流量超过阈值时自动拒绝非核心请求。
/**
* 主从延迟降级配置
*/
@Configuration
@RefreshScope
public class MasterSlaveDegradeConfig {
// 主从延迟阈值(ms),超过则触发降级
@Value("${master-slave.delay-threshold:5000}")
private long delayThreshold;
// 是否开启自动降级
@Value("${master-slave.auto-degrade:true}")
private boolean autoDegrade;
// 核心接口强制走主库
@Value("${master-slave.core-force-master:true}")
private boolean coreForceMaster;
// 非核心接口降级开关
@Value("${master-slave.non-core-degrade:false}")
private boolean nonCoreDegrade;
// getter方法
}/**
* 降级拦截器
*/
@Component
public class DegradeInterceptor implements HandlerInterceptor {
@Autowired
private MasterSlaveDegradeConfig degradeConfig;
@Autowired
private MasterSlaveMonitorService monitorService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!degradeConfig.isAutoDegrade()) {
return true;
}
long currentDelay = monitorService.getCurrentDelay();
// 延迟超过阈值,触发降级
if (currentDelay > degradeConfig.getDelayThreshold()) {
// 核心接口强制走主库
if (isCoreInterface(request) && degradeConfig.isCoreForceMaster()) {
DataSourceContextHolder.forceMaster();
return true;
}
// 非核心接口直接降级
if (!isCoreInterface(request) && degradeConfig.isNonCoreDegrade()) {
response.getWriter().write("系统繁忙,请稍后再试");
return false;
}
}
return true;
}
private boolean isCoreInterface(HttpServletRequest request) {
String uri = request.getRequestURI();
return uri.startsWith("/api/order") || uri.startsWith("/api/pay") || uri.startsWith("/api/inventory");
}
}4. pt-heartbeat 精准监控延迟 SQL
技术亮点:比show slave status的Seconds_Behind_Master准确 100 倍,可精确到毫秒级。
-- 主库创建心跳表
CREATE TABLE heartbeat (
id INT PRIMARY KEY,
ts TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)
);
-- 主库每秒写入一次心跳数据(可通过crontab或程序实现)
INSERT INTO heartbeat (id, ts) VALUES (1, NOW(3)) ON DUPLICATE KEY UPDATE ts = NOW(3);
-- 从库查询延迟(单位:毫秒)
SELECT ROUND((UNIX_TIMESTAMP(NOW(3)) - UNIX_TIMESTAMP(ts)) * 1000) AS delay_ms FROM heartbeat;核心技术难点及解决方案 🛠️
| 技术难点 | 问题描述 | 根本原因 | 解决方案 | 实战注意事项 |
|---|---|---|---|---|
| 大事务导致的延迟雪崩 | 一个大事务执行几分钟,从库重放也需要几分钟,期间所有从库读都是旧数据,甚至引发连锁反应 | binlog 是按事务提交顺序写入的,大事务的 binlog 会阻塞后面所有事务的重放 | 1. 强制拆分大事务为每次 1000-5000 条的小事务 2. 代码中添加事务大小校验 3. 数据库层面设置 max_binlog_size限制 | 拆分时要考虑事务原子性,用最终一致性代替强一致性;避免在大促期间执行批量更新 |
| 大表 DDL 导致的长时间延迟 | 千万级表加索引,主库执行几分钟,从库重放也需要几分钟,甚至锁表导致从库读完全阻塞 | 传统 DDL 会加排他锁,binlog 重放时也会锁从库表,导致从库所有读请求等待 | 1. MySQL 8.0 + 使用原生 Online DDL 2. 低版本使用gh-ost工具 3. DDL 执行期间限速,避免影响主库 | DDL 必须在凌晨低峰期执行;提前备份数据;先在测试环境验证;执行期间密切监控主从延迟 |
| 缓存与数据库一致性问题 | 引入缓存解决主从延迟,但缓存和数据库不一致会导致更严重的业务问题(如超卖、订单状态错误) | 数据库主从延迟 + 缓存更新顺序不当 + 缓存删除失败 | 1. 采用 "更新数据库 + 删除缓存" 的最终一致性方案 2. 设置缓存过期时间兜底 3. 缓存删除失败时通过 MQ 重试 | 绝对不要用 "更新缓存 + 更新数据库" 的顺序;不要用 "更新数据库 + 更新缓存";删除缓存要放在事务提交之后 |
| 自动降级的准确性和安全性 | 降级误判会导致主库压力过大甚至宕机,降级不及时会导致大量业务异常 | 延迟监控不准确 + 降级策略不合理 + 缺乏流量控制 | 1. 使用pt-heartbeat精准监控延迟2. 多维度告警(延迟 + 主库 CPU + 主库连接数) 3. 分级降级策略 4. 设置主库流量上限 | 非核心功能优先降级;核心功能只在极端情况降级;必须保留手动降级开关;降级后要有告警通知 |
| 跨机房主从延迟不可避免 | 跨机房网络延迟几十毫秒,主从延迟至少几十毫秒,无法通过任何优化完全消除 | 物理距离导致的网络传输延迟(光速限制) | 1. 核心业务同机房部署 2. 采用单元化架构,每个单元独立读写 3. 非核心数据允许延迟 | 单元化要做好数据分片,避免跨单元调用;跨机房同步只用于灾备,不用于日常读写 |
| 从库并行复制效果不佳 | 升级到 MySQL 5.7/8.0 后,并行复制仍然没有达到预期效果 | 并行复制依赖于事务的组提交,组提交数量不足会导致并行度低 | 1. 调整主库binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count参数2. 增加主库并发写压力,提高组提交数量 | 调整参数会增加主库写入延迟,需要在延迟和吞吐量之间做平衡;MySQL 8.0 的写集并行复制效果最好 |
模拟真实面试
模拟真实面试
面试官 😊
“同学,前面基础题聊得不错,咱们来道场景设计题。
我们的电商系统用 MySQL 一主两从、读写分离。用户下单时写主库,下单后页面立刻跳转订单详情,查询走从库。有用户反馈:明明付款成功了,却提示‘订单不存在’,但刷新一下又好了。你分析下这是什么问题?如果让你来处理,你会怎么设计?”
候选人 🤔
“好的面试官,这明显是主从复制延迟导致的问题。我画个时序图就清楚了。”
“核心矛盾是写完后立刻读,读的是还没同步完的从库。解决方案我会从四个层面打组合拳:强制路由、复制加速、缓存补偿、应用兜底,再加一套监控。”
面试官 🧐
“不错,抓得准。那先说说第一个层面——强制路由,你怎么落地?”
候选人 💡
“最关键的一招:关键写后读,直接绕过从库,强制走主库。我们用的是 ShardingSphere 读写分离,代码里这么处理:”
// 下单服务
public Order placeOrder(Order order) {
orderDao.insert(order); // 写主库
// 紧接着查询,强制走主库
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setMasterRouteOnly();
return orderDao.selectById(order.getId());
}
}“这样就能保证下单后立即查询返回的是最新数据。但这里有两个要点:
- ✅ 只用在‘写后立即读’的高敏感场景,比如下单、支付回调、库存扣减等;
- ❌ 滥用会打爆主库,所以我们配合了一套决策树,后面会提到。”
面试官 🤔
“如果这个场景不能强制走主库,或者主库压力已经很大了,你怎么办?”
候选人 🛠️
“那就上第二、第三招:复制加速 + 缓存补偿。
- 复制加速主要是调参数,让从库尽量快:
| 措施 | 作用 | 代价 |
|---|---|---|
| 半同步复制 (AFTER_SYNC) | 主库提交事务需等至少一个从库收到 binlog | 写操作 RT 增加约 1ms |
| 并行复制 (MTS) | 从库多线程回放 relay log | 需关注 slave_parallel_workers,避免大事务冲突 |
| 硬件升级 | SSD + 万兆网络 | 💰 砸钱 |
- 缓存补偿更常用,我们会在主库写入成功后,立即往 Redis 写一份短期缓存,过期时间 3~5 秒,刚好覆盖延迟窗口。
“查询时先读 Redis,读不到再降级到从库。这样既透明,又不会给主库增加读负担。”
面试官 👍
“缓存补偿很实用。但万一 Redis 也挂了,或者缓存那几秒都没命中,从库依然没数据呢?”
候选人 🧯
“这时候就要靠应用层兜底了。我们会在查不到订单时做双重确认:
public Order getOrder(String orderId) {
Order order = slaveDao.get(orderId);
if (order == null) {
// 可能延迟,再查一次主库
order = masterDao.get(orderId);
if (order == null) {
// 真没有就友好提示
throw new BizException("订单处理中,请稍后刷新");
}
}
return order;
}“这样就算从库延迟非常严重,也能从主库拿到真实数据。再配合前端‘正在处理中,请稍候’的提示 + 自动刷新,用户体验就不会崩。”
面试官 🧐
“方案挺全的。这些策略什么时候用哪种?给我个决策思路。”
候选人 🧭
“我梳理了一张决策树图,团队按这个选型。”
“最后,光靠方案不行,还得有监控兜底:我们会定时写心跳表,计算主从延迟秒数,接入 Prometheus + Grafana,一旦延迟超过 3 秒就触发报警,甚至自动降级:把所有读暂时切回主库,直到延迟恢复。”
面试官 😊
“思路很清晰,从业务路由、数据层加速、缓存补偿到应用兜底,最后还有监控降级。整个体系很完整,而且给出了具体落地点,不是纸上谈兵。这个场景设计题你过了。”
候选人 😄
“谢谢面试官,我平时确实在生产环境处理过类似问题,所以印象比较深。”
面试官 👨💻
“方案讲得不错,落地能力也得验证一下。那接下来咱们现场说几段核心代码,再盘一盘这个场景下的技术难点,你怎么解决的。”
核心代码 & 技术亮点 📝
1. 强制路由走主库(读写分离中间件 ShardingSphere)
场景:下单后立刻查询订单详情,必须读到最新数据。
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/**
* 下单并返回最新订单详情
* 技术亮点:利用 HintManager 强制走主库,方法级粒度控制
*/
public OrderVO placeOrderAndGet(OrderDTO dto) {
// 1. 写主库
Order order = convertToEntity(dto);
orderMapper.insert(order);
// 2. 读强制走主库,保证写后读一致性
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.setMasterRouteOnly(); // 🎯 核心:一行代码绕过从库
Order freshOrder = orderMapper.selectById(order.getId());
return convertToVO(freshOrder);
}
}
}✨ 技术亮点:
- 使用
try-with-resources确保HintManager被自动关闭,避免线程污染; - 对业务代码侵入性极小,只在需要的方法上加这段即可。
2. 缓存补偿:写主库同步写 Redis
场景:不能强制走主库时,用短期缓存覆盖延迟窗口。
public OrderVO getOrderWithCache(String orderId) {
// 1. 查 Redis
String cacheKey = "order:detail:" + orderId;
String cachedJson = redisTemplate.opsForValue().get(cacheKey);
if (cachedJson != null) {
return JSON.parseObject(cachedJson, OrderVO.class); // 命中,直接返回 ✅
}
// 2. 查从库
Order order = slaveOrderMapper.selectById(orderId);
if (order != null) {
// 回填缓存(防缓存击穿可加短过期时间)
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(order),
5, TimeUnit.SECONDS);
return convertToVO(order);
}
// 3. 兜底:从库也没有,再查主库(预防极端延迟)
order = masterOrderMapper.selectById(orderId);
if (order != null) {
// 补上缓存,并返回
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(order),
5, TimeUnit.SECONDS);
return convertToVO(order);
}
throw new BizException("ORDER_NOT_FOUND", "订单处理中,请稍后刷新");
}// 下单成功后同步写缓存
@Transactional
public Order placeOrder(OrderDTO dto) {
Order order = convertToEntity(dto);
masterOrderMapper.insert(order);
// 🎯 亮点:写入缓存,TTL 3~5 秒覆盖延迟
String cacheKey = "order:detail:" + order.getId();
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(order),
5, TimeUnit.SECONDS);
return order;
}✨ 技术亮点:
- 写入缓存的动作和主库写入放在同一个事务里(或至少是紧耦合的本地操作),保证数据一致性;
- 多级查询链:Redis → 从库 → 主库,层层兜底,又不会频繁打主库。
3. 主从延迟监控:心跳检测 + 自动降级
场景:监控主从延迟,超过阈值自动把读流量切回主库。
@Component
public class ReplicationLagMonitor {
@Autowired
private JdbcTemplate masterJdbc;
@Autowired
private JdbcTemplate slaveJdbc;
@Autowired
private DynamicDataSource dataSource;
private volatile long lagSeconds = 0;
// 每秒检测一次
@Scheduled(fixedDelay = 1000)
public void checkLag() {
// 主库写入时间戳
masterJdbc.update("INSERT INTO heartbeat (id, ts) VALUES (1, NOW()) ON DUPLICATE KEY UPDATE ts = NOW()");
String masterTime = masterJdbc.queryForObject("SELECT ts FROM heartbeat WHERE id=1", String.class);
// 从库读取时间戳
String slaveTime = slaveJdbc.queryForObject("SELECT ts FROM heartbeat WHERE id=1", String.class);
// 计算延迟秒数
lagSeconds = ChronoUnit.SECONDS.between(
LocalDateTime.parse(slaveTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
LocalDateTime.parse(masterTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
);
// 🎯 亮点:超过阈值自动降级,读流量全部切主库
if (lagSeconds > 3) {
dataSource.forceMasterRoute(); // 切换数据源,强制走主库
log.warn("主从延迟 {} 秒,触发自动降级", lagSeconds);
} else {
dataSource.restoreNormalRoute(); // 恢复正常读写分离
}
}
}✨ 技术亮点:
- 用独立的心跳表精确量化延迟,不依赖数据库自带的状态变量;
- 自动降级,无需人工干预,延迟恢复后自动切回,保障高可用。
技术难点 & 解决方案拆解 ⚡
难点 1:如何界定“关键业务”与“可降级业务”?
| 难点分析 | 解决方案 |
|---|---|
| 所有写后读都走主库,主库压力暴增 | 用前面给的决策树,只对支付、库存、下单回调等强一致性场景强制走主库;普通浏览类业务容忍短暂延迟 |
难点 2:半同步复制带来的性能损耗与主库阻塞风险
| 难点分析 | 解决方案 |
|---|---|
| 半同步模式下,主库需要等待至少一个从库应答,如果从库挂掉或网络抖动,写操作会阻塞 | ① 使用 AFTER_SYNC 模式(先提交引擎层,再等从库ACK),降低阻塞窗口;② 超时机制:设置 rpl_semi_sync_master_timeout,超时自动降级为异步复制,避免长时间阻塞 |
难点 3:缓存补偿的一致性边界
| 难点分析 | 解决方案 |
|---|---|
| 写 Redis 和写 DB 不在一个事务,可能 DB 成功、Redis 失败,导致缓存丢失 | ① Redis 写失败记录日志 + 重试;② 兜底的二级查询链(从库 → 主库)会补上数据;③ TTL 设短(3~5秒),即使缓存缺失影响范围极小 |
难点 4:从库延迟过大时,自动降级可能引发主库过载
| 难点分析 | 解决方案 |
|---|---|
| 一旦降级,所有读流量瞬间涌向主库 | ① 逐步放量:不一次性把全部读切到主库,先切换 50% 流量,观察负载;② 结合限流组件(Sentinel/Guava RateLimiter)对主库的读操作做保护;③ 故障止损优先,恢复后第一时间切回 |
难点 5:并行复制可能带来的数据冲突
| 难点分析 | 解决方案 |
|---|---|
| 开启 MTS 后,从库多线程回放,如果多个事务修改同一行,可能出现短暂不一致 | ① 调整 binlog_transaction_dependency_tracking 为 WRITESET,让 MySQL 自动分析事务依赖,安全并行;② 尽量让业务写入做到单行、按主键顺序,减少冲突概率 |
面试官 😊
“代码写得够细致,难点也拆解得透彻,尤其是监控自动降级和缓存多级兜底这两块,能看出实战经验。这轮代码和难点部分也顺利通过,继续保持这个状态。”
