MySQL读写分离和分库分表分别从不同角度提高系统的处理效率,读写分离侧重于处理高并发的场景,分库分表是从数据量的角度去提升单次读写的效率。
1、MySQL读写分离
当面临大量的读操作,造成写操作的效率降低时,将数据库的读和写操作分开,分别由不同的数据库服务器处理,可以有效地分担数据库的压力,优化系统性能。
读写分离是数据库架构中的一种常见策略,主要用于解决以下几种情况:
-
当数据库的并发读写请求非常高时,单一的数据库服务器可能无法承受这样的压力,这时就需要通过读写分离来分散压力。读请求可以分发到多个从库,写请求则由主库处理。
-
资源优化,读写分离可以使得主库专注于处理写操作和事务性的操作,从库则处理读操作。这样可以根据主从库的不同特性进行硬件和系统资源的优化配置。
-
数据备份和故障转移,这是一个间接作用。通过读写分离,可以实现数据的实时备份。当主库出现故障时,可以迅速切换到从库,保证服务的持续可用。
-
提高查询性能,读写分离可以将复杂的查询操作分散到多个从库上执行,从而提高查询性能。

(1)实现模式
- 主从模式(Master-Slave):一个主库处理所有写操作,并将数据变更同步到一个或多个从库,从库处理读请求。
优点:简单易实现,适用于大多数场景。
缺点:主库可能成为瓶颈,从库延迟可能影响数据一致性。
- 主主模式(Master-Master):多个主库同时处理读写请求,每个主库都可以接收写操作,并将变更同步到其他主库。
优点:高可用性和高写入吞吐量。
缺点:数据冲突和一致性问题更加复杂,配置和管理难度大。
- 多级复制(Multi-Tier Replication):在主从架构基础上,增加中间层从库,形成多级复制结构。例如,主库 -> 中间从库 -> 最终从库。
优点:分担主库和中间从库的压力,提高读取性能。
缺点:配置和维护复杂,延迟可能累积。
(2)实现方式
-
应用层实现(程序代码实现):在应用程序代码中手动管理数据库连接,将写请求发送到主库,读请求发送到从库。
-
ProxySQL中间件:高性能的 MySQL 代理,支持动态负载均衡、读写分离、查询缓存等功能。
-
ShardingSphere:阿里巴巴开源的分布式数据库中间件,支持分库分表、读写分离、分布式事务等。
2、ProxySQL
ProxySQL是基于MySQL的一款开源的中间件的产品,是一个灵活的MySQL代理层,可以实现读写分离,支持 Query路由功能,支持动态指定某个SQL进行缓存,
支持动态加载配置信息(无需重启 ProxySQL 服务),支持故障切换和SQL的过滤功能。

ProxySQL中管理结构
在ProxySQL,6032端口共五个库: main、disk、stats 、monitor、stats_history
- main:
main 库中有如下信息:
mysql_servers: 后端可以连接 MySQL 服务器的列表
mysql_users: 配置后端数据库的账号和监控的账号。
mysql_query_rules: 指定 Query 路由到后端不同服务器的规则列表。
mysql_replication_hostgroups : 节点分组配置信息
注: 表名以 runtime_开头的表示ProxySQL 当前运行的配置内容,不能直接修改。不带runtime_是下文图中Mem相关的配置。
-
disk : 持久化的磁盘的配置
-
stats: 统计信息的汇总
-
monitor:监控的收集信息,比如数据库的健康状态等
-
stats_history:ProxySQL 收集的有关其内部功能的历史指标
admin管理接口,默认端口为6032 。该端口用于查看、配置ProxySQL。通过6032管理端口登入后,默认就是main库,所有的配置更改都必须在这个库中进行,disk存档库不会直接受到影响。
接收SQL语句的接口,默认端口为6033,这个接口类似于MySQL的3306端口。

ProxySQL的多层配置
整套配置系统分为三层:
-
RUNTIME:代表 ProxySQL 当前正在使用的配置,无法直接修改此配置,必须要从下一层 (MEM层)"load" 进来。
-
MEMORY:MEMORY 层上面连接 RUNTIME 层,下面disk持久层。这层可以在线操作 ProxySQL配置,随便修改,不会影响生产环境。确认正常之后在加载达到RUNTIME 和持久化的磁盘上。修改方法: insert、update、delete、select。
-
DISK和CONFIG FILE:持久化配置信息。重启时,可以从磁盘快速加载回来
使用proxysql
主要需要完成以下几项内容的配置:
1、配置监控账号。监控账号用于检测后端mysql实例是否健康(是否能连接、复制是否正常、复制是否有延迟等)。
2、到后端mysql实例创建监控账号。
3、配置后端mysql实例连接信息。实例连接信息存储在mysql_servers表。
4、配置连接proxysql和后端实例的账号。账号信息存储在mysql_users表。
5、配置查询路由信息。路由信息存储在mysql_query_rules表。
6、配置后端mysql集群信息。根据后端mysql集群架构,配置分别存储在mysql_replication_hostgroups、mysql_group_replication_hostgroups、runtime_mysql_galera_hostgroups、runtime_mysql_aws_aurora_hostgroups等表中。
7、根据具体需要,调优相关参数。参数存储在global_variables表。
(1)从库设定read_only参数
ProxySQL 会根据MySQL server 的read_only 的取值将服务器进行分组。 read_only=0 的server,会被分到写组,read_only=1 的server,则被分到读组。所以需要将从库设置: read_only=1;
sql
vim /etc/my.cnf
[mysqld]
read_only=1
(2)配置读写组编号
在ProxySQL 上 mysql_replication_hostgroup表中(自带,无需创建)

sql
mysql_replication_hostgroups (
writer_hostgroup INT CHECK (writer_hostgroup>=0) NOT NULL PRIMARY KEY,
reader_hostgroup INT NOT NULL CHECK (reader_hostgroup<>writer_hostgroup AND reader_hostgroup>=0),
check_type VARCHAR CHECK (LOWER(check_type) IN ('read_only','innodb_read_only','super_read_only','read_only|innodb_read_only','read_only&innodb_read_only')) NOT NULL DEFAULT 'read_only',
comment VARCHAR NOT NULL DEFAULT '', UNIQUE (reader_hostgroup)
)
创建组:(定义写为1,读为0),数字可以自定义
sql
[root@db03 ~]# mysql -uadmin -padmin -h127.0.0.1 -P6032
MySQL> insert into mysql_replication_hostgroups (writer_hostgroup,reader_hostgroup,comment) values (1,0,'proxy');
Query OK, 1 row affected (0.00 sec)
sql
#加载到当前生效
MySQL [(none)]> load mysql servers to runtime;
Query OK, 0 rows affected (0.01 sec)
#永久保存
MySQL [(none)]> save mysql servers to disk;
Query OK, 0 rows affected (0.02 sec)
ProxySQL会根据serverd的/etc/my.cnf配置文件中额的read_only的取值将服务器进行分组。read_only=0的server,master被分到编号为1的写组
(3)添加主机到ProxySQL
sql
insert into mysql_servers(hostgroup_id,hostname,port) values (1,'172.16.100.21',3306);
insert into mysql_servers(hostgroup_id,hostname,port) values (0,'172.16.100.22',3306);
insert into mysql_servers(hostgroup_id,hostname,port) values (0,'172.16.100.23',3306);

sql
//加载主从节点信息到runtime
load mysql servers to runtime;
save mysql servers to disk;
(4)配置 ProxySQL 所需账户
在 MySQL 的Master节点上创建 ProxySQL 的监控账户和对外访问账户
sql
#监控账户
mysql>create user 'monitor'@'172.16.%.%' identified with mysql_native_password by 'Monitor@123.com';
grant all privileges on *.* to 'monitor'@'172.16.%.%' with grant option;
#proxysql 的对外访问账户
mysql>create user 'proxysql'@'172.16.%.%' identified with mysql_native_password by '2025pro0702';
grant all privileges on *.* to 'proxysql'@'172.16.%.%' with grant option;
(5)为ProxySQL监控MySQL后端节点
sql
#切换到monitor库
MySQL [(none)]> use monitor
Database changed
MySQL [monitor]> set mysql-monitor_username='monitor';
Query OK, 1 row affected (0.00 sec)
MySQL [monitor]> set mysql-monitor_password='Monitor@123.com';
Query OK, 1 row affected (0.00 sec)
上面这两句是修改变量的方式还可以在main库下面用sql语句方式修改
在main下修改:
sql
MySQL [monitor]> use main
Database changed
MySQL [main]> UPDATE global_variables SET variable_value='monitor' WHERE variable_name='mysql-monitor_username';
Query OK, 1 row affected (0.00 sec)
MySQL [main]> UPDATE global_variables SET variable_value='Monitor@123.com' WHERE variable_name='mysql-monitor_password';
Query OK, 1 row affected (0.00 sec)
修改后,保存到runtime和disk
sql
MySQL [monitor]> load mysql variables to runtime;
MySQL [monitor]> save mysql variables to disk;
(6)ProxySQL配置对外访问账号
ProxySQL 的mysql_users表结构如下:
sql
mysql_users (
username VARCHAR NOT NULL,
password VARCHAR,
active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,
use_ssl INT CHECK (use_ssl IN (0,1)) NOT NULL DEFAULT 0,
default_hostgroup INT NOT NULL DEFAULT 0,
default_schema VARCHAR,
schema_locked INT CHECK (schema_locked IN (0,1)) NOT NULL DEFAULT 0,
transaction_persistent INT CHECK (transaction_persistent IN (0,1)) NOT NULL DEFAULT 1,
fast_forward INT CHECK (fast_forward IN (0,1)) NOT NULL DEFAULT 0,
backend INT CHECK (backend IN (0,1)) NOT NULL DEFAULT 1,
frontend INT CHECK (frontend IN (0,1)) NOT NULL DEFAULT 1,
max_connections INT CHECK (max_connections >=0) NOT NULL DEFAULT 10000,
attributes VARCHAR CHECK (JSON_VALID(attributes) OR attributes = '') NOT NULL DEFAULT '',
comment VARCHAR NOT NULL DEFAULT '',
PRIMARY KEY (username, backend),
UNIQUE (username, frontend)
)

sql
insert into mysql_users (username,password,default_hostgroup,transaction_persistent) values ('proxysql','2025pro0702',1,1);
load mysql users to runtime;
save mysql users to disk;
transaction_persistent 如果为1,则一个完整的SQL只可能路由到一个节点;这点非常重要,主要解决这种情况:一个事务有混合的读操作和写操作组成,事务未提交前,如果事务中的读操作和写操作路由到不同节点,那么读取到的结果必然是脏数据。所以一般情况下,该值应该设置为1,尤其是业务中使用到事务机制的情况(默认为0)
mysql_users 表有不少字段,最主要的三个字段username,password,default_hostgroup 。
(7)添加读写分离规则(mysql_query_rules)

基于用户的路由:
sql
Insert into mysql_users(username,password,default_hostgroup)
values('writer','123',10),('reader','123',20);
load mysql users to runtime;
save mysql users to disk;
insert into mysql_query_rules(rule_id,active,username,destination_hostgroup,apply)
values(1,1,'writer',10,1),(2,1,'reader',20,1);
load mysql query rules to runtime;
save mysql query rules to disk;
基于端口的路由:
sql
## 修改ProxySQL监听SQL流量的端口号,监听多端口上。
set mysql-interfaces='0.0.0.0:6033;0.0.0.0:6034';
save mysql variables to disk;
## 重启生效
systemctl restart proxysql
## 设定路由规则
insert into mysql_query_rules(rule_id,active,proxy_port,destination_hostgroup,apply)
values(1,1,6033,10,1), (2,1,6034,20,1);
load mysql query rules to runtime;
save mysql query rules to disk;
还可以基于监听地址(修改字段proxy_addr即可),也可以基于客户端地址(修改字段client_addr字段即可)。
基于正则表达式的路由:
sql
insert into mysql_query_rules(rule_id,active,match_pattern,destination_hostgroup,apply)
values(1,1,'^select .* for update$',1,1);
insert into mysql_query_rules(rule_id,active,match_pattern,destination_hostgroup,apply)
values(2,1,'^select',0,1);
- 表示像select * from xxx for update这种语句都会分到到写组,2)表示像select这种语句都会被分配到读组。