MySQL读写分离写完读不到问题如何解决

发布时间:2021-07-16 09:54:43 作者:chen
来源:亿速云 阅读:320
# MySQL读写分离写完读不到问题如何解决

## 引言

在现代互联网应用中,数据库往往是性能瓶颈所在。为了提升数据库的并发处理能力,读写分离架构被广泛采用:主库(Master)负责写操作,从库(Slave)负责读操作。这种架构虽然能显著提升系统性能,但会引入一个典型问题——**写完读不到**,即主库写入后,从库无法立即读取到最新数据。

本文将深入分析MySQL读写分离架构下"写完读不到"问题的产生原因,并给出多种解决方案,帮助开发者根据业务场景选择最合适的处理方式。

## 一、问题现象与原因分析

### 1.1 典型场景示例

```sql
-- 客户端向主库执行写入
INSERT INTO orders (user_id, amount) VALUES (1001, 500); 

-- 立即从从库查询(可能查不到刚插入的记录)
SELECT * FROM orders WHERE user_id = 1001;

1.2 根本原因

出现该问题的核心原因是主从复制延迟(Replication Lag),具体包括:

  1. 网络传输延迟:主库的binlog需要传输到从库
  2. 从库重放延迟:从库需要顺序执行主库的binlog事件
  3. 从库负载过高:从库处理大量读请求导致SQL线程处理变慢
  4. 大事务延迟:主库执行大事务会导致从库延迟明显

1.3 影响评估

根据业务场景不同,数据不一致的容忍度也不同:

业务类型 容忍度 示例
金融交易 零容忍 支付后查余额
社交内容 可容忍 发帖后显示
日志统计 高容忍 操作日志记录

二、解决方案全景图

解决方案可分为三大类:

  1. 强制读主库:牺牲部分读性能保证一致性
  2. 等待复制:通过机制确保数据同步
  3. 中间件辅助:借助外部组件管理路由

三、强制读主方案

3.1 指定数据源路由

// Spring配置多数据源
@Bean
@Primary
public DataSource masterDataSource() {
    // 主库配置
}

@Bean
public DataSource slaveDataSource() {
    // 从库配置
}

// 使用注解切换数据源
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReadMaster {
}

// AOP切面处理
@Around("@annotation(readMaster)")
public Object around(ProceedingJoinPoint joinPoint, ReadMaster readMaster) {
    DynamicDataSource.setDataSource("master");
    try {
        return joinPoint.proceed();
    } finally {
        DynamicDataSource.clear();
    }
}

3.2 按表/操作类型路由

可制定路由规则: - 订单表相关查询强制走主库 - UPDATE/DELETE操作后的SELECT走主库

3.3 优缺点分析

优点: - 实现简单 - 强一致性保证

缺点: - 主库压力增大 - 失去读写分离优势

四、等待复制方案

4.1 基于GTID等待

-- 主库执行写入后获取GTID
SELECT @@GLOBAL.GTID_EXECUTED;

-- 从库等待直到应用该GTID
SELECT WT_FOR_EXECUTED_GTID_SET('aaa-bbb-ccc:10', 2); -- 超时2秒

4.2 基于位点等待

-- 主库查询binlog位置
SHOW MASTER STATUS;

-- 从库等待
SELECT MASTER_POS_WT('mysql-bin.000002', 154, 10);

4.3 程序实现示例

def write_then_read(query):
    # 执行写操作
    execute_master("INSERT...")
    
    # 获取主库最后GTID
    gtid = query_master("SELECT @@GLOBAL.GTID_EXECUTED")
    
    # 从库等待
    wait_slave(f"SELECT WT_FOR_EXECUTED_GTID_SET('{gtid}', 1)")
    
    # 执行查询
    return query_slave(query)

4.4 适用场景

五、中间件解决方案

5.1 ShardingSphere实现

# 配置主从规则
spring:
  shardingsphere:
    masterslave:
      name: ms_ds
      master-data-source-name: master
      slave-data-source-names: slave1,slave2
      load-balance-algorithm-type: round_robin
    props:
      max.connections.size.per.query: 5
      sql.show: true
      # 开启读主库配置
      master-slave.read.write-splitting.allow-read-write-splitting-when-replication-delay: false
      master-slave.read.write-splitting.replication-delay-milliseconds-threshold: 1000

5.2 ProxySQL路由配置

-- 定义查询规则
INSERT INTO mysql_query_rules (rule_id,active,match_pattern,destination_hostgroup,apply) 
VALUES (1,1,'^SELECT.*FOR UPDATE',10,1); -- 主库

INSERT INTO mysql_query_rules (rule_id,active,match_pattern,destination_hostgroup,apply) 
VALUES (2,1,'^SELECT',20,1); -- 从库

-- 特殊规则:写后读主
INSERT INTO mysql_query_rules (rule_id,active,flagIN,flagOUT,apply) 
VALUES (3,1,0,100,0);

INSERT INTO mysql_query_rules (rule_id,active,flagIN,match_pattern,destination_hostgroup,apply)
VALUES (4,1,100,'^SELECT',10,1);

六、高级解决方案

6.1 半同步复制

# 主库my.cnf配置
[mysqld]
plugin-load = "rpl_semi_sync_master=semisync_master.so"
rpl_semi_sync_master_enabled = 1
rpl_semi_sync_master_timeout = 10000 # 10秒超时

# 从库配置
plugin-load = "rpl_semi_sync_slave=semisync_slave.so"
rpl_semi_sync_slave_enabled = 1

6.2 组复制(MGR)

-- 初始化组复制
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;

-- 查看节点状态
SELECT * FROM performance_schema.replication_group_members;

6.3 数据库代理中间件对比

方案 一致性保证 性能影响 复杂度
原生MySQL路由
ProxySQL 中等
MGR
业务层控制 可定制 可变

七、业务层解决方案

7.1 缓存标记法

public Object readAfterWrite(String key, Supplier<Object> loader) {
    // 先查缓存
    Object value = cache.get(key);
    if (value != null) {
        return value; 
    }
    
    // 查数据库(根据标记决定查主/从)
    if (writeFlag.get(key)) {
        value = masterDb.query(loader);
        writeFlag.remove(key);
    } else {
        value = slaveDb.query(loader);
    }
    
    // 回填缓存
    cache.set(key, value);
    return value;
}

7.2 版本号校验

-- 数据表增加版本字段
ALTER TABLE orders ADD COLUMN data_version INT DEFAULT 0;

-- 写入时更新版本
UPDATE orders SET amount=100, data_version=data_version+1 WHERE id=1;

-- 读取时校验版本
SELECT * FROM orders WHERE id=1 AND data_version >= ?;

八、监控与优化

8.1 关键监控指标

-- 查看从库延迟
SHOW SLAVE STATUS\G
-- 关注:
-- Seconds_Behind_Master
-- Slave_SQL_Running_State

-- 性能监控
SELECT * FROM sys.schema_table_lock_waits;
SELECT * FROM performance_schema.events_statements_summary_by_digest;

8.2 主从优化建议

  1. 从库配置优化

    [mysqld]
    slave_parallel_workers = 8
    slave_parallel_type = LOGICAL_CLOCK
    
  2. 网络优化:主从同机房部署

  3. 大事务拆分:将大事务拆分为小批次

九、方案选型指南

9.1 决策树

是否需要强一致?
├── 是 → 业务能否接受延迟?
│   ├── 能 → 等待复制方案
│   └── 不能 → 强制读主
└── 否 → 采用最终一致+业务补偿

9.2 各方案适用场景

方案 适用场景 一致性级别 性能影响
强制读主 金融核心业务 强一致
等待复制 可容忍短延迟 最终一致
中间件路由 常规业务 可配置
MGR 高可用要求 强一致 较高

十、总结

MySQL读写分离架构下的”写完读不到”问题需要根据业务特点选择合适的解决方案:

  1. 强一致性要求:采用强制读主或半同步复制
  2. 可接受短延迟:使用GTID等待机制
  3. 高可用场景:考虑组复制(MGR)
  4. 业务可降级:采用缓存标记+超时降级策略

实际生产中,建议通过完善的监控掌握主从延迟情况,结合业务特点进行技术选型。随着MySQL 8.0的普及,诸如MGR等新特性为读写分离场景提供了更多可能性,值得持续关注。

作者注:本文讨论的方案需要根据实际MySQL版本和业务场景进行调整,生产环境实施前务必充分测试。 “`

推荐阅读:
  1. 如何解决MySQL的幻读问题
  2. MySQL可重复读级别可以解决幻读问题吗

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

mysql

上一篇:Linux下文件标述符和文件指针的示例分析

下一篇:Web开发中客户端跳转与服务器端跳转有什么区别

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》