# MySQL讀寫分離寫完讀不到問題如何解決
## 引言
在現代互聯網應用中,數據庫往往是性能瓶頸所在。為了提升數據庫的并發處理能力,讀寫分離架構被廣泛采用:主庫(Master)負責寫操作,從庫(Slave)負責讀操作。這種架構雖然能顯著提升系統性能,但會引入一個典型問題——**寫完讀不到**,即主庫寫入后,從庫無法立即讀取到最新數據。
本文將深入分析MySQL讀寫分離架構下"寫完讀不到"問題的產生原因,并給出多種解決方案,幫助開發者根據業務場景選擇最合適的處理方式。
## 一、問題現象與原因分析
### 1.1 典型場景示例
```sql
-- 客戶端向主庫執行寫入
INSERT INTO orders (user_id, amount) VALUES (1001, 500);
-- 立即從從庫查詢(可能查不到剛插入的記錄)
SELECT * FROM orders WHERE user_id = 1001;
出現該問題的核心原因是主從復制延遲(Replication Lag),具體包括:
根據業務場景不同,數據不一致的容忍度也不同:
業務類型 | 容忍度 | 示例 |
---|---|---|
金融交易 | 零容忍 | 支付后查余額 |
社交內容 | 可容忍 | 發帖后顯示 |
日志統計 | 高容忍 | 操作日志記錄 |
解決方案可分為三大類:
// 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();
}
}
可制定路由規則: - 訂單表相關查詢強制走主庫 - UPDATE/DELETE操作后的SELECT走主庫
優點: - 實現簡單 - 強一致性保證
缺點: - 主庫壓力增大 - 失去讀寫分離優勢
-- 主庫執行寫入后獲取GTID
SELECT @@GLOBAL.GTID_EXECUTED;
-- 從庫等待直到應用該GTID
SELECT WT_FOR_EXECUTED_GTID_SET('aaa-bbb-ccc:10', 2); -- 超時2秒
-- 主庫查詢binlog位置
SHOW MASTER STATUS;
-- 從庫等待
SELECT MASTER_POS_WT('mysql-bin.000002', 154, 10);
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)
# 配置主從規則
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
-- 定義查詢規則
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);
# 主庫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
-- 初始化組復制
SET GLOBAL group_replication_bootstrap_group=ON;
START GROUP_REPLICATION;
SET GLOBAL group_replication_bootstrap_group=OFF;
-- 查看節點狀態
SELECT * FROM performance_schema.replication_group_members;
方案 | 一致性保證 | 性能影響 | 復雜度 |
---|---|---|---|
原生MySQL路由 | 弱 | 低 | 低 |
ProxySQL | 中等 | 中 | 中 |
MGR | 強 | 高 | 高 |
業務層控制 | 可定制 | 可變 | 高 |
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;
}
-- 數據表增加版本字段
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 >= ?;
-- 查看從庫延遲
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;
從庫配置優化:
[mysqld]
slave_parallel_workers = 8
slave_parallel_type = LOGICAL_CLOCK
網絡優化:主從同機房部署
大事務拆分:將大事務拆分為小批次
是否需要強一致?
├── 是 → 業務能否接受延遲?
│ ├── 能 → 等待復制方案
│ └── 不能 → 強制讀主
└── 否 → 采用最終一致+業務補償
方案 | 適用場景 | 一致性級別 | 性能影響 |
---|---|---|---|
強制讀主 | 金融核心業務 | 強一致 | 高 |
等待復制 | 可容忍短延遲 | 最終一致 | 中 |
中間件路由 | 常規業務 | 可配置 | 低 |
MGR | 高可用要求 | 強一致 | 較高 |
MySQL讀寫分離架構下的”寫完讀不到”問題需要根據業務特點選擇合適的解決方案:
實際生產中,建議通過完善的監控掌握主從延遲情況,結合業務特點進行技術選型。隨著MySQL 8.0的普及,諸如MGR等新特性為讀寫分離場景提供了更多可能性,值得持續關注。
作者注:本文討論的方案需要根據實際MySQL版本和業務場景進行調整,生產環境實施前務必充分測試。 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。