15_ProxySQL读写分离

备注:文章编写时间201904-201905期间,后续官方在github的更新没有被写入 ~ ~ ProxySQL读写分离设置[ProxySQL Read Write Split]

查询路由是ProxySQL的核心功能之一。读写分离可能是最常用的查询路由之一,而另一种最常用的查询路由实现是用于分片(sharding)。

一、使用不同的端口实现读写分离[Read/write split using different ports]

如果使用类似haproxy的代理,可以将其配置为监听两个端口:一个端口作为写入的端口,另一个端口作为读取的端口。人们经常会问如何使用相同的方法配置ProxySQL,以及如何现实基于传入端口的路由查询。

下面是一个关于如何基于传入端口实现查询路由的示例,要在ProxySQL的Admin管理界面运行下面的内容。这里假设您已经在正确的hostgroups中配置了master和slaves: hostgroup 10中的mysql writer,hostgroup 20中的mysql readers。如果您正在使用Galera或组复制,则将应用类似的方法。步骤如下:

1、修改接入端口相关参数

将ProxySQL配置为监听两个端口并重新启动:mysql-interfaces 是少数几个在运行时无法动态更改,需要重新启动的变量之一。

1)当前参数值
Admin> SELECT * FROM global_variables WHERE variable_name ='mysql-interfaces';
+------------------+----------------+
| variable_name    | variable_value |
+------------------+----------------+
| mysql-interfaces | 0.0.0.0:6033   |
+------------------+----------------+
1 row in set (0.00 sec)

或用SHOW命令
Admin> SHOW VARIABLES LIKE 'mysql-interfaces';
+------------------+--------------+
| Variable_name    | Value        |
+------------------+--------------+
| mysql-interfaces | 0.0.0.0:6033 |
+------------------+--------------+
1 row in set (0.00 sec)
2)修改配置
Admin> UPDATE global_variables SET variable_value='0.0.0.0:6401;0.0.0.0:6402' WHERE variable_name ='mysql-interfaces';
或
Admin> SET mysql-interfaces = '0.0.0.0:6401;0.0.0.0:6402';
3)查看修改结果
Admin> SELECT * FROM global_variables where variable_name ='mysql-interfaces';
+------------------+---------------------------+
| variable_name    | variable_value            |
+------------------+---------------------------+
| mysql-interfaces | 0.0.0.0:6401;0.0.0.0:6402 |
+------------------+---------------------------+
1 row in set (0.00 sec)
4)将修改结果持久化
Admin> SAVE MYSQL VARIABLES TO DISK;
Query OK, 110 rows affected (0.00 sec)
5)重启ProxySQL服务
Admin> PROXYSQL RESTART;

2、根据传入端口添加路由规则

1)查看当前后端MySQL主机组配置
Admin> SELECT * FROM mysql_servers;
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| hostgroup_id | hostname     | port | gtid_port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment |
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| 1            | 188.188.0.68 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 2            | 188.188.0.69 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 2            | 188.188.0.70 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
3 rows in set (0.01 sec)
2)清除旧的分组
Admin> DELETE FROM mysql_servers ;
Query OK, 6 rows affected (0.00 sec)

Admin> SELECT * FROM mysql_servers;
Empty set (0.00 sec)
3)新增mysql writer组10和mysql readers组20
Admin> INSERT INTO mysql_servers(hostgroup_id,hostname,port) VALUES(10,'188.188.0.68',3306),(20,'188.188.0.69',3306),(20,'188.188.0.70',3306);
Query OK, 3 rows affected (0.00 sec)

Admin> SELECT * FROM mysql_servers;
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| hostgroup_id | hostname     | port | gtid_port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment |
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| 10           | 188.188.0.68 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 20           | 188.188.0.69 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 20           | 188.188.0.70 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
3 rows in set (0.00 sec)
4)查看当前拥有的规则
Admin> SELECT rule_id,active,proxy_port,destination_hostgroup,apply FROM mysql_query_rules;
+---------+--------+------------+-----------------------+-------+
| rule_id | active | proxy_port | destination_hostgroup | apply |
+---------+--------+------------+-----------------------+-------+
| 5       | 1      | NULL       | NULL                  | 1     |
| 10      | 1      | NULL       | 2                     | 1     |
| 20      | 1      | NULL       | 2                     | 0     |
| 30      | 1      | NULL       | NULL                  | 1     |
+---------+--------+------------+-----------------------+-------+
4 rows in set (0.00 sec)
5)删除旧规则
Admin> DELETE FROM mysql_query_rules;
Query OK, 2 rows affected (0.00 sec)

Admin> SELECT rule_id,active,proxy_port,destination_hostgroup,apply FROM mysql_query_rules;
Empty set (0.00 sec)
6)插入路由规则
Admin> INSERT INTO mysql_query_rules (rule_id,active,proxy_port,destination_hostgroup,apply)
    -> VALUES (1,1,6401,10,1), (2,1,6402,20,1);
Query OK, 2 rows affected (0.00 sec)

Admin> SELECT rule_id,active,proxy_port,destination_hostgroup,apply FROM mysql_query_rules;
+---------+--------+------------+-----------------------+-------+
| rule_id | active | proxy_port | destination_hostgroup | apply |
+---------+--------+------------+-----------------------+-------+
| 1       | 1      | 6401       | 10                    | 1     |
| 2       | 1      | 6402       | 20                    | 1     |
+---------+--------+------------+-----------------------+-------+
2 rows in set (0.00 sec)
7)将修改后的配置加载到RUNTIME层
Admin> LOAD MYSQL SERVERS TO RUNTIME;
Query OK, 0 rows affected (0.01 sec)

Admin> SELECT * FROM runtime_mysql_servers;
+--------------+--------------+------+-----------+--------------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| hostgroup_id | hostname     | port | gtid_port | status       | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment |
+--------------+--------------+------+-----------+--------------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| 2            | 188.188.0.70 | 3306 | 0         | OFFLINE_HARD | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 20           | 188.188.0.70 | 3306 | 0         | ONLINE       | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 20           | 188.188.0.69 | 3306 | 0         | ONLINE       | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 10           | 188.188.0.68 | 3306 | 0         | ONLINE       | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
+--------------+--------------+------+-----------+--------------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
4 rows in set (0.00 sec)


Admin> LOAD MYSQL QUERY RULES TO RUNTIME;
Query OK, 0 rows affected (0.00 sec)

Admin> SELECT rule_id,active,proxy_port,destination_hostgroup,apply FROM runtime_mysql_query_rules;
+---------+--------+------------+-----------------------+-------+
| rule_id | active | proxy_port | destination_hostgroup | apply |
+---------+--------+------------+-----------------------+-------+
| 1       | 1      | 6401       | 10                    | 1     |
| 2       | 1      | 6402       | 20                    | 1     |
+---------+--------+------------+-----------------------+-------+
2 rows in set (0.00 sec)
8)将修改后的配置持久化到DISK层
Admin> SAVE MYSQL SERVERS TO DISK;
Query OK, 0 rows affected (0.02 sec)

Admin> SELECT * FROM disk.mysql_servers;
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| hostgroup_id | hostname     | port | gtid_port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment |
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
| 10           | 188.188.0.68 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 20           | 188.188.0.69 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
| 20           | 188.188.0.70 | 3306 | 0         | ONLINE | 1      | 0           | 1000            | 0                   | 0       | 0              |         |
+--------------+--------------+------+-----------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------+
3 rows in set (0.00 sec)


Admin> SAVE MYSQL QUERY RULES TO DISK;
Query OK, 0 rows affected (0.01 sec)

Admin> SELECT rule_id,active,proxy_port,destination_hostgroup,apply FROM disk.mysql_query_rules;
+---------+--------+------------+-----------------------+-------+
| rule_id | active | proxy_port | destination_hostgroup | apply |
+---------+--------+------------+-----------------------+-------+
| 1       | 1      | 6401       | 10                    | 1     |
| 2       | 1      | 6402       | 20                    | 1     |
+---------+--------+------------+-----------------------+-------+
2 rows in set (0.00 sec)

到此配置完成! 现在,所有到 6401 端口的查询都将发送到hostgroup 10中的mysql服务器,而所有到 6402 端口的查询都将发送到hostgroup 20中的一个mysql服务器。

3、验证结果

1)查看当前规则命中信息
Admin> SELECT hits, mysql_query_rules.rule_id, match_digest, match_pattern, replace_pattern, cache_ttl, apply 
    -> FROM mysql_query_rules 
    -> NATURAL JOIN stats.stats_mysql_query_rules 
    -> ORDER BY mysql_query_rules.rule_id;
+------+---------+--------------+---------------+-----------------+-----------+-------+
| hits | rule_id | match_digest | match_pattern | replace_pattern | cache_ttl | apply |
+------+---------+--------------+---------------+-----------------+-----------+-------+
| 0    | 1       | NULL         | NULL          | NULL            | NULL      | 1     |
| 0    | 2       | NULL         | NULL          | NULL            | NULL      | 1     |
+------+---------+--------------+---------------+-----------------+-----------+-------+
2 rows in set (0.01 sec)
2)在后端可写MySQL上新建测试表
mysql> show create table t1 \G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `id` int(11) NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
3)通过读写账户 6401 连入 ProxySQL 执行写入操作:
[root@localhost ~]# mysql -utest_rw -p -h 188.188.0.71 -P 6401
mysql> use test;
mysql> insert into t1 values(1,'a');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t1 values(2,'b');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t1;
+----+------+
| id | name |
+----+------+
|  1 | a    |
|  2 | b    |
+----+------+
2 rows in set (0.00 sec)
4)查看ProxySQL中的信息

查看Proxy SQL中进程信息

Admin> show full processlist;
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
| ThreadID | SessionID | user    | db   | cli_host     | cli_port | hostgroup | l_srv_host | l_srv_port | srv_host | srv_port | command | time_ms | info | status_flags |
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
| 2        | 134       | test_rw | test | 188.188.0.70 | 37072    | 10        |            |            |          |          | Sleep   | 10010   |      |              |
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
1 row in set (0.00 sec)

可以看到,我们在188.188.0.70发起连接,操作的是写组 hostgroup 10,库为test;和我们预期的一样。

查看规则命中信息

Admin> SELECT hits, mysql_query_rules.rule_id, match_digest, match_pattern, replace_pattern, cache_ttl, apply 
    -> FROM mysql_query_rules  NATURAL JOIN stats.stats_mysql_query_rules 
    -> ORDER BY mysql_query_rules.rule_id;
+------+---------+--------------+---------------+-----------------+-----------+-------+
| hits | rule_id | match_digest | match_pattern | replace_pattern | cache_ttl | apply |
+------+---------+--------------+---------------+-----------------+-----------+-------+
| 6    | 1       | NULL         | NULL          | NULL            | NULL      | 1     |
| 0    | 2       | NULL         | NULL          | NULL            | NULL      | 1     |
+------+---------+--------------+---------------+-----------------+-----------+-------+
2 rows in set (0.00 sec)

规则1有了命中信息。 备注:只要是向 6401 发出的指令,如show、use等都会使hits增加。

5)通过读写账户 6402 连入 ProxySQL 执行读取操作:
[root@localhost ~]# mysql -utest_rw -p -h 188.188.0.71 -P 6402
mysql> SELECT * FROM test.t1 ;
+----+------+
| id | name |
+----+------+
|  1 | a    |
|  2 | b    |
+----+------+
2 rows in set (0.00 sec)
6)查看ProxySQL中的信息

查看Proxy SQL中进程信息

Admin> show full processlist;
+----------+-----------+---------+--------------------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
| ThreadID | SessionID | user    | db                 | cli_host     | cli_port | hostgroup | l_srv_host | l_srv_port | srv_host | srv_port | command | time_ms | info | status_flags |
+----------+-----------+---------+--------------------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
| 0        | 135       | test_rw | information_schema | 188.188.0.70 | 47732    | 20        |            |            |          |          | Sleep   | 18018   |      |              |
+----------+-----------+---------+--------------------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
1 row in set (0.00 sec)

可以看到,我们在188.188.0.70发起连接,操作的是写组 hostgroup 20,库为test;和我们预期的一样。

查看规则命中信息

Admin> SELECT hits, mysql_query_rules.rule_id, match_digest, match_pattern, replace_pattern, cache_ttl, apply 
    -> FROM mysql_query_rules  NATURAL JOIN stats.stats_mysql_query_rules 
    -> ORDER BY mysql_query_rules.rule_id;
+------+---------+--------------+---------------+-----------------+-----------+-------+
| hits | rule_id | match_digest | match_pattern | replace_pattern | cache_ttl | apply |
+------+---------+--------------+---------------+-----------------+-----------+-------+
| 6    | 1       | NULL         | NULL          | NULL            | NULL      | 1     |
| 2    | 2       | NULL         | NULL          | NULL            | NULL      | 1     |
+------+---------+--------------+---------------+-----------------+-----------+-------+
2 rows in set (0.00 sec)

规则2也有了命中信息。

说明:如果先前连入过,则会受到连接池的影响,在连入时不会产生命中数增加。

到此,不同端口的读写分离配置完毕!!

二、基于传入端口的读写分离限制[Limitation of read/write split based on incoming port]

在前面提到过人们经常会问如何配置ProxySQL以使用基于传入端口的路由。虽然这有时是一种有效的方法,但在我看来它有一个很大的缺点: 使用数据库的应用程序需要具有内置的读写分离功能,以便区分读和写。但通常情况并非如此。 通常应用程序只使用一个连接端点,当然它是MySQL主节点。如果使用ProxySQL,则可以在单个端口中接受所有的流量,并可以根据查询类型分析流量以执行读/写拆分。这非常方便,因为它不需要任何应用程序更改。

尽管如此,其主要优势不是能够在不更改应用程序的情况下路由流量。主要的优点是DBA现在有了控制发送到数据库流量的工具。DBA是一个在午夜,由于数据库服务器过载,而被叫醒的群体;而且,因为当时周围没有开发人员,想在应用程序中进行更改是不可能的;但他们现在有了控制流量的选项。

三、(基于1个端口)使用正则表达式的实现基本读写分离[basic read/write split using regex]

在这一段中,将展示一个如何使用正则表达式来实现读写分离的示例。

-- >>>>>>>>>>>>>>>>>>配置方式一(不建议):

!!!!请不要在生产中使用下面这个示例配置!!!!

1、修改接入端口相关参数

Admin> SHOW VARIABLES LIKE 'mysql_interfaces';
+------------------+---------------------------+
| Variable_name    | Value                     |
+------------------+---------------------------+
| mysql-interfaces | 0.0.0.0:6401;0.0.0.0:6402 |
+------------------+---------------------------+
1 row in set (0.00 sec)

改用一个端口对外提供接入。

Admin> SET mysql-interfaces ='0.0.0.0:6033';
Query OK, 1 row affected (0.00 sec)

将修改持久化

Admin> SAVE MYSQL VARIABLES TO DISK;
Query OK, 110 rows affected (0.02 sec)

重启ProxySQL服务,使配置生效

Admin> PROXYSQL RESTART;

查看修改结果

Admin> SHOW VARIABLES LIKE 'mysql_interfaces';
+------------------+--------------+
| Variable_name    | Value        |
+------------------+--------------+
| mysql-interfaces | 0.0.0.0:6033 |
+------------------+--------------+
1 row in set (0.00 sec)

2、实验前删除以前创建的查询规则:

Admin> DELETE FROM mysql_query_rules;
Query OK, 6 rows affected (0.00 sec)

Admin> SELECT * FROM mysql_query_rules ;
Empty set (0.00 sec)

3、创建读/写的基本规则:

1)修改目标用户的默认组
Admin> SELECT username,active,default_hostgroup FROM mysql_users WHERE username='test_rw';
+----------+--------+-------------------+
| username | active | default_hostgroup |
+----------+--------+-------------------+
| test_rw  | 1      | 1                 |
+----------+--------+-------------------+
1 row in set (0.00 sec)

# by default, all goes to HG10
Admin> UPDATE mysql_users SET default_hostgroup=10 WHERE username='test_rw';
Query OK, 1 row affected (0.00 sec)

Admin> SELECT username,active,default_hostgroup FROM mysql_users WHERE username='test_rw';
+----------+--------+-------------------+
| username | active | default_hostgroup |
+----------+--------+-------------------+
| test_rw  | 1      | 10                |
+----------+--------+-------------------+
1 row in set (0.00 sec)
2)加载配置到RUNTIME层,使其生效
Admin> LOAD MYSQL USERS TO RUNTIME;
Query OK, 0 rows affected (0.00 sec)

Admin> SELECT * FROM runtime_mysql_users WHERE username='test_rw';
+----------+-------------------------------------------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+---------+
| username | password                                  | active | use_ssl | default_hostgroup | default_schema | schema_locked | transaction_persistent | fast_forward | backend | frontend | max_connections | comment |
+----------+-------------------------------------------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+---------+
| test_rw  | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 | 1      | 0       | 10                |                | 0             | 1                      | 0            | 1       | 0        | 10000           |         |
| test_rw  | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 | 1      | 0       | 10                |                | 0             | 1                      | 0            | 0       | 1        | 10000           |         |
+----------+-------------------------------------------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+---------+
2 rows in set (0.00 sec)
3)持久化配置到DISK层
Admin> SAVE MYSQL USERS TO DISK;
Query OK, 0 rows affected (0.00 sec)

Admin> SELECT * FROM disk.mysql_users WHERE username='test_rw';
+----------+-------------------------------------------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+---------+
| username | password                                  | active | use_ssl | default_hostgroup | default_schema | schema_locked | transaction_persistent | fast_forward | backend | frontend | max_connections | comment |
+----------+-------------------------------------------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+---------+
| test_rw  | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 | 1      | 0       | 10                |                | 0             | 1                      | 0            | 1       | 1        | 10000           |         |
+----------+-------------------------------------------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+---------+
1 row in set (0.00 sec)
4)插入读写规则
Admin> INSERT INTO mysql_query_rules (rule_id,active,match_digest,destination_hostgroup,apply)
    -> VALUES (1,1,'^SELECT.*FOR UPDATE$',10,1),(2,1,'^SELECT',20,1);
Query OK, 2 rows affected (0.00 sec)

Admin> SELECT rule_id,active,match_digest,destination_hostgroup,apply FROM mysql_query_rules ;
+---------+--------+----------------------+-----------------------+-------+
| rule_id | active | match_digest         | destination_hostgroup | apply |
+---------+--------+----------------------+-----------------------+-------+
| 1       | 1      | ^SELECT.*FOR UPDATE$ | 10                    | 1     |
| 2       | 1      | ^SELECT              | 20                    | 1     |
+---------+--------+----------------------+-----------------------+-------+
2 rows in set (0.00 sec)
5)加载配置到RUNTIME层,使其生效
Admin> LOAD MYSQL QUERY RULES TO RUNTIME;
Query OK, 0 rows affected (0.00 sec)

Admin> SELECT rule_id,active,match_digest,destination_hostgroup,apply FROM runtime_mysql_query_rules ;
+---------+--------+----------------------+-----------------------+-------+
| rule_id | active | match_digest         | destination_hostgroup | apply |
+---------+--------+----------------------+-----------------------+-------+
| 1       | 1      | ^SELECT.*FOR UPDATE$ | 10                    | 1     |
| 2       | 1      | ^SELECT              | 20                    | 1     |
+---------+--------+----------------------+-----------------------+-------+
2 rows in set (0.00 sec)
6)持久化配置到DISK层
Admin> SAVE MYSQL QUERY RULES TO DISK;
Query OK, 0 rows affected (0.01 sec)

Admin> SELECT rule_id,active,match_digest,destination_hostgroup,apply FROM disk.mysql_query_rules ;
+---------+--------+----------------------+-----------------------+-------+
| rule_id | active | match_digest         | destination_hostgroup | apply |
+---------+--------+----------------------+-----------------------+-------+
| 1       | 1      | ^SELECT.*FOR UPDATE$ | 10                    | 1     |
| 2       | 1      | ^SELECT              | 20                    | 1     |
+---------+--------+----------------------+-----------------------+-------+
2 rows in set (0.00 sec)

现在路由将会按如下工作: 1)所有 SELECT FOR UPDATE 查询都发往 HG10 ; 2)其他所有的 SELECT 查询都发往 HG20 ; 3)除以上2种查询以外,其他的查询都将发往 HG10 (默认组);

4、结果验证

1)查看当前规则命中信息
Admin> SELECT hits, mysql_query_rules.rule_id, match_digest, match_pattern, replace_pattern, cache_ttl, apply
    -> FROM mysql_query_rules 
    -> NATURAL JOIN stats.stats_mysql_query_rules 
    -> ORDER BY mysql_query_rules.rule_id;
+------+---------+----------------------+---------------+-----------------+-----------+-------+
| hits | rule_id | match_digest         | match_pattern | replace_pattern | cache_ttl | apply |
+------+---------+----------------------+---------------+-----------------+-----------+-------+
| 0    | 1       | ^SELECT.*FOR UPDATE$ | NULL          | NULL            | NULL      | 1     |
| 0    | 2       | ^SELECT              | NULL          | NULL            | NULL      | 1     |
+------+---------+----------------------+---------------+-----------------+-----------+-------+
2 rows in set (0.00 sec)

规则1、2都是0。

2)通过 6033 端口连入 ProxySQL 执行操作:
[root@localhost ~]# mysql -utest_rw -p -h 188.188.0.71 -P 6033
mysql> use test;   ==>触发一次规则2 hits

mysql> select * from t1;    ==>触发一次规则2 hits
Empty set (0.00 sec)

mysql> insert into t1 values(1,'a');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t1 values(2,'b');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t1;    ==>触发一次规则2 hits
3)操作后,查看ProxySQL中的信息

查看连接信息

Admin> SHOW FULL PROCESSLIST;
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
| ThreadID | SessionID | user    | db   | cli_host     | cli_port | hostgroup | l_srv_host | l_srv_port | srv_host | srv_port | command | time_ms | info | status_flags |
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
| 2        | 3         | test_rw | test | 188.188.0.70 | 55705    | 20        |            |            |          |          | Sleep   | 22022   |      |              |
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
1 row in set (0.00 sec)

最后的select请求流入了H20,与预设一致。

查看规则命中信息

Admin> SELECT hits, mysql_query_rules.rule_id, match_digest, match_pattern, replace_pattern, cache_ttl, apply
    -> FROM mysql_query_rules 
    -> NATURAL JOIN stats.stats_mysql_query_rules 
    -> ORDER BY mysql_query_rules.rule_id;
+------+---------+----------------------+---------------+-----------------+-----------+-------+
| hits | rule_id | match_digest         | match_pattern | replace_pattern | cache_ttl | apply |
+------+---------+----------------------+---------------+-----------------+-----------+-------+
| 0    | 1       | ^SELECT.*FOR UPDATE$ | NULL          | NULL            | NULL      | 1     |
| 3    | 2       | ^SELECT              | NULL          | NULL            | NULL      | 1     |
+------+---------+----------------------+---------------+-----------------+-----------+-------+
2 rows in set (0.00 sec)

三次触发,使得hits增加了3。

4)测试FOR UPDATE
mysql> select * from t1 for update ; 
+----+------+
| id | name |
+----+------+
|  1 | a    |
|  2 | b    |
+----+------+
2 rows in set (0.00 sec)
5)操作后,查看ProxySQL中的信息

查看连接信息

Admin> SHOW FULL PROCESSLIST;                                                                                                                                                 
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
| ThreadID | SessionID | user    | db   | cli_host     | cli_port | hostgroup | l_srv_host | l_srv_port | srv_host | srv_port | command | time_ms | info | status_flags |
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
| 2        | 3         | test_rw | test | 188.188.0.70 | 55705    | 10        |            |            |          |          | Sleep   | 12012   |      |              |
+----------+-----------+---------+------+--------------+----------+-----------+------------+------------+----------+----------+---------+---------+------+--------------+
1 row in set (0.00 sec)

FOR UPDATE请求流入了HG10,与预设一致。

查看规则命中信息

Admin> SELECT hits, mysql_query_rules.rule_id, match_digest, match_pattern, replace_pattern, cache_ttl, apply
    -> FROM mysql_query_rules 
    -> NATURAL JOIN stats.stats_mysql_query_rules 
    -> ORDER BY mysql_query_rules.rule_id;
+------+---------+----------------------+---------------+-----------------+-----------+-------+
| hits | rule_id | match_digest         | match_pattern | replace_pattern | cache_ttl | apply |
+------+---------+----------------------+---------------+-----------------+-----------+-------+
| 1    | 1       | ^SELECT.*FOR UPDATE$ | NULL          | NULL            | NULL      | 1     |
| 3    | 2       | ^SELECT              | NULL          | NULL            | NULL      | 1     |
+------+---------+----------------------+---------------+-----------------+-----------+-------+
2 rows in set (0.00 sec)

一次请求,使得hits增加了1。

请注意,我认为上述方法不是读取/写入拆分的好方法。 我经常使用这个例子来描述如何配置规则,但它却经常被错误地解释为配置读/写拆分的方法。

!!!!请不要在生产中使用上述示例配置!!!!

-- >>>>>>>>>>>>>>>>>>配置方式二(推荐):

在下面这段中,将展示一种更好的方法。

现在,让我们删除所有规则:

Admin> DELETE FROM mysql_query_rules;
Query OK, 2 rows affected (0.00 sec)

Admin> LOAD MYSQL QUERY RULES TO RUNTIME;
Query OK, 0 rows affected (0.00 sec)

Admin> SAVE MYSQL QUERY RULES TO DISK; 
Query OK, 0 rows affected (0.02 sec)

Admin> SELECT * FROM runtime_mysql_query_rules;
Empty set (0.00 sec)

Admin> SELECT * FROM disk.mysql_query_rules;
Empty set (0.00 sec)

1、使用regex和digest进行读/写拆分[read/write split using regex and digest]

有效设置读/写拆分的配置过程如下: 1)配置 ProxySQL 只将所有流量发送到一个 MySQL 节点——master(写和读); 2)在 stats_mysql_query_digest 中查找执行成本较高的 SELECT 语句; 3)确定哪些成本较高的 SELECT 语句应该被移动到读节点上执行; 4)配置 mysql_query_rules 将需要移动的 SELECT 语句发送到读节点; 因此,这个想法非常简单:只发送你想发送给 Slave/Reader 的内容,而不只是任何SELECT语句。

2、使用stats_mysql_query_digest查找成本较高的查询[Find expensive queries using stats_mysql_query_digest]

下面是一个示例列表,说明如何识别可以发送给Readers的潜在查询。由于 ProxySQL 在表中记录了所有度量信息,所以可以创建复杂的查询来收集信息。 这些结果是基于一个运行了几个月的非常繁忙的 ProxySQL 实例;到目前为止,该实例已经处理了大约千亿个查询。

1)根据总执行时间查找最耗时的前5个查询:
Admin> SELECT digest,SUBSTR(digest_text,0,25),count_star,sum_time FROM stats.stats_mysql_query_digest WHERE digest_text LIKE 'SELECT%' ORDER BY sum_time DESC LIMIT 5;
+--------------------+--------------------------+------------+---------------+
| digest             | SUBSTR(digest_text,0,25) | count_star | sum_time      |
+--------------------+--------------------------+------------+---------------+
| 0x037C3E6D996DAFE2 | SELECT a.ip_id as ip_id, | 2030026798 | 1479082636017 |
| 0xB081A85245DEA5B7 | SELECT a.ip_id as ip_id, | 2025902778 | 1206116187539 |
| 0x38BE36BDFFDBE638 | SELECT instance.name as  | 59343662   | 1096236803754 |
| 0xB4233552504E43B8 | SELECT ir.type as type,  | 1362897166 | 488971769571  |
| 0x4A131A16DCFFD6C6 | SELECT i.id as id, i.sta | 934402293  | 475253770301  |
+--------------------+--------------------------+------------+---------------+
5 rows in set (0.01 sec)
2)根据计数查找前5个查询:
Admin> SELECT digest,SUBSTR(digest_text,0,25),count_star,sum_time FROM stats_mysql_query_digest WHERE digest_text LIKE 'SELECT%' ORDER BY count_star DESC LIMIT 5;
+--------------------+--------------------------+------------+---------------+
| digest             | SUBSTR(digest_text,0,25) | count_star | sum_time      |
+--------------------+--------------------------+------------+---------------+
| 0x037C3E6D996DAFE2 | SELECT a.ip_id as ip_id, | 2030040688 | 1479092529369 |
| 0xB081A85245DEA5B7 | SELECT a.ip_id as ip_id, | 2025916528 | 1206123010791 |
| 0x22E0A5C585C53EAD | SELECT id as instanceid, | 1551361254 | 426419508609  |
| 0x3DB4B9FA4B2CB36F | SELECT i.id as instancei | 1465274289 | 415565419867  |
| 0xB4233552504E43B8 | SELECT ir.type as type,  | 1362906755 | 488974931108  |
+--------------------+--------------------------+------------+---------------+
5 rows in set (0.00 sec)

嗯...,这些查询中的一些可以缓存吗?也许proxysql查询缓存可以帮助您!

3)根据最长执行时间查找前5个查询:
Admin> SELECT digest,SUBSTR(digest_text,0,25),count_star,sum_time,sum_time/count_star avg_time, min_time, max_time FROM stats_mysql_query_digest WHERE digest_text LIKE 'SELECT%' ORDER BY max_time DESC LIMIT 5;
+--------------------+--------------------------+------------+--------------+----------+----------+-----------+
| digest             | SUBSTR(digest_text,0,25) | count_star | sum_time     | avg_time | min_time | max_time  |
+--------------------+--------------------------+------------+--------------+----------+----------+-----------+
| 0x36CE5295726DB5B4 | SELECT COUNT(*) as total | 146390     | 185951894994 | 1270249  | 445      | 237344243 |
| 0xDA8C56B5644C0822 | SELECT COUNT(*) as total | 44130      | 24842335265  | 562935   | 494      | 231395575 |
| 0x8C1B0405E1AAB9DB | SELECT COUNT(*) as total | 1194       | 1356742749   | 1136300  | 624      | 216677507 |
| 0x6C03197B4A2C34BE | Select *, DateDiff(Date_ | 4796       | 748804483    | 156131   | 607      | 197881845 |
| 0x1DEFCE9DEF3BDF87 | SELECT DISTINCT i.extid  | 592196     | 40209254260  | 67898    | 416      | 118055372 |
+--------------------+--------------------------+------------+--------------+----------+----------+-----------+
5 rows in set (0.01 sec)

具体结果表明,有些查询的最大执行时间很高,而最小执行时间很小,平均速度也相当慢。 例如,Digest为0x36CE5295726DB5B4的查询,平均执行时间为1.27秒,最小执行时间为0.4ms,最大执行时间为237.34秒。也许值得调查一下为什么执行时间不均匀。

4)查找按总执行时间排序的前5个查询,最小执行时间至少为1毫秒:
Admin> SELECT digest,SUBSTR(digest_text,0,20),count_star,sum_time,sum_time/count_star avg_time, min_time, max_time FROM stats_mysql_query_digest WHERE digest_text LIKE 'SELECT%' AND min_time > 1000 ORDER BY sum_time DESC LIMIT 5;
+--------------------+--------------------------+------------+-------------+----------+----------+----------+
| digest             | SUBSTR(digest_text,0,20) | count_star | sum_time    | avg_time | min_time | max_time |
+--------------------+--------------------------+------------+-------------+----------+----------+----------+
| 0x9EED412C6E63E477 | SELECT a.id as acco      | 961733     | 24115349801 | 25074    | 10994    | 7046628  |
| 0x8DDD43A9EA37750D | Select ( Coalesce((      | 107069     | 3156179256  | 29477    | 1069     | 24600674 |
| 0x9EED412C6E63E477 | SELECT a.id as acco      | 91996      | 1883354396  | 20472    | 10095    | 497877   |
| 0x08B23A268C35C08E | SELECT id as reward      | 49401      | 244088592   | 4940     | 1237     | 1483791  |
| 0x437C846F935344F8 | SELECT Distinct i.e      | 164        | 163873101   | 999226   | 1383     | 7905811  |
+--------------------+--------------------------+------------+-------------+----------+----------+----------+
5 rows in set (0.01 sec)
```
#### 5)查找按总执行时间排序的前5个查询,平均执行时间至少为1秒。同时显示总执行时间的百分比:
```
Admin> SELECT digest,SUBSTR(digest_text,0,25),count_star,sum_time,sum_time/count_star avg_time, ROUND(sum_time*100.00/(SELECT SUM(sum_time) FROM stats_mysql_query_digest),3) pct FROM stats_mysql_query_digest WHERE digest_text LIKE 'SELECT%' AND sum_time/count_star > 1000000 ORDER BY sum_time DESC LIMIT 5;
+--------------------+--------------------------+------------+--------------+----------+-------+
| digest             | SUBSTR(digest_text,0,25) | count_star | sum_time     | avg_time | pct   |
+--------------------+--------------------------+------------+--------------+----------+-------+
| 0x36CE5295726DB5B4 | SELECT COUNT(*) as total | 146390     | 185951894994 | 1270249  | 2.11  |
| 0xD38895B4F4D2A4B3 | SELECT instance.name as  | 9783       | 12409642528  | 1268490  | 0.141 |
| 0x8C1B0405E1AAB9DB | SELECT COUNT(*) as total | 1194       | 1356742749   | 1136300  | 0.015 |
+--------------------+--------------------------+------------+--------------+----------+-------+
3 rows in set (0.00 sec)
```
所有这些查询都需要在master上执行?如果一个查询的平均执行时间超过1秒,则答案可能是"No"。
对于某些应用程序,即使运行平均执行时间为15毫秒的查询也可能需要转到slave。


#### 6)编写查询规则
在与应用程序开发人员确认之后,例如,我们可以将digset为 0x38BE36BDFFDBE638 的查询转移到Slave上执行:
INSERT INTO mysql_query_rules (rule_id,active,digest,destination_hostgroup,apply)
VALUES (1,1,'0x38BE36BDFFDBE638',20,1);

#### 7)同样,假设要将所有以SELECT COUNT(*)开头的查询都可以转到Slaves
查看该类语句的执行情况:
```
SELECT digest,digest_text,count_star,sum_time,sum_time/count_star avg_time, ROUND(sum_time*100.00/(SELECT SUM(sum_time) FROM stats_mysql_query_digest WHERE digest_text LIKE 'SELECT%'),3) pct FROM stats_mysql_query_digest WHERE digest_text LIKE 'SELECT COUNT%' ORDER BY sum_time DESC;
```
编写查询规则:
```
INSERT INTO mysql_query_rules (rule_id,active,match_digest,destination_hostgroup,apply)
VALUES (1,1,'^SELECT COUNT\(\*\)',20,1);
```

#### 8)最后,将每个规则加载到RUNTIME层和持久化到DISK层:
```
LOAD MYSQL QUERY RULES TO RUNTIME;

SAVE MYSQL QUERY RULES TO DISK; # if you want this change to be permanent
```



## 四、结论[Conclusion]
ProxySQL可以有效地用于非常有选择性的查询路由。
对于某些应用程序,可以接受将所有 SELECT 发送给Reader/Slave,而将剩余的其他查询则发送给Writer/Master;但对于许多其他applications/workloads来说,并不是那么简单。
DBA应该能够用复杂的规则配置ProxySQL,只将不需要在Master上执行的查询发送到Slaves,而不需要更改任何应用程序。

~
~
完毕!!