6.6.7 读写分离与一致性策略

读写分离旨在通过主库承担写入、从库承担查询来扩展吞吐与隔离负载,但必须处理复制延迟、读一致性与路由策略之间的平衡。常见架构包括应用层路由、代理层路由(如 ProxySQL)与中间件路由,选择时需结合业务读写比、事务一致性要求与故障切换机制。

原理与架构草图

文章图片

一致性级别与业务匹配
- 强一致:读必须返回最新写入,适用于支付、库存、订单状态。
- 会话一致(Read Your Writes):同一会话内读可见写,适合用户中心。
- 最终一致:允许短暂不一致,适合报表、统计。

常见一致性策略
- 主库强制读:关键读请求直接路由到主库。
- 延迟阈值读:基于从库延迟阈值选择是否读从。
- GTID/位点等待:写后记录 GTID,读前等待从库执行。
- 会话粘滞:写后一定时间窗口内读固定主库。
- 读写事务拆分:只读事务走从库,强一致事务走主库。


示例一:主从复制基础检查与延迟治理#

1)检查主库 binlog 与从库复制状态

# 主库:确认开启 binlog
mysql -uroot -p -e "SHOW VARIABLES LIKE 'log_bin'; SHOW MASTER STATUS\G"

# 从库:查看复制状态与延迟
mysql -uroot -p -e "SHOW SLAVE STATUS\G"

关键字段解释
- Seconds_Behind_Master:复制延迟秒数(NULL 表示异常或未运行)。
- Slave_IO_Running/Slave_SQL_Running:IO/SQL 线程运行状态。
- Relay_Log_Space:中继日志堆积大小,越大可能延迟越高。

2)降低延迟的配置示例(从库 my.cnf)

# /etc/my.cnf.d/replica.cnf
[mysqld]
server_id=2
log_bin=mysql-bin
relay_log=relay-bin
read_only=ON
super_read_only=ON

# 开启并行复制
slave_parallel_type=LOGICAL_CLOCK
slave_parallel_workers=4

3)重载并验证

systemctl restart mysqld
mysql -uroot -p -e "SHOW VARIABLES LIKE 'slave_parallel_%';"

示例二:GTID 等待实现会话一致性#

1)主库写入并获取 GTID

-- 连接主库执行
CREATE DATABASE IF NOT EXISTS demo;
USE demo;
CREATE TABLE IF NOT EXISTS t1(id INT PRIMARY KEY, val VARCHAR(50));
INSERT INTO t1 VALUES(1,'v1');

-- 获取当前会话的 GTID
SELECT @@GLOBAL.GTID_EXECUTED\G

2)从库等待 GTID 执行完成后读取

-- 连接从库执行,等待 5 秒
SELECT WAIT_FOR_EXECUTED_GTID_SET('gtid-set-string', 5);

-- 等待成功后读取
SELECT * FROM demo.t1 WHERE id=1;

说明
- WAIT_FOR_EXECUTED_GTID_SET 返回 0 表示成功,1 表示超时。
- gtid-set-string 替换为主库获取的 GTID 集合。


示例三:ProxySQL 读写分离最小可用配置#

1)安装与启动

# CentOS/RHEL
yum install -y proxysql
systemctl enable proxysql
systemctl start proxysql

2)添加主从节点与用户

-- 连接 ProxySQL 管理端口
mysql -uadmin -padmin -h127.0.0.1 -P6032

-- 添加主库(hostgroup 10)与从库(hostgroup 20)
INSERT INTO mysql_servers(hostgroup_id, hostname, port, weight) VALUES
(10, '10.0.0.10', 3306, 100),
(20, '10.0.0.11', 3306, 100),
(20, '10.0.0.12', 3306, 100);

-- 添加业务用户
INSERT INTO mysql_users(username, password, default_hostgroup) VALUES
('appuser','app_pass',10);

-- 读写规则:SELECT 去从库,其它去主库
INSERT INTO mysql_query_rules(rule_id, active, match_pattern, destination_hostgroup, apply) VALUES
(1,1,'^SELECT',20,1),
(2,1,'.*',10,1);

-- 加载到运行时并保存
LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;
LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;
LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;

3)应用连接 ProxySQL

# 应用连接 6033 数据端口
mysql -uappuser -papp_pass -h127.0.0.1 -P6033 -e "SELECT @@hostname;"

预期效果
- SELECT 返回从库 hostname。
- INSERT/UPDATE/DELETE 走主库。


路由规则设计要点(示例)#

事务内读写走主库

-- 应用侧:开启事务强制主库
START TRANSACTION;
UPDATE demo.t1 SET val='v2' WHERE id=1;
SELECT * FROM demo.t1 WHERE id=1; -- 同一事务读主库
COMMIT;

写后关键读强制主库(伪代码)

# 连接池层:写操作后设置会话标记
if write_success:
  session.force_master_ttl = 5s

排错清单(常见问题与命令)#

1)从库延迟过高

# 查看复制线程是否异常
mysql -uroot -p -e "SHOW SLAVE STATUS\G"

# 查看慢查询与大事务
mysql -uroot -p -e "SHOW PROCESSLIST;"
mysql -uroot -p -e "SHOW ENGINE INNODB STATUS\G"

2)ProxySQL 路由不生效

-- 确认规则已生效
SELECT * FROM runtime_mysql_query_rules\G;
SELECT * FROM stats_mysql_query_rules\G;

3)读到旧数据
- 检查 Seconds_Behind_Master 是否超阈值。
- 临时把关键读路由到主库。
- 启用 GTID 等待或会话粘滞。


练习#

  1. 搭建 1 主 2 从的复制,并通过 SHOW SLAVE STATUS\G 验证。
  2. 使用 ProxySQL 配置读写分离,验证 SELECTINSERT 分别路由到从/主库。
  3. 模拟延迟(从库执行 SLEEP),观察 Seconds_Behind_Master 变化并切换到主库读。
  4. 使用 GTID 等待实现写后读一致性,记录等待前后查询结果。

最佳实践小结#

  • 明确业务一致性等级并分级路由,避免一刀切。
  • 关键写后读强制主库,普通读走从库并监控延迟阈值。
  • 使用 GTID 与复制延迟指标构建可观测的一致性保障体系。
  • 切换后短时间内启用主库读保护窗口,防止读到旧数据。