ProxySQL读写分离mysql
查询路由是proxysql的核心特性之一。正则表达式
读/写分离多是最经常使用的查询路由之一,而另外一种最经常使用的查询路由是分片。sql
1、使用不一样的端口进行读写分离数据库
若是使用像HAProxy这样的代理,能够将其配置为侦听两个端口:一个端口做为写入端,而第二个端口做为读取端。人们常常询问如何使用相同的方法配置proxysql,以及基于传入端口查询路由。服务器
下面是一个关于如何实现基于传入端口的查询路由的示例,在proxysql的Admin上运行如下命令。app
假设已经在正确的主机组中配置了主服务器和从服务器:ide
主机组10中的MySQL写入工具
主机组20中的MySQL读取性能
若是使用Galera或组复制,也可使用相似的方法。步骤以下this
1. 配置proxysql侦听两个端口并从新启动: mysql-interfaces是少数几个在runtime不能更改且须要从新启动的变量之一
SET mysql-interfaces='0.0.0.0:6401;0.0.0.0:6402';
## save it on disk and restart proxysql
SAVE MYSQL VARIABLES TO DISK;
PROXYSQL RESTART;
2. 添加基于传入端口的路由
INSERT INTO mysql_query_rules (rule_id,active,proxy_port,destination_hostgroup,apply)
VALUES (1,1,6401,10,1), (2,1,6402,20,1);
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK; # if you want this change to be permanent
完成了!如今,全部到端口6401的查询将发送到主机组10中的MySQL服务器,而全部到端口6402的查询将发送到主机组20中的MySQL服务器中的一个。
基于传入端口的读/写分离限制
在前一段中,我写到,人们常常询问如何配置proxysql来使用基于传入端口的路由。
虽然有时这是一种有效的方法,但在我看来,它有一个很大的缺点:应用程序须要内置读/写分离功能,以便区分读和写。
(如下是介绍使用ProxySQL的好处 balabala....)
但实际生产环境中并不是如此。一般,应用程序链接串只配置一个连接(不区分读写),而这个连接就是MySQL主机。若是使用了proxysql,则能够接受单个端口中的全部流量,并能够分析流量,以便根据查询类型执行读/写分离。
这很是方便,由于它不须要任何应用程序更改。
尽管如此,它的主要优点并不在于可以在不更改应用程序的状况下路由流量。主要优势是DBA如今拥有了控制发送到数据库的流量的工具。DBA在半夜被叫醒因为DB服务器超载,在没有开发人员的状况下,不会选择更改应用程序配置,他如今拥有控制流量的选项(即DBA能够经过ProxySQL控制语句发送至哪些MySQL服务器,很是好)。
2、基于正则表达式的读/写分离
在这一段中,将展现一个关于如何使用正则表达式执行读/写分离的例子。
首先,应该删除以前建立的查询规则:
DELETE FROM mysql_query_rules;
而后,为读/写建立基本规则:
UPDATE mysql_users SET default_hostgroup=10; # by default, all goes to HG10
LOAD MYSQL USERS TO RUNTIME;
SAVE MYSQL USERS TO DISK; # if you want this change to be permanent
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);
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK; # if you want this change to be permanent
如今路由将工做以下:
1. 全部SELECT FOR UPDATE将会发送到 HG10
2. 全部其余的SELECT将会发送到HG20
3. 其余全部内容都将发送到HG10(默认值)
注意,我(做者)认为上面的方法不是一个好的读写分离方法。
我(做者)常常用这个例子来描述如何配置规则,但它常常被误解为配置读/写分离的方法(貌似看网上博客也是这样)。
不要在生产中使用上面的例子(来自做者的强调,也就是不要单纯的使用上面两个规则就做为生产环境的读写分离配置,应该是有什么坑的...反正在这里说了,谁爱这样用就这样用(网上不少博客也是这样写的,单用^SELECT.*FOR UPDATE$、^SELECT实现读写分离),死道友不死贫道...)
在下一段中,我(做者)将展现一种更好的方法(使用ProxySQL作读写分离的正确方式)。
如今,让咱们删除全部规则:
DELETE FROM mysql_query_rules;
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK; # if you want this change to be permanent
3、使用正则表达式和摘要进行读/写分离
一个有效地设置读/写分离的配置过程以下:
1. 配置proxysql将全部流量只发送到一个MySQL节点,即主节点(包括写和读)
2. 在stats_mysql_query_digest中查出哪些SELECT最消耗性能
3. 肯定哪些最消耗性能的语句应该移动到读节点
4. 配置mysql_query_rules(建立规则),只向读节点发送最消耗性能的SELECT语句
所以,这个想法很是简单:只发送您想发送的SELECT语句给从库或读节点,而不是任何SELECT语句。(这才是ProxySQL的正确使用方式...)
使用stats_mysql_query_digest查找最消耗性能的查询语句
下面是一些示例,说明如何识别能够发送给读者的潜在查询。
由于proxysql导出表中的全部指标,因此能够建立复杂的查询来收集信息。
这些结果基于一个运行了几个月的很是繁忙的proxysql实例,到目前为止,该实例已经处理了大约千亿次查询。
1. 根据总执行时间查找前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 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. 根据count查找前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)
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)
这个特定的结果代表,有些查询的最大执行时间很是高,而最小执行时间很是小,平均速度也至关慢。
例如,使用摘要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 | ×××26 | 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)
6. 查找按总执行时间排序的前5个查询,平均执行时间至少为15毫秒。还显示总执行时间的百分比:
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 WHERE digest_text LIKE 'SELECT%'),3) pct FROM stats_mysql_query_digest WHERE digest_text LIKE 'SELECT%' AND sum_time/count_star > 15000 ORDER BY sum_time DESC LIMIT 5;
+--------------------+--------------------------+------------+---------------+----------+--------+
| digest | SUBSTR(digest_text,0,25) | count_star | sum_time | avg_time | pct |
+--------------------+--------------------------+------------+---------------+----------+--------+
| 0x38BE36BDFFDBE638 | SELECT instance.name as | 59360371 | 1096562204931 | 18472 | 13.006 |
| 0x36CE5295726DB5B4 | SELECT COUNT(*) as total | 146390 | 185951894994 | 1270249 | 2.205 |
| 0x1DEFCE9DEF3BDF87 | SELECT DISTINCT i.extid | 592281 | 40215136635 | 67898 | 0.477 |
| 0xDA8C56B5644C0822 | SELECT COUNT(*) as total | 44130 | 24842335265 | 562935 | 0.295 |
| 0x9EED412C6E63E477 | SELECT a.id as accountid | 961768 | 24116011513 | 25074 | 0.286 |
+--------------------+--------------------------+------------+---------------+----------+--------+
5 rows in set (0.00 sec)
全部这些查询都须要在master上执行吗?若是一个查询的平均执行时间超过1秒,那么答案极可能是否认的。
对于某些应用程序,甚至平均执行时间为15ms的查询也可能变为从属查询。
例如,在与应用程序全部者进行检查后,咱们能够决定将使用摘要0x38BE36BDFFDBE638查询能够发送到从库:
INSERT INTO mysql_query_rules (rule_id,active,digest,destination_hostgroup,apply)
VALUES
(1,1,'0x38BE36BDFFDBE638',20,1);
一样,在检查这个输出后:
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;
咱们赞成全部以SELECT COUNT(*)开头的查询均可以发送到从库:
INSERT INTO mysql_query_rules (rule_id,active,match_digest,destination_hostgroup,apply)
VALUES
(1,1,'^SELECT COUNT\(\*\)',20,1);
最后,将每一个规则加载到runtime:
LOAD MYSQL QUERY RULES TO RUNTIME;
SAVE MYSQL QUERY RULES TO DISK; # if you want this change to be permanent
proxysql对有选择性的查询路由是很是有效的。
虽然对于某些应用程序,将全部SELECT发送给读库/从库是能够接受的,而将其余全部发送给写库/主库是能够接受的,可是对于许多其余应用程序/工做负载,状况就不那么简单了。
DBA应该可以使用复杂的规则配置proxysql,只将不须要在主服务器上执行的查询发送给从服务器,而不须要对应用程序进行任何更改。