转载自:https://blog.csdn.net/weixin_30911809/article/details/98601663
当ProxySQL收到前端app发送的SQL语句后,它需要将这个SQL语句(或者重写后的SQL语句)发送给后端的MySQL Server,然后收到SQL语句的MySQL Server执行查询,并将查询结果返回给ProxySQL,再由ProxySQL将结果返回给客户端(如果设置了查询缓存,则先缓存查询结果)。
ProxySQL可以实现多种方式的路由:基于ip/port、username、schema、SQL语句。其中基于SQL语句的路由是按照规则进行匹配的,匹配方式有hash高效匹配、正则匹配,还支持更复杂的链式规则匹配。
在这里本篇只介绍基于SQL语句的路由的匹配
可以通过 show create table mysql_query_rules
语句查看定义该表的语句。
| COLUMN | TYPE | NULL? | DEFAULT |
|-----------------------|---------|----------|------------|
| rule_id (pk) | INTEGER | NOT NULL | |
| active | INT | NOT NULL | 0 |
| username | VARCHAR | | |
| schemaname | VARCHAR | | |
| flagIN | INT | NOT NULL | 0 |
| client_addr | VARCHAR | | |
| proxy_addr | VARCHAR | | |
| proxy_port | INT | | |
| digest | VARCHAR | | |
| match_digest | VARCHAR | | |
| match_pattern | VARCHAR | | |
| negate_match_pattern | INT | NOT NULL | 0 |
| re_modifiers | VARCHAR | | 'CASELESS' |
| flagOUT | INT | | |
| replace_pattern | VARCHAR | | |
| destination_hostgroup | INT | | NULL |
| cache_ttl | INT | | |
| reconnect | INT | | NULL |
| timeout | INT | | |
| retries | INT | | |
| delay | INT | | |
| mirror_flagOU | INT | | |
| mirror_hostgroup | INT | | |
| error_msg | VARCHAR | | |
| sticky_conn | INT | | |
| multiplex | INT | | |
| log | INT | | |
| apply | INT | NOT NULL | 0 |
| comment | VARCHAR | | |
各个字段的意义如下:
# 查询信息状态
select hostgroup hg,count_star cs,digest,digest_text dt from stats_mysql_query_digest;
select rule_id,active,digest,match_digest,match_pattern,destination_hostgroup,apply from mysql_query_rules;
# 查询规则
select rule_id,active,match_pattern,match_digest,destination_hostgroup,apply from mysql_query_rules;
# 修改规则
update mysql_query_rules set match_digest='^select.*getSequenceNo.*' where rule_id=2;
# 停止规则
update mysql_query_rules set active=0 where rule_id=2;
update mysql_query_rules set rule_id=100 where destination_hostgroup=100;
update mysql_query_rules set rule_id=101 where destination_hostgroup=1000;
# 删除规则
delete from mysql_query_rules where rule_id=2;
# 主从分组信息
select hostgroup_id,hostname,port,status,weight from mysql_servers;
# log
select * from mysql_server_ping_log;
select * from mysql_server_replication_lag_log;
select * from mysql_server_read_only_log;
# 写入新规则
insert into mysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply) VALUES (2,1,'^select.*getSequenceNo',0,1);
load mysql query rules to runtime;
save mysql query rules to disk;
ProxySQL接收到前端发送的SQL语句后,首先分析语句,然后从mysql_query_rules表中寻找是否有匹配该语句的规则。如果先被username或ip/port类的规则匹配并应用,则按这些规则路由给后端,如果是被基于SQL语句的规则匹配,则启动正则引擎进行正则匹配,然后路由给对应的后端组,如果规则中指定了正则替换字段,则还会重写SQL语句,然后再发送给后端。
什么是参数化?
select * from tbl where id=?
这里将where条件语句中字段id的值进行了参数化,也就是上面的问号?。
我们在客户端发起的SQL语句都是完整格式的语句,但是SQL优化引擎出于优化的目的需要考虑很多事情。例如,如何缓存查询结果、如何匹配查询缓存中的数据并取出,等等。将SQL语句参数化是优化引擎其中的一个行为,对于那些参数相同但参数值不同的查询语句,SQL语句认为这些是同类查询,同类查询的SQL语句不会重复去编译而增加额外的开销。
select * from tbl where id=10;
select * from tbl where id=20;
将它们参数化后,结果如下:
select * from tbl where id=?;
通俗地讲,这里的"?"就是一个变量,任何满足这个语句类型的值都可以传递到这个变量中。
所以,对参数化进行一个通俗的定义:对于那些参数相同、参数值不同的SQL语句,使用问号"?"去替换参数值,替换后返回的语句就是参数化的结果。
ProxySQL也支持参数化。当前端发送SQL语句到达ProxySQL后,ProxySQL会将其参数化并分类。例如,下面是ProxySQL统计的参数化语句。
+----+----------+------------+-------------------------------------------------------------+
| hg | sum_time | count_star | digest_text |
+----+----------+------------+-------------------------------------------------------------+
| 2 | 14520738 | 50041 | SELECT c FROM sbtest1 WHERE id=? |
| 1 | 3142041 | 5001 | COMMIT |
| 1 | 2270931 | 5001 | SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ?+? ORDER BY c |
| 1 | 2021320 | 5003 | SELECT c FROM sbtest1 WHERE id BETWEEN ? AND ?+? |
| 1 | 1768748 | 5001 | UPDATE sbtest1 SET k=k+? WHERE id=? |
| 1 | 1697175 | 5003 | SELECT SUM(K) FROM sbtest1 WHERE id BETWEEN ? AND ?+? |
| 1 | 1346791 | 5001 | UPDATE sbtest1 SET c=? WHERE id=? |
| 1 | 1263259 | 5001 | DELETE FROM sbtest1 WHERE id=? |
| 1 | 1191760 | 5001 | INSERT INTO sbtest1 (id, k, c, pad) VALUES (?, ?, ?, ?) |
| 1 | 875343 | 5005 | BEGIN |
+----+----------+------------+-------------------------------------------------------------+
ProxySQL的mysql_query_rules表中有三个字段,能基于参数化后的SQL语句进行三种不同方式的匹配:
如果要进行SQL语句的重写(即正则替换),或者对参数值匹配,则必须采用match_pattern。如果可以,尽量采用digest匹配方式,因为它的效率更高。
在ProxySQL的stats库中,包含了几个统计表。
admin> show tables from stats;
+--------------------------------------+
| tables |
+--------------------------------------+
| global_variables |
| stats_memory_metrics |
| stats_mysql_commands_counters | <--已执行查询语句的统计信息
| stats_mysql_connection_pool | <--连接池信息
| stats_mysql_connection_pool_reset | <--重置连接池统计数据
| stats_mysql_global | <--全局统计数据
| stats_mysql_prepared_statements_info |
| stats_mysql_processlist | <--模拟show processlist的结果
| stats_mysql_query_digest | <--本文解释
| stats_mysql_query_digest_reset | <--本文解释
| stats_mysql_query_rules | <--本文解释
| stats_mysql_users | <--各mysql user前端和ProxySQL的连接数
| stats_proxysql_servers_checksums | <--ProxySQL集群相关
| stats_proxysql_servers_metrics | <--ProxySQL集群相关
| stats_proxysql_servers_status | <--ProxySQL集群相关
+--------------------------------------+
stats_mysql_query_digest: 这个表对于分析SQL语句至关重要,是分析语句性能、定制路由规则指标的最主要来源。
这个统计表中记录的就是每个参数化分类后的语句对应的统计数据,包括该类语句的执行次数、所花总时间、所花最短、最长时间,还包括语句的文本以及它的digest。
select hostgroup hg,count_star,FROM_UNIXTIME(first_seen),sum_time,digest,digest_text from stats_mysql_query_digest order by first_seen DESC limit 5;
以下是各个字段的意义:
注意,该表中的查询所花时长是指ProxySQL从接收到客户端查询开始,到ProxySQL准备向客户端发送查询结果的时长。因此,这些时间更像是客户端看到的发起、接收的时间间隔(尽管客户端到服务端数据传输也需要时间)。更精确一点,在执行查询之前,ProxySQL可能需要更改字符集或模式,可能当前后端不可用(当前后端执行语句失败)而找一个新的后端,可能因为所有连接都繁忙而需要等待空闲连接,这些都不应该计算到查询执行所花时间内。
其中hostgroup、digest、digest_text、count_start、{sum,min,max}_time
这几列最常用。
admin> select hostgroup hg,count_star,sum_time,digest,digest_text from stats_mysql_query_digest;
+----+------------+----------+--------------------+------------------------+
| hg | count_star | sum_time | digest | digest_text |
+----+------------+----------+--------------------+------------------------+
| 10 | 4 | 2412 | 0xADB885E1F3A7A5C2 | select * from test2.t1 |
| 10 | 6 | 4715 | 0x57497F236587B138 | select * from test1.t1 |
+----+------------+----------+--------------------+------------------------+
从中分析,两个语句都路由到了hostgroup=10的组中,第一个语句执行了4次,这4次总共花费了2412微秒(即2.4毫秒),第二个语句执行了6次,总花费4.7毫秒。还给出了这两个语句参数化后的digest值,以及参数化后的SQL文本。
stats_mysql_query_digest_reset: 这个表的表结构和stats_mysql_query_digest是完全一样的,只不过每次从这个表中检索数据(随便检索什么,哪怕where 1=0),都会重置stats_mysql_query_digest表中已统计的数据。
stats_mysql_query_rules
这个表只有两个字段:
digest匹配规则是对digest进行精确匹配。
例如,从stats_mysql_query_digest中获取两个对应的digest值。注意,现在它们的hostgroup_id=10。
admin> select hostgroup hg,count_star,sum_time,digest,digest_text from stats_mysql_query_digest;
+----+------------+----------+--------------------+------------------------+
| hg | count_star | sum_time | digest | digest_text |
+----+------------+----------+--------------------+------------------------+
| 10 | 4 | 2412 | 0xADB885E1F3A7A5C2 | select * from test2.t1 |
| 10 | 6 | 4715 | 0x57497F236587B138 | select * from test1.t1 |
+----+------------+----------+--------------------+------------------------+
插入两条匹配这两个digest的规则:
insert into mysql_query_rules(rule_id,active,digest,destination_hostgroup,apply) values(1,1,"0xADB885E1F3A7A5C2",20,1),(2,1,"0x57497F236587B138",10,1);
然后测试
mysql -uroot -pP@ssword1! -h127.0.0.1 -P6033 -e "select * from test1.t1;"
mysql -uroot -pP@ssword1! -h127.0.0.1 -P6033 -e "select * from test2.t1;"
再去查看规则的路由命中情况:
admin> select * from stats_mysql_query_rules;
+---------+------+
| rule_id | hits |
+---------+------+
| 1 | 1 |
| 2 | 1 |
+---------+------+
查看路由的目标:
admin> select hostgroup hg,count_star cs,digest,digest_text from stats_mysql_query_digest;
+----+----+--------------------+------------------------+
| hg | cs | digest | digest_text |
+----+----+--------------------+------------------------+
| 20 | 1 | 0xADB885E1F3A7A5C2 | select * from test2.t1 |
| 10 | 1 | 0x57497F236587B138 | select * from test1.t1 |
+----+----+--------------------+------------------------+
可见,基于digest的精确匹配规则已经生效。
match_digest是对digest做正则匹配,但注意match_pattern字段中给的规则不是hash值,而是SQL语句的文本匹配规则。
ProxySQL支持两种正则引擎:
1.PCRE
2.RE2
老版本中默认的正则引擎是RE2,现在默认的正则引擎是PCRE。可从变量mysql-query_processor_regex获知当前的正则引擎是RE2还是PCRE:
Admin> select @@mysql-query_processor_regex;
+-------------------------------+
| @@mysql-query_processor_regex |
+-------------------------------+
| 1 |
+-------------------------------+
其中1代表PCRE,2代表RE2。
在mysql_query_rules表中有一个字段re_modifiers,它用于定义正则引擎的修饰符,默认已经设置caseless,表示正则匹配时忽略大小写,所以select和SELECT都能匹配。此外,还可以设置global修饰符,表示匹配全局,而非匹配第一个,这个在重写SQL语句时有用。
(RE2引擎无法同时设置caseless和global,即使它们都设置了也不会生效。所以,将默认的正则引擎改为了PCRE)
在进行下面的实验之前,先把mysql_query_rules表清空,并将规则的统计数据也清空。
delete from mysql_query_rules;
select * from stats_mysql_query_digest_reset;
insert into mysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply) values (1,1,"^select .* test2.*",20,1),(2,1,"^select .* test1.*",10,1);
load mysql query rules to runtime;
save mysql query rules to disk;
然后分别执行:
mysql -uroot -pP@ssword1! -h127.0.0.1 -P6033 -e "select * from test1.t1;"
mysql -uroot -pP@ssword1! -h127.0.0.1 -P6033 -e "select * from test2.t1;"
查看规则匹配结果:
admin> select * from stats_mysql_query_rules;
+---------+------+
| rule_id | hits |
+---------+------+
| 1 | 1 |
| 2 | 1 |
+---------+------+
admin> select hostgroup hg,count_star cs,digest,digest_text dt from stats_mysql_query_digest;
+----+----+--------------------+------------------------+
| hg | cs | digest | dt |
+----+----+--------------------+------------------------+
| 10 | 1 | 0x57497F236587B138 | select * from test1.t1 |
| 20 | 1 | 0xADB885E1F3A7A5C2 | select * from test2.t1 |
+----+----+--------------------+------------------------+
显然,命中规则,且按照期望进行路由。
如果想对match_digest取反,即不被正则匹配的SQL语句才命中规则,则设置mysql_query_rules表中的字段negate_match_pattern=1。同样适用于下面的match_pattern匹配方式。
和match_digest的匹配方式类似,但match_pattern是基于原始SQL语句进行匹配的,包括参数值。有两种情况必须使用match_pattern:
如果想对match_pattern取反,即不被正则匹配的SQL语句才命中规则,则设置mysql_query_rules表中的字段negate_match_pattern=1。
例如:
## 清空规则以及规则的统计数据
delete from mysql_query_rules;
select * from stats_mysql_query_digest_reset where 1=0;
insert into mysql_query_rules(rule_id,active,match_pattern,destination_hostgroup,apply) values(1,1,"^select .* test2.*",20,1),(2,1,"^select .* test1.*",10,1);
load mysql query rules to runtime;
save mysql query rules to disk;
执行查询:
mysql -uroot -pP@ssword1! -h127.0.0.1 -P6033 -e "select * from test1.t1;"
mysql -uroot -pP@ssword1! -h127.0.0.1 -P6033 -e "select * from test2.t1;"
然后查看匹配结果:
admin> select * from stats_mysql_query_rules;
+---------+------+
| rule_id | hits |
+---------+------+
| 1 | 1 |
| 2 | 1 |
+---------+------+
admin> select hostgroup hg,count_star cs,digest,digest_text dt from stats_mysql_query_digest;
+----+----+--------------------+------------------------+
| hg | cs | digest | dt |
+----+----+--------------------+------------------------+
| 20 | 1 | 0xADB885E1F3A7A5C2 | select * from test2.t1 |
| 10 | 1 | 0x57497F236587B138 | select * from test1.t1 |
+----+----+--------------------+------------------------+
再来看看匹配参数值(虽然几乎不会这样做)。这里要测试的语句如下:
mysql -uroot -p123456 -h127.0.0.1 -P6033 -e "select * from test1.t1 where name like 'malong%';"
mysql -uroot -p123456 -h127.0.0.1 -P6033 -e "select * from test2.t1 where name like 'xiaofang%';"
现在插入两条规则,对参数"malong%"和"xiaofang"进行匹配。
## 清空规则以及规则的统计数据
delete from mysql_query_rules;
select * from stats_mysql_query_digest_reset where 1=0;
insert into mysql_query_rules(rule_id,active,match_pattern,destination_hostgroup,apply) values(1,1,"malong",20,1),(2,1,"xiaofang",10,1);
load mysql query rules to runtime;
save mysql query rules to disk;
执行上面的两个查询语句,然后查看匹配结果:
admin> select * from stats_mysql_query_rules;
+---------+------+
| rule_id | hits |
+---------+------+
| 1 | 1 |
| 2 | 1 |
+---------+------+
admin> select hostgroup hg,count_star cs,digest,digest_text dt from stats_mysql_query_digest;
+----+----+--------------------+------------------------------------------+
| hg | cs | digest | dt |
+----+----+--------------------+------------------------------------------+
| 20 | 1 | 0x0C624EDC186F0217 | select * from test1.t1 where name like ? |
| 10 | 1 | 0xA38442E236D915A7 | select * from test2.t1 where name like ? |
+----+----+--------------------+------------------------------------------+
已按预期进行路由。
一个极简单却大有用处的读、写分离功能:将默认路由组设置为写组,然后再插入下面两个select语句的规则。
# 10为写组,20为读组
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);
但需要注意的是,这样的规则只适用于小环境下的读写分离,对于稍复杂的环境,需要对不同语句进行开销分析,对于开销大的语句需要制定专门的路由规则。
情况说明:已经知道具体的sql语句,并且要求这些sql语句走写所在组的查询(MySQL主从同步,数据时效性,个别sql语句查询走主库),且要求使用digest匹配规则
比如SQL语句: (表前面一定要加数据库名)
SELECT
id,
open_id,
nick_name,
plate_no,
serial_no,
park_name,
park_code,
arm_code,
arm_name,
ip_addr,
park_log_id,
car_enter_id,
enter_time,
out_time,
charge_total_fee,
charge_due,
charge_unpaid,
charge_paid,
charge_duration,
charge_prepaid,
order_status_desc,
order_status,
version,
total_amt,
point_amt,
ecoupon_amt,
mer_disc_amt,
coupon_amt,
coupon_id,
platform_flag,
discount_param,
discount_type,
out_trade_no,
third_party_out_trade_no,
order_id,
pay_time,
total_disc_amt,
mer_id,
attach,
cust_id,
card_no,
bank_disc_amt,
payment_amt,
pay_type,
source_type,
bill_status,
car_type,
invoice_bill,
red_type,
red_invoice_bill,
coupon_use_flag,
create_time,
update_time,
reject_flag,
reject_amt,
medium_type,
real_charge,
out_coin_charge,
weixin_charge,
charge,
pay_channel,
transaction_id,
remark,
verify_status,
merchant_bill_id,
order_source,
sort_id,
pay_entrance,
trade_time,
third_attach,
payment_scenario,
goods_name,
report_status
FROM
park_cloud_db.park_order_info
where car_enter_id='335401514161017511215104' and order_status ='2' order by create_time desc;
具体操作办法:
1.获取该sql语句对应的digest值
打开一个系统命令行窗口,执行下面的语句进行查询 (走的是proxysql配置的账号信息:user_proxysql,proxysql的端口号:6033)
mysql -uuser_proxysql -p -h192.168.0.176 -P6033 -e "
SELECT
id,
open_id,
nick_name,
plate_no,
serial_no,
park_name,
park_code,
arm_code,
arm_name,
ip_addr,
park_log_id,
car_enter_id,
enter_time,
out_time,
charge_total_fee,
charge_due,
charge_unpaid,
charge_paid,
charge_duration,
charge_prepaid,
order_status_desc,
order_status,
version,
total_amt,
point_amt,
ecoupon_amt,
mer_disc_amt,
coupon_amt,
coupon_id,
platform_flag,
discount_param,
discount_type,
out_trade_no,
third_party_out_trade_no,
order_id,
pay_time,
total_disc_amt,
mer_id,
attach,
cust_id,
card_no,
bank_disc_amt,
payment_amt,
pay_type,
source_type,
bill_status,
car_type,
invoice_bill,
red_type,
red_invoice_bill,
coupon_use_flag,
create_time,
update_time,
reject_flag,
reject_amt,
medium_type,
real_charge,
out_coin_charge,
weixin_charge,
charge,
pay_channel,
transaction_id,
remark,
verify_status,
merchant_bill_id,
order_source,
sort_id,
pay_entrance,
trade_time,
third_attach,
payment_scenario,
goods_name,
report_status
FROM
park_cloud_db.park_order_info
where car_enter_id='335401514161017511215104' and order_status ='2' order by create_time desc;"
执行后输入密码进行查询。
然后在新开一个命令行窗口,登录proxysql管理窗口:
/usr/bin/mysql -uadmin -padmin -h 127.0.0.1 -P 6032
执行如下语句进行匹配查询,获取上一步sql执行后获取的digest值
select hostgroup hg,count_star,FROM_UNIXTIME(first_seen),sum_time,digest,digest_text from stats_mysql_query_digest order by first_seen DESC limit 5;
1000是读组,根据sql语句后面的条件进行区分不同的sql语句,从而获取到digest值
2.根据上一步获取的digest值添加近匹配规则中
注意:mysql_query_rules表中的rule_id是有作用的,规则是按照rule_id的顺序进行处理的。
# 查询规则,考虑把这个添加到第一个上,若已有1则可以进行修改,然后再添加
select rule_id,active,digest,match_digest,match_pattern,destination_hostgroup,apply from mysql_query_rules;
insert into mysql_query_rules(rule_id,active,digest,destination_hostgroup,apply) values (1,1,"0x719FE5F71C276C5A",100,1);
# 同时添加多个的写法
# insert into mysql_query_rules(rule_id,active,digest,destination_hostgroup,apply) values (80,1,"0x719FE5F71C276C5A",100,1),(81,1,"0xE8F7E13FF68362A4",100,1),(82,1,"0x2FCB0C6F738267C2",100,1),(83,1,"0x78891A8A66FA6E08",100,1),(84,1,"0x599C0D8234D854A0",100,1),(85,1,"0xC94F20CFAA43B207",100,1),(86,1,"0x9F0BEBB697D7C0BA",100,1),(87,1,"0x70A940C6A02F4FB5",100,1);
load mysql query rules to runtime;
save mysql query rules to disk;
# 查询规则
select rule_id,active,digest,match_digest,match_pattern,destination_hostgroup,apply from mysql_query_rules;
手机扫一扫
移动阅读更方便
你可能感兴趣的文章