您的位置:首页 > 路由器知识路由器知识

2025超实用ProxySQL路由规则全解析:从入门到故障自愈的实战指南

2026-02-06人已围观

2025超实用ProxySQL路由规则全解析:从入门到故障自愈的实战指南

一、ProxySQL路由就像快递分拣系统

想象你是一家大型物流公司的分拣员,每天要处理成千上万的包裹。有的包裹需要加急送达(就像数据库的写操作),有的可以走普通物流(读操作),还有的必须送到特定城市(分库分表)。ProxySQL就扮演着这样的智能分拣中心角色,它能根据包裹上的各种标签(SQL语句特征、用户名、端口等),把数据库请求精准地送到不同的后端服务器。

当ProxySQL收到前端应用发送的SQL语句后,它会先看看这个"包裹"的各种特征,然后根据我们提前设定的"分拣规则",把它转发到最合适的后端MySQL服务器。处理完成后,再把结果打包返回给客户端。这个过程中,ProxySQL还能顺便做些"增值服务",比如缓存常用查询结果、记录物流信息(监控统计)等。

二、手把手教你搭建ProxySQL环境

2.1 准备工作

在开始前,我们需要准备几台服务器。假设我们有三台服务器,计划将一台作为主库(写库),两台作为从库(读库):

- 主库:192.168.1.10:3306

- 从库1:192.168.1.11:3306

- 从库2:192.168.1.12:3306

- ProxySQL服务器:192.168.1.20:6033(服务端口)/6032(管理端口)

2.2 安装ProxySQL

以CentOS系统为例,我们使用RPM包安装最新稳定版ProxySQL:

```bash

下载ProxySQL RPM包

wget https://github.com/sysown/proxysql/releases/download/v2.4.8/proxysql-2.4.8-1-centos7.x86_64.rpm

安装

sudo rpm -ivh proxysql-2.4.8-1-centos7.x86_64.rpm --nodeps

启动ProxySQL服务

sudo systemctl start proxysql

设置开机自启

sudo systemctl enable proxysql

检查状态

sudo systemctl status proxysql

```

成功启动后,ProxySQL会监听两个端口:6032(管理端口)和6033(数据库服务端口)。

2.3 初始化配置

使用默认管理员账号登录ProxySQL的管理界面:

```bash

mysql -uadmin -padmin -h127.0.0.1 -P6032 -A

```

首次登录后,建议立即修改默认密码:

```sql

UPDATE global_variables SET variable_value='new_password' WHERE variable_name='admin-admin_credentials';

LOAD ADMIN VARIABLES TO RUNTIME;

SAVE ADMIN VARIABLES TO DISK;

```

三、认识ProxySQL的"大脑":mysql_query_rules表

ProxySQL的核心就是路由规则,这些规则都保存在`mysql_query_rules`表中。这个表就像我们给分拣员的"操作手册",记录了各种包裹应该如何处理。让我们先了解一下这个表中最重要的几个字段:

- rule_id:规则ID,规则按ID从小到大顺序执行

- active:是否启用这条规则(1=启用,0=禁用)

- username:匹配特定数据库用户名

- schemaname:匹配特定数据库名

- match_pattern:用正则表达式匹配SQL语句文本

- match_digest:用正则表达式匹配参数化后的SQL语句

- digest:精确匹配SQL语句的hash值

- destination_hostgroup:匹配后要路由到的目标主机组

- apply:是否立即应用此规则,不再检查后续规则(1=是,0=否)

- cache_ttl:查询结果缓存时间(毫秒)

- timeout:查询超时时间(毫秒)

理解这些字段是配置路由规则的基础。就像快递分拣手册中的各项条款,每个字段都有其特定用途。

四、5种常用路由方式实战

4.1 基于端口的路由:最简单的读写分离

假设我们希望:

- 连接6033端口的请求都路由到写库(主机组10)

- 连接6034端口的请求都路由到读库(主机组20)

首先,修改ProxySQL配置,让它监听6034端口:

```sql

UPDATE global_variables SET variable_value='6033;6034' WHERE variable_name='mysql-interfaces';

LOAD MYSQL VARIABLES TO RUNTIME;

SAVE MYSQL VARIABLES TO DISK;

```

然后添加路由规则:

```sql

-- 规则1:6033端口的请求路由到写组10

INSERT INTO mysql_query_rules(rule_id, active, proxy_port, destination_hostgroup, apply)

VALUES (1, 1, 6033, 10, 1);

-- 规则2:6034端口的请求路由到读组20

INSERT INTO mysql_query_rules(rule_id, active, proxy_port, destination_hostgroup, apply)

VALUES (2, 1, 6034, 20, 1);

-- 使规则生效

LOAD MYSQL QUERY RULES TO RUNTIME;

SAVE MYSQL QUERY RULES TO DISK;

```

这种方式非常简单,适合小型应用,但缺点是客户端需要根据读写操作手动切换端口。

4.2 基于用户名的路由:按用户权限分离

假设我们创建了两个用户:

- `writer`用户:有写权限,应该路由到写库

- `reader`用户:只有读权限,应该路由到读库

首先在后端MySQL创建这两个用户:

```sql

-- 在主库执行

CREATE USER 'writer'@'%' IDENTIFIED BY 'WritePass123!';

GRANT ALL PRIVILEGES ON . TO 'writer'@'%';

CREATE USER 'reader'@'%' IDENTIFIED BY 'ReadPass123!';

GRANT SELECT ON . TO 'reader'@'%';

```

然后在ProxySQL中添加用户和路由规则:

```sql

-- 添加用户

INSERT INTO mysql_users(username, password, default_hostgroup, active)

VALUES ('writer', 'WritePass123!', 10, 1);

INSERT INTO mysql_users(username, password, default_hostgroup, active)

VALUES ('reader', 'ReadPass123!', 20, 1);

-- 添加路由规则

INSERT INTO mysql_query_rules(rule_id, active, username, destination_hostgroup, apply)

VALUES (3, 1, 'writer', 10, 1);

INSERT INTO mysql_query_rules(rule_id, active, username, destination_hostgroup, apply)

VALUES (4, 1, 'reader', 20, 1);

-- 使配置生效

LOAD MYSQL USERS TO RUNTIME;

SAVE MYSQL USERS TO DISK;

LOAD MYSQL QUERY RULES TO RUNTIME;

SAVE MYSQL QUERY RULES TO DISK;

```

这种方式让不同用户自动路由到不同的主机组,比端口路由更灵活。

4.3 基于数据库名的路由:实现简单分库

如果我们想把不同数据库的请求路由到不同的数据库集群,可以使用基于`schemaname`的路由。例如:

- `order_db`路由到订单数据库集群(主机组30)

- `user_db`路由到用户数据库集群(主机组40)

配置如下:

```sql

-- 规则5:order_db路由到主机组30

INSERT INTO mysql_query_rules(rule_id, active, schemaname, destination_hostgroup, apply)

VALUES (5, 1, 'order_db', 30, 1);

-- 规则6:user_db路由到主机组40

INSERT INTO mysql_query_rules(rule_id, active, schemaname, destination_hostgroup, apply)

VALUES (6, 1, 'user_db', 40, 1);

-- 使规则生效

LOAD MYSQL QUERY RULES TO RUNTIME;

SAVE MYSQL QUERY RULES TO DISK;

```

不过要注意,这种方式只能基于默认数据库名进行路由,如果SQL语句中显式指定了数据库名(如`SELECT FROM order_db.orders`),这种路由方式就会失效。这时候就需要使用基于SQL语句的路由了。

4.4 基于SQL语句的路由:最灵活的方式

这是ProxySQL最强大的功能,可以根据SQL语句的内容进行精细路由。比如实现读写分离:

```sql

-- 规则7:所有SELECT FOR UPDATE语句路由到写组10

INSERT INTO mysql_query_rules(rule_id, active, match_pattern, destination_hostgroup, apply)

VALUES (7, 1, '^SELECT.FOR UPDATE$', 10, 1);

-- 规则8:所有SELECT语句路由到读组20

INSERT INTO mysql_query_rules(rule_id, active, match_pattern, destination_hostgroup, apply)

VALUES (8, 1, '^SELECT', 20, 1);

-- 使规则生效

LOAD MYSQL QUERY RULES TO RUNTIME;

SAVE MYSQL QUERY RULES TO DISK;

```

这里使用了`match_pattern`字段来匹配SQL语句。正则表达式`^SELECT.FOR UPDATE$`会匹配所有带`FOR UPDATE`的SELECT语句(这些是需要写锁的查询,应该路由到主库),而`^SELECT`会匹配所有普通SELECT查询(路由到从库)。

4.5 基于参数化SQL的路由:更高性能的匹配

ProxySQL会对SQL语句进行参数化处理,把类似`SELECT FROM users WHERE id=1`和`SELECT FROM users WHERE id=2`这样的语句识别为同一种查询,参数化后变成`SELECT FROM users WHERE id=?`。这个参数化语句的hash值就是`digest`,我们可以基于这个值进行路由:

首先,查看ProxySQL统计的SQL语句和它们的digest:

```sql

SELECT digest, digest_text, count_star FROM stats_mysql_query_digest;

```

假设我们发现有一个频繁执行的查询:`SELECT FROM products WHERE category=?`,它的digest是`0x1234567890ABCDEF`,我们想把它路由到专门的缓存节点(主机组50):

```sql

-- 规则9:基于digest路由特定查询

INSERT INTO mysql_query_rules(rule_id, active, digest, destination_hostgroup, cache_ttl, apply)

VALUES (9, 1, '0x1234567890ABCDEF', 50, 3000, 1);

-- 使规则生效

LOAD MYSQL QUERY RULES TO RUNTIME;

SAVE MYSQL QUERY RULES TO DISK;

```

这种方式匹配效率最高,还可以通过`cache_ttl`字段设置查询结果缓存时间(这里是3000毫秒,即3秒)。

五、完整的读写分离配置示例

让我们综合前面的知识,配置一个完整的读写分离方案:

5.1 添加后端服务器

```sql

-- 添加写组(主库)

INSERT INTO mysql_servers(hostgroup_id, hostname, port, weight, max_replication_lag)

VALUES (10, '192.168.1.10', 3306, 1, 10);

-- 添加读组(从库)

INSERT INTO mysql_servers(hostgroup_id, hostname, port, weight, max_replication_lag)

VALUES (20, '192.168.1.11', 3306, 1, 10);

INSERT INTO mysql_servers(hostgroup_id, hostname, port, weight, max_replication_lag)

VALUES (20, '192.168.1.12', 3306, 2, 10); -- 权重2,会分配更多流量

-- 使配置生效

LOAD MYSQL SERVERS TO RUNTIME;

SAVE MYSQL SERVERS TO DISK;

```

注意这里给第二个从库设置了权重2,这意味着在负载均衡时,它会收到大约2/3的读请求,而第一个从库收到1/3。

5.2 配置监控用户

ProxySQL需要监控后端服务器的状态:

```sql

-- 在后端MySQL创建监控用户

CREATE USER 'proxysql_monitor'@'%' IDENTIFIED BY 'MonitorPass123!';

GRANT REPLICATION CLIENT ON . TO 'proxysql_monitor'@'%';

-- 在ProxySQL中配置监控用户

UPDATE global_variables SET variable_value='proxysql_monitor' WHERE variable_name='mysql-monitor_username';

UPDATE global_variables SET variable_value='MonitorPass123!' WHERE variable_name='mysql-monitor_password';

-- 启用read_only监控(自动检测主从角色)

UPDATE global_variables SET variable_value='1' WHERE variable_name='mysql-monitor_read_only_interval';

UPDATE global_variables SET variable_value='5000' WHERE variable_name='mysql-monitor_connect_timeout';

-- 使配置生效

LOAD MYSQL VARIABLES TO RUNTIME;

SAVE MYSQL VARIABLES TO DISK;

```

5.3 创建应用用户

```sql

-- 在后端MySQL创建应用用户

CREATE USER 'app_user'@'%' IDENTIFIED BY 'AppPass123!';

GRANT ALL PRIVILEGES ON . TO 'app_user'@'%';

-- 在ProxySQL中添加应用用户

INSERT INTO mysql_users(

username, password, default_hostgroup,

transaction_persistent, active

) VALUES (

'app_user', 'AppPass123!', 10, 1, 1

);

-- 使配置生效

LOAD MYSQL USERS TO RUNTIME;

SAVE MYSQL USERS TO DISK;

```

注意`transaction_persistent=1`表示在事务中保持连接到同一主机组,确保事务一致性。

5.4 配置读写分离规则

```sql

-- 规则10:管理员命令路由到主库

INSERT INTO mysql_query_rules(

rule_id, active, match_pattern,

destination_hostgroup, apply, comment

) VALUES (

10, 1, '^ADMIN', 10, 1, 'Route admin commands to master'

);

-- 规则11:事务中的查询路由到主库

INSERT INTO mysql_query_rules(

rule_id, active, match_pattern,

destination_hostgroup, apply, comment

) VALUES (

11, 1, '^BEGIN', 10, 1, 'Route transactions to master'

);

-- 规则12:SELECT FOR UPDATE路由到主库

INSERT INTO mysql_query_rules(

rule_id, active, match_pattern,

destination_hostgroup, apply, comment

) VALUES (

12, 1, '^SELECT.FOR UPDATE$', 10, 1, 'Route SELECT FOR UPDATE to master'

);

-- 规则13:普通SELECT路由到从库

INSERT INTO mysql_query_rules(

rule_id, active, match_pattern,

destination_hostgroup, apply, comment

) VALUES (

13, 1, '^SELECT', 20, 1, 'Route SELECT to slaves'

);

-- 使规则生效

LOAD MYSQL QUERY RULES TO RUNTIME;

SAVE MYSQL QUERY RULES TO DISK;

```

这样就完成了一个基本的读写分离配置:写操作和特殊读操作路由到主库,普通读操作路由到从库。

六、监控与故障排查

6.1 查看规则命中情况

```sql

-- 查看各规则命中次数

SELECT rule_id, hits FROM stats_mysql_query_rules;

```

如果某个规则的`hits`一直为0,说明没有匹配到请求,可能是正则表达式有问题。

6.2 查看SQL语句统计

```sql

-- 查看SQL执行统计

SELECT

hostgroup, digest_text, count_star,

sum_time/1000000 as sum_seconds,

min_time/1000 as min_ms,

max_time/1000 as max_ms,

avg_time/1000 as avg_ms

FROM stats_mysql_query_digest

ORDER BY sum_time DESC;

```

这个视图能帮助我们找到最耗时的SQL语句,以便针对性优化。

6.3 查看后端服务器状态

```sql

-- 查看运行时服务器状态

SELECT FROM runtime_mysql_servers;

```

关注`status`列,正常应该是`ONLINE`。如果是`SHUNNED`或`OFFLINE_SOFT`,说明服务器有问题。

6.4 常见故障解决

1. 规则不生效

- 检查`active`字段是否为1

- 确认规则顺序是否正确(rule_id小的先执行)

- 检查是否有`apply=1`的规则在前面匹配了请求

- 执行`LOAD MYSQL QUERY RULES TO RUNTIME`使规则生效

2. 连接后端失败

- 检查后端MySQL是否允许ProxySQL服务器连接

- 确认mysql_users表中的密码是否正确

- 检查防火墙设置,确保3306端口开放

3. 读写分离后数据不一致

- 检查从库延迟:`SHOW SLAVE STATUS`

- 调整`max_replication_lag`参数,延迟过大的从库会被自动下线

- 关键业务读取使用`SELECT ... FOR UPDATE`强制路由到主库

4. MySQL 8.0认证问题

- 如果后端是MySQL 8.0且使用caching_sha2_password认证

- 需要在ProxySQL中设置`admin-hash_passwords=false`

- 并在mysql_users表中存储明文密码

5. ProxySQL重启后配置丢失

- 所有配置修改后都要执行`SAVE ... TO DISK`

- 完整保存命令:

```sql

SAVE MYSQL USERS TO DISK;

SAVE MYSQL SERVERS TO DISK;

SAVE MYSQL QUERY RULES TO DISK;

SAVE MYSQL VARIABLES TO DISK;

```

七、10个实用小技巧

1. 规则顺序很重要:规则按rule_id从小到大执行,更具体的规则应该放在前面

2. 使用apply=1终止匹配:对于确定的规则,设置apply=1可以提高性能

3. 利用缓存减轻读库压力:对频繁查询且实时性要求不高的语句设置cache_ttl

4. 设置查询超时:为长查询设置timeout防止资源耗尽

5. 使用weight分配读负载:给性能好的从库设置更高的weight

6. 监控规则命中率:定期检查stats_mysql_query_rules,优化未命中的规则

7. 备份配置:定期备份/var/lib/proxysql目录下的配置文件

8. 测试新规则:新规则先在测试环境验证,再到生产环境启用

9. 使用negate_match_pattern取反:设置negate_match_pattern=1可以匹配不满足条件的语句

10. 定期清理stats_mysql_query_digest:查询统计会占用内存,定期从stats_mysql_query_digest_reset查询来重置统计

八、新手避坑清单

1. 忘记加载配置到运行时:修改配置后必须执行`LOAD ... TO RUNTIME`才能生效

2. 规则ID冲突:确保rule_id唯一,避免覆盖已有规则

3. 正则表达式错误:match_pattern的正则表达式要测试准确,特别是特殊字符

4. 密码哈希问题:MySQL 8.0使用caching_sha2_password时需要特殊处理

5. 事务一致性:transaction_persistent=1才能保证事务中的语句路由到同一节点

6. 监控用户权限不足:确保监控用户有足够权限获取服务器状态

7. 忽略从库延迟:设置合理的max_replication_lag避免读取过期数据

8. 过度使用match_pattern:简单路由优先使用username、schema等字段,性能更好

9. 忘记保存到磁盘:配置修改后一定要执行SAVE ... TO DISK,否则重启后丢失

10. 规则太复杂:尽量保持规则简洁易懂,复杂规则难以维护和排障

九、长期使用体验

使用ProxySQL已经有两年多了,最大的感受是它的稳定性和灵活性。当初选择它而不是其他中间件,主要看中了这几点:

首先,配置全部通过SQL完成,对DBA非常友好。我们可以像管理数据库一样管理ProxySQL,甚至可以编写脚本自动化配置。

其次,在线配置生效的特性太实用了。生产环境最怕重启服务,而ProxySQL的所有配置都可以在线修改并立即生效,完全不影响业务。

第三,强大的规则系统几乎能满足所有路由需求。从简单的读写分离到复杂的分库分表,从查询缓存到流量控制,ProxySQL都能胜任。

当然,也遇到过一些挑战。比如刚开始使用时,正则表达式写得不够精确导致路由错误;还有一次从库延迟突然增大,没有及时调整max_replication_lag参数,导致应用读到了过期数据。不过这些问题都可以通过完善规则和监控来解决。

总的来说,ProxySQL是一款非常优秀的数据库中间件,尤其适合中大型MySQL集群。它就像一位可靠的交通指挥官,默默管理着数据库流量,让我们的应用跑得更稳更快。

话说回来,数据库中间件只是性能优化的一部分。真正的高性能架构需要从应用设计、数据库优化、缓存策略等多方面综合考虑。但有了ProxySQL这个强大的工具,我们在构建高可用、高性能的数据库架构时,无疑多了一个有力的武器。