溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

MySQL讀寫分離寫完讀不到問題如何解決

發布時間:2021-07-16 09:54:43 來源:億速云 閱讀:323 作者:chen 欄目:數據庫
# 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 適用場景

  • 需要強一致且讀少寫的場景
  • 可接受額外等待時間(通常<1s)

五、中間件解決方案

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版本和業務場景進行調整,生產環境實施前務必充分測試。 “`

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女