软件系统研究文档
系统名称:MyCAT问题总结
(仅供内部使用)
项目组:DBMW
时 间:2016-02-22
版本修订历史
日期 | 版本 | 说明/备注 | 作 者 | 审核人1 | 审核人2 |
---|---|---|---|---|---|
2016.02.22 | V1.0 | 章颖、赵闪 | |||
2018.01.29 | V1.1 | 文中标红为补充内容 | 何清华 | ||
1 介绍
mycat是一个开源的分布式数据库系统,是一个实现了MySQL协议的服务器,前端用户可以把它看作是一个数据库代理,用MySQL客户端工具和命令行访问,而其后端可以用MySQL原生协议与多个MySQL服务器通信,也可以用JDBC协议与大多数主流数据库服务器通信,其核心功能是分表分库,即将一个大表水平分割为N个小表,存储在后端MySQL服务器里或者其他数据库里。目前生产环境使用的版本主要为0.9、1.4以及1.6。
具体原理介绍及项目源码可查看GitHub WiKi:
https://github.com/MyCATApache/Mycat-Server/wiki
2 SQL限制
MyCAT作为公司使用的数据库中间件,由于其对一些SQL语句的解析存在一定的限制,而由于MyCAT1.0采用的解析器是fdbparser,而1.3之后的解析器统一改为druidparser,现将一些主要的DML语句在使用不同版本的基础上进行测试;
首先需要注意的一点是MyCAT1.0不支持大部分的DDL语句,因此在1.0版本上一般禁止使用DDL语句,一些建库和建表操作都是通过配置文件实现的,现在主要对一些DML语句的限制进行说明;
在MyCAT1.0版本大部分的简单DML语句是支持的,现对一些SELECT语句进行说明,因为很多对SELECT语句的限制其实也非常适用于INSERT、UPDATE和DELETE操作。
注意:不管是哪个版本的MYCAT,都存在一个限制,即分片字段不能更新。
2.1 DML语句的执行情况解析
2.1.1 DISTINCT操作
针对版本1.0,在单库下执行SELECT DISTINCT操作不会出现任何问题,但在多库情况下,这个操作将会出现问题,因为fdbparser对此操作不会进行解析,只是把此语句转发到每个具体的分库,这样就会导致一个非常严重的后果,每个库返回的DISTINCT结果都出现在了结果集中,导致MYCAT呈现的DISTINCT结果并不DISTINCT,因此在分库的情况下应避免含有此关键字的SQL语句,而在应用层进行拼接;
2.1.2 WHERE操作
(1)一般的where操作MyCAT都会支持,但带有分片字段的where会出现一定的问题,where后带有分片字段后,将会到该分片字段所在的库去执行相应的操作(MyCAT只对应单库的情况下没有任何影响),这就导致所有的查询结果其实都是在分片字段所在库的返回结果,因此类似在分片字段上执行“!=”、“>=”以及where后面带有“or”等操作将会导致返回的结果集并非真正的结果集,而只是一个单库的结果集,值得一提的是, where后带有普通字段的操作却不会出现任何问题。例如:SELECT * from t_day_flow WHERE mobile >=’1024’;返回的结果集则是‘1024’分片后所对应的结果;SELECT from t_day_flow WHERE useflow=10 or appid like ‘%21cn%’的结果则是正常的;(mobile为分片字段);
在MyCAT1.4版本中,类似SELECT from t_day_flow WHERE mobile >=’1024’的bug问题得到了修复,但SELECT from t_day_flow WHERE mobile=’13632342036’ or appid like ‘%21cn%’ and useflow >10这种类型的问题还是会存在,指定了库就会进入到它所在的库进行操作,而or操作之后的也是在指定库中进行的。
(2)在MyCat1.4版本中,如果数据库实例中不存在某一字段,但在mycat中将该字段作为条件进行操作时,语句还能执行成功;造成的结果是mycat内存耗尽,最后被卡死。具体原因是所执行的sql得不到结果则一直执行下去,造成数据拥堵,当数据拥堵达到一定量时,内存被耗尽,mycat被卡死。具体看下面一个例子,select from invoice_info where phoneNum=’111111111’ and email=’xjqang@163.com’,
因为invoice_info表中根本不存在字段 email,因此在wrapper.log 日志中报出内存用尽的错误:
同时在mycat.log日志中报出错误:
WARN [$_NIOREACTOR-27-RW] (SingleNodeHandler.java:189) -execute sql err : errno:1054 Unknown column ‘email’ in ‘where clause’ con:MySQLConnection
2.1.3 limit m,n操作
分页查询实际上返回的结果集是m+n行的结果,而不是对m+n筛选后得到的n行结果;这也是fdbparser解析器的一个bug问题;
2.1.4 replace into操作
该语法存在矛盾性(该语句的含义是:数据库中不存在就insert,存在就replace,即更新。根据拆分表原则:insert 时必须带拆分字段,而更新时绝不能更新拆分字段,即绝不能带拆分字段,这两种逻辑是矛盾的),所以MyCAT禁止这类操作。
2.1.5 子查询语句
对于子查询,MyCAT是支持的,但在分表分库场景下,MyCAT并不能跨库查询,导致查询的结果实际上是每个单库里的查询结果的汇总,因此如果涉及到的子查询会在不同的分库里,有可能会导致最终的结果并不是准确的结果;如果能保证涉及的子查询不会进行跨库操作,或者只对应一个单库,这种子查询是完全没有问题的。
2.1.6 GROUP BY和ORDER BY操作
这类语句在MyCAT中是支持的,但涉及到多个分片库时,解析器会将SQL语句路由到每个分片库上去执行,最后返回的结果实际上是每个分库结果的汇总,同样不会进行跨库操作,不过ORDER BY的结果是正常的,GROUP BY的结果却不正常。
2.1.7 INSERT操作
(1)进行insert操作时,必须要带有分片字段,同时不支持进行批量插入,同时insert … on duplicate key update也是不支持的,因为对分片字段是无法进行更新的;同时对SELECT的一些条件限制也同样适用于insert;
2.1.8 UPDATE操作
2.2 DML语句操作规范
2.2.1 MyCAT1.0版本下部分DML语句操作规范
单库 | 多库 | |
---|---|---|
SELECT DISTINCT … | 允许 | 禁止 |
SELECT … where “分片字段”=‘’and … | 允许 | 允许 |
SELECT … where “分片字段”=‘’or … | 允许 | 禁止 |
SELECT … where “分片字段”!=‘’and … | 允许 | 禁止 |
SELECT … where “分片字段”like‘’and … | 允许 | 禁止 |
SELECT…from WHERE “分片字段” >=’1024’ | 允许 | 禁止 |
SELECT…from WHERE “分片字段” in… | 允许 | 允许 |
SELECT…from WHERE “分片字段” between…and… | 允许 | 允许 |
SELECT…from WHERE “非分片字段” 条件判断 | 允许 | 允许 |
order by …limit m | 允许 | 允许 |
order by …limit m,n | 禁止 | 禁止 |
join查询 | 允许 | 禁止 |
SELECT CONCAT from… | 允许 | 允许 |
分片字段更新 | 禁止 | 禁止 |
批量插入 | 禁止 | 禁止 |
insert into…on duplicate key | 禁止 | 禁止 |
replace into | 禁止 | 禁止 |
2.2.2 MyCAT1.4版本部分DML语句操作规范
相对MyCAT1.0来讲,MyCAT1.4采用了druidparser解析器,支持的SQL语句更多,但由于许多SQL语句进行了跨库操作,其得到的结果是不准确的,只相当于将单库查询的结果合并在了一起,这样的结果和跨库查询的结果是不同的,因此,在正式的生产环境中,应避免这些操作。
单库 | 多库 | |
---|---|---|
SELECT DISTINCT … | 允许 | 禁止 |
SELECT … where “分片字段”=‘’and … | 允许 | 允许 |
SELECT … where “分片字段”=‘’or … | 允许 | 禁止 |
SELECT … where “分片字段”!=‘’and … | 允许 | 允许 |
SELECT … where “分片字段”like‘’and … | 允许 | 禁止 |
SELECT…from WHERE “分片字段” >=’1024’ | 允许 | 允许 |
SELECT…from WHERE “分片字段” in… | 允许 | 允许 |
SELECT…from WHERE “分片字段” between…and… | 允许 | 允许 |
SELECT…from WHERE “非分片字段” 条件判断 | 允许 | 允许 |
order by …limit m | 允许 | 允许 |
order by …limit m,n | 允许 | 允许 |
join查询 | 允许 | 禁止 |
SELECT CONCAT from… | 允许 | 允许 |
分片字段更新 | 禁止 | 禁止 |
批量插入 | 禁止 | 禁止 |
insert into…on duplicate key | 禁止 | 禁止 |
replace into | 禁止 | 禁止 |
值得注意的是,我们在1.4版本进行测试后发现,对于where和limit m,n进行测试后发现,最新的druidparser解析器时能避免1.0的一些bug的,对于GROUP BY实现了跨库查询结果的优化,但其优化并不彻底,所以类似GROUP BY操作最好避免。
因此,即使是最新的1.4版本也只是部分避免了一些DML语句的bug,但却没有实现跨库查询,只是将一些在单库中查询的结果进行了一定程度的优化,对复杂的SQL语句的支持还是不够,因此,在多个分库情况下时,应避免一些复杂SQL运行。
3 连接数控制
(1)对于后端数据库连接数控制主要由schema.xml文件中的dataHost中的maxCon参数控制,当前端连接数较多时,需要适当增大maxCon参数,同时注意,所有负载均衡的maxCon之和应小于mysql设置的max-connection参数。
(2)0.9版本如果发生过多的idle timeout空闲超时连接(可以通过统计日志,命令为:cat /data/logs/mycat/mycat.log |grep ‘idle timeout’|wc –l的超时总数来判断),如:单台mycat超过400个空闲连接被回收,说明mycat前端连接过多,超负载的情况下可能会导致某些表的逻辑丢失(非物理删除),应用层无法查询而报错,此时需要进行reload @@config;热切更新配置或者重启mycat方能恢复表正常配置的数量。
4 对事务的支持
MyCAT中的事务分为两种情况:
(1) SQL不跨分片:事务中的SQL在单个节点执行;
(2) SQL跨分片:事务中的SQL在多个节点执行;
其中,在单个datanode上执行的事务和标准的数据库事务模式相同,要么提交要么回滚,不存在中间检查点的操作;而对于跨多个datanode的事务,MyCAT执行一种弱XA事务模式,即首先事务内的SQL会在各自的分片上执行并返回状态码,若某个分片上的返回码是error,则MyCAT认为事务失败,事务回滚,MyCAT收到回滚指令后,依次回滚事务中涉及的所有分片;若事务中的所有SQL的执行都返回成功的返回码,则应用程序提交事务,MyCAT会同时向事务中涉及的节点发送提交事务的指令。
如图所示,这是在单个节点执行事务操作时的结果;通过set autocommit=0;开启事务,值得注意的是,我们即使开启了事务,但show variables like ‘%autocommit%’;得到的值确和我们开启事务不相符(这时因为show variables操作默认都会去分主1执行的缘故);但这些都没有影响我们的操作,事务最后能够成功提交,而且,在事务未提交阶段,事务还能保证隔离性操作;
如上图所示,当对多个节点进行操作时,只要有一个节点commit失败,这个事务中其他节点也会失败,最终实现整个事务的回滚;
只有当所有节点都提交成功时,跨节点事务才实现提交成功,这就是弱XA的原理。
值得注意的是MyCAT1.3版本之前都不支持begin操作,1.4之后开始支持begin transaction操作。
XA事务成功的过程如下
Xa事务的完成分两个阶段一是prepare阶段,另一个是commit阶段,只有两个阶段都成功了,xa事务才能成功;
而对于XA事务失败的情况,则是指多个资源管理器至少有一个没有prepare,这时,会导致其他节点rollback,从而导致整个事务失败;
5 读写分离
由schema.xml中的balance参数控制
0(BALANCE_NONE):不开启读写分离机制。
1(BALANCE_ALL_BACK):全部的readHost与非当前写writeHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,M2,S1,S2都参与select语句的负载均衡。
2(BALANCE_ALL):所有的readHost与writeHost都参与select语句的负载均衡,也就是说,当系统的写操作压力不大的情况下,所有主机都可以承担负载均衡
3(BALANCE_ALL_READ):全部的readHost参与select语句的负载均衡,简单的说,当双主双从模式(M1->S1,M2->S2,并且M1与 M2互为主备),正常情况下,只有S1,S2都参与select语句的负载均衡。
上述配置情况下,当写挂了,读不可用
上述配置情况下,当写挂了,可以继续使用。
MyCAT1.0 | MyCAT1.4 | |
---|---|---|
是否支持读写分离 | √ | √ |
Balance的值 | 0,1,2 | 0,1,2,3 |
是否支持主从延时监控 | × | √ |
表1:Mycat1.0与Mycat1.4读写分离功能对比情况
判断服务器是否宕机是通过mycat的心跳检测机制实现的,如果心跳检测3次失败,则判定服务不可用,心跳检测间隔时间可以在server.xml文件中设置:
单位:毫秒
6 在线重新加载配置文件
(1)对于0.9低版本的cobar,重启方式为先kill相关进程,再执行./ startup.sh,并注意删除bin目录下生成的异常中断说明文件hs_err_pid13212.log,core.9666等。
(2)Mycat1.0使用管理端口的reload @@config功能会断开后端数据库连接,而且该功能不稳定,会出现reload不生效的情况。
(3)Mycat1.4使用管理端口的reload @@config可以reload基本配置,如果需要reload所有配置,需要使用reload @@config_all命令,该功能还是会断开后端数据库连接,使用时会影响线上正在连接的服务。
因此,对于在线服务,不推荐使用reload @@config功能;当上一层还有haproxy服务时,在mycat进行重启前,可以先进性热切换,先将需要重启的mycat注释掉,再对mycat进行重启,检查无误后,再在haproxy配置中取消注释。
7 日志
(1)org.apache.log4j.DailyRollingFileAppender采用这种每天归档日志的形式时,修改log4j.xml中的日志配置文件不会即时生效
(2)org.apache.log4j.ConsoleAppender采用这种归档日志方式,修改log4j.xml中的日志配置文件可以即时生效,修改log4j.xml时,console会load这个./conf/log4j.xml文件
mycat1.0的日志文件有console.log和mycat.log
mycat1.4的日志文件有wrapper.log和mycat.log
8 字符集问题
1.4版本如果存在非utf8字符集连接如:latin1,按debug日志的经验,当连接复用时,可能会发生字符集的继承,即上一个连接为latin1字符集,当下一次写入复用此连接时,原utf8的字符集会被篡改为latin1,若此时存在中文,会导致写入的乱码。