Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

错误的SQL请求导致服务的前端连接超时 #2498

Closed
wu-ernest opened this issue Jun 6, 2020 · 3 comments · Fixed by #2517
Closed

错误的SQL请求导致服务的前端连接超时 #2498

wu-ernest opened this issue Jun 6, 2020 · 3 comments · Fixed by #2517

Comments

@wu-ernest
Copy link
Contributor

 在某业务系统的生产环境中,执行跑批作业时报出服务的前端连接超时异常,经多次重启业务系统后重试,问题依旧。
 根据分析其日志得出:在报出异常前,都有一个时间段未打印任何日志,当等待到这个时间段之后就立刻打印出异常信息,如下贴出具有代表性的日志信息:

- | 2019-12-18 20:37:08.839 | SQLINFO | hlwjr | batch.A3170278E11F44B281CDF48DC69D52A3_-process-thread | cn.sunline.ltts.dao.mybatis.MyBatisSqlProcessor>447 | [1146][ipw]SQL[WaSql.selLoanOfpacdIsNull][895000][0-2147483647]RowCount=796 | 
- | 2019-12-18 20:37:08.840 | SQLINFO | hlwjr | batch.A3170278E11F44B281CDF48DC69D52A3_-process-thread | cn.sunline.ltts.dao.mybatis.MyBatisSqlProcessor>399 | [x]SQL[WaSql.selLoanOfpacdIsNull][962000][0-2147483647] | 
- | 2019-12-18 20:59:03.868 | SQLINFO | hlwjr | batch.A3170278E11F44B281CDF48DC69D52A3_-process-thread | cn.sunline.ltts.dao.mybatis.MyBatisSqlProcessor>447 | [1315028][ipw]SQL[WaSql.selLoanOfpacdIsNull][962000][0-2147483647]RowCount=0 | 
- | 2019-12-18 20:59:03.868 | ERROR | hlwjr | batch.A3170278E11F44B281CDF48DC69D52A3_-process-thread | cn.sunline.ltts.dao.db.LTTSDBConnectionManager>617 | 验证数据库连接是否可用时, 执行语句失败! | 
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure

The last packet successfully received from the server was 1,315,313 milliseconds ago. The last packet sent successfully to the server was 0 milliseconds ago.

 经过与服务的各项参数比对,发现多次重试连接的这个等待时间始终稳定在 [idletimeout, idletimeout+processorCheckPeriod] 时间区间内,那么说明是请求超时导致此异常,即问题原因在于为什么会超时?
 经 DBA 分析 MySQL 后表示:并无慢 SQL 产生。那么该阻塞就只能是存在于 Mycat 内部。然后通过top命令发现服务的 CPU 使用率高达 100%,且居高不下,再通过使用jstack PID命令查看服务进程内部的线程栈轨迹,发现有如下栈轨迹信息:

"$_NIOREACTOR-45-RW" prio=10 tid=0x00007fcd3469e000 nid=0x4d08 runnable [0x00007fcc9a5e4000]
   java.lang.Thread.State: RUNNABLE
        at io.mycat.route.impl.DruidMycatRouteStrategy.analyseDescrSQL(DruidMycatRouteStrategy.java:422)
        at io.mycat.route.impl.DruidMycatRouteStrategy.routeSystemInfo(DruidMycatRouteStrategy.java:378)
        at io.mycat.route.impl.AbstractRouteStrategy.route(AbstractRouteStrategy.java:79)
        at io.mycat.server.ServerConnection.routeEndExecuteSQL(ServerConnection.java:277)
        at io.mycat.server.ServerConnection.execute(ServerConnection.java:231)
        at io.mycat.server.ServerQueryHandler.query(ServerQueryHandler.java:154)
        at io.mycat.net.FrontendConnection.query(FrontendConnection.java:317)
        at io.mycat.net.handler.FrontendCommandHandler.handler(FrontendCommandHandler.java:79)
        at io.mycat.net.FrontendConnection.rawHandle(FrontendConnection.java:478)
        at io.mycat.net.FrontendConnection.handle(FrontendConnection.java:460)
        at io.mycat.net.AbstractConnection.onReadData(AbstractConnection.java:321)
        at io.mycat.net.NIOSocketWR.asynRead(NIOSocketWR.java:201)
        at io.mycat.net.AbstractConnection.asynRead(AbstractConnection.java:273)
        at io.mycat.net.NIOReactor$RW.run(NIOReactor.java:106)
        at java.lang.Thread.run(Thread.java:745)

 线程栈轨迹表明:名为 $_NIOREACTOR-45-RW 的线程一直运行在 DruidMycatRouteStrategy#analyseDescrSQL 方法内。分析该方法的源码后发现:该方法的循环体内部的条件分支语句没有形成完整的闭环。经与业务开发人员多次沟通和多次调试源码后发现有如下 SQL 语句进入视线。

elect * from table_name where name = '张三' order by id desc limit 10

 业务开发人员在客户端手动编写 SQL 时,因疏忽把select少写了一个字母,未检查语法就直接发送给 mycat 服务,而此语句并未在 mycat 内部被解析成的 select 类型,而是解析成了 describe 类型,流转到了 analyseDescrSQL 方法中,但却因为该语句不是标准的 describe 类型,导致无法满足if条件,代码又没给出默认的行为,所以形成死循环,一直占用该线程。而能够满足以上问题的错误 sql 应具有以下特征:

  • 必须单独存在 desc (不区分大小写 )关键词。
  • desc 关键词前须存在其他非合法起始关键字的字符。
  • desc 关键字后存在其他非分号和空格的字符。

解决思路

  1. 为 DruidMycatRouteStrategy#analyseDescrSQL 方法内的循环添加完整的闭环。
  2. 在浅解析过程中,当根据首个可见起始词匹配语句类型失败时,是否还有必要继续匹配下一个词?
@funnyAnt
Copy link
Collaborator

确实存在这个问题,我们之前遇到过,我来提交一个PR

@wu-ernest
Copy link
Contributor Author

@funnyAntDruidMycatRouteStrategy#analyseDescrSQL 之所以没有完整的闭环,是因为原作者认为正常的desc类型语句不会再有else分支,但要满足该意愿的前提是:该方法只处理desc类型的语句。
 现实现象是其他类型的语句也进入了专门处理desc类型语句的方法中,也就是说我列举的错误SQL为什么会被识别成desc类型语句才是关键。
 以上纯属个人愚见,欢迎批评、指正。

@wu-ernest
Copy link
Contributor Author

我自己尝试修复了一下源码:

  1. 补全DESC类型分支内的循环闭环,这一步没什么好解释的。
  2. 在浅解析中,都是根据SQL的起始可见词来匹配SQL类型,当首词匹配失败后,就直接判定为OTHER类型,不再继续匹配后续单词,避免进入别的语句类型分支中,因为无法保证其他分支中是否也存在无闭环的循环。

 首个可见词(Hint不在其中)匹配失败就基本可以认为该SQL语法错误,之所以不立即抛出,是因为错误的组合有很多,无法做到穷举,只需将其“保驾护航”至“Druid Parse”中,由解析器去抛出具体的错误信息,即相信他人的力量。
 测试时,可在任意正确的SQL前添加一个不是合法SQL起始词的字符(串),如下所示:

delet select * from table_name

 此语句在修复前会被匹配成select类型,进入select类型分支中,虽然最终还是会被解析器判断出语法错误,但是进入了select类型分支就是不正确的,那么假设是其他错误类型呢?比如进入了 showDDL等类型分支中,都能保证能够顺利到达深解析吗?即使能够抵达,那也仅能证明功能上没问题,但从设计层面来说能接受吗?

此种修复方式又回到了 1.4-release 版本,我不是很清楚 1.5-GA 如此修复的初衷,从给出注释来看,应该是出于对 Hint 的考量。此种修复方式还需要大量的测试。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants