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 主 2 从的复制,并通过
SHOW SLAVE STATUS\G验证。 - 使用 ProxySQL 配置读写分离,验证
SELECT与INSERT分别路由到从/主库。 - 模拟延迟(从库执行
SLEEP),观察Seconds_Behind_Master变化并切换到主库读。 - 使用 GTID 等待实现写后读一致性,记录等待前后查询结果。
最佳实践小结#
- 明确业务一致性等级并分级路由,避免一刀切。
- 关键写后读强制主库,普通读走从库并监控延迟阈值。
- 使用 GTID 与复制延迟指标构建可观测的一致性保障体系。
- 切换后短时间内启用主库读保护窗口,防止读到旧数据。