作者:鲍凤奇
本文摘要:
DBLE 是一款企业级的开源分布式中间件,江湖人送外号 “MyCat Plus”。Prepared Statement 协议是 MySQL 5.1 版本新加入的功能。MyCat 从1.6版本实现了 Prepared Statement 协议,但 MyCat 存在一些至今仍未修复的Bug。
本文将从两名 DBLE 用户提交的Bug开始说起,详细解读 DBLE 是如何实现 Prepared Statement 协议的。
事发当天 ![]()
2019年4月12日下午,GitHub得到举报,两名 DBLE 用户各发现了一个极为凶残的Bug。DBLE 社区片儿警马上赶到案发现场进行取证并对Bug们开始展开调查。举报信息如下:
BugOne(https://github.com/actiontech/dble/issues/1122)
error message: Dble has an error message 'unknown pStmtId when executing' when the client set useServerPrepStmts=true #1122 dble version: dble-9.9.9.9-884fc6b612d64cc22101226536f8fd1d24580857-20190221182143
BugTwo(https://github.com/actiontech/dble/issues/1124) error message: use PreparedStatement with JDBC and MySQL J Connector will get wrong result when useCursorFetch=true #1124 dble version: dble 2.18.10.5 and before (not yet test on later version)
信息分析 ![]()
两个Bug的错误信息虽然不同,但相同点是都涉及到了 Prepared Statement 协议,也就是MySQL 的预处理功能。想要完整了解两个Bug背后的隐情,我们先要回顾一下 MySQL Prepared Statement 协议以及 DBLE 是如何实现 Prepared Statement 协议的。
MySQL Prepared Statement 协议
先看看 MySQL 官方的介绍:
MySQL 5.1 版本开始为服务器端预处理语句提供支持。此支持利用了高效的客户端/服务器二进制协议。将带有占位符的预准备语句用于参数值具有以下好处:
-
每次执行时解析语句的开销更少。通常,数据库应用程序处理大量几乎相同的语句,只更改子句中的文字或变量值,例如 WHERE 查询和删除,SET 更新和 VALUES 插入。
-
防止 SQL 注入攻击。参数值可以包含未转义的 SQL 引号和分隔符。
MySQL Prepared Statement 的二进制协议交互过程如图:


注意:
1. COMSTMTSENDLONGDATA 必须在 COMSTMTEXECUTE 前发送。 2. COMSTMTFETCH 必须在 COMSTMTEXECUTE 后发送。 3. COMSTMTRESET 是专为重置 COMSTMTSENDLONGDATA ,不能单独使用。
通过一图一表,我们对 MySQL Prepared Statement 协议交互中的各种行为做了一个回顾。下面让我们看看 DBLE 是如何实现的。
DBLE 对 prepare statement 协议的实现
对于客户端的预编译 请求,DBLE缓存了 SQL 并模拟返回报文,对于服务端改用 COM_QUERY 命令执行,并将各节点返回数据整合转换格式返回客户端。

说明:
1. 在 COMSTMTPREPARE 阶段,DBLE 此时接收的 SQL 不完整,不能确定下发节点,但MySQL Prepared Statement 协议要求此处返回一个 response 报文,因此 DBLE 会伪装COMSTMTPREPAREOK 报文返回。若有些 MySQL 驱动(如 JDBC )想从 response 报文中获取信息,这些信息会不准确。
2. 在 COMSTMTEXECUTE 阶段,DBLE 会根据客户端传输的参数将预编译SQL替换为具体的SQL,并使用 COMQUERY 命令下发至后端节点,因为 COMQUERY 不再使用二进制协议传输,因此DBLE需要对后端返回的数据进行转换后再返回客户端。
3. DBLE 不支持 COMSTMT_FETCH 命令。
好了,当我们了解完 MySQL 和 DBLE 对 Prepared Statement 协议实现过程后,我们再回过头来看那个两个Bug到底是怎么来的。
#1122 Bug分析
经过分析,在同一连接中,当发起 COMSTMTCLOSE 销毁当前 prepare statement 后,紧接着又创建一个 prepare statement。这两个操作是在 DBLE 中是异步进行,存在线程安全的问题。知道问题的根源之后,解决方案是 DBLE 将两个操作变成同步操作,避免线程安全的问题。
#1124 Bug分析
使用 JDBC 时,在URL中设置 useCursorFetch=true 的参数,希望开启 MySQL Server Side 游标功能。但是要使用 MySQL Server Side 游标需要满足下面条件:
-
必须是SELECT语句
-
设置了fetchSize > 0
-
设置了useCursorFetch = true
-
数据集类型为ResultSet.TYPEFORWARDONLY
-
数据集并发设置为ResultSet.CONCURREADONLY
-
Server versions 5.0.5 or newer
这是因为 JDBC 在代码层面做了如下限制:
// we only create cursor-backed result sets if
// a) The query is a SELECT
// b) The server supports it
// c) We know it is forward-only (note this doesn't preclude updatable result sets)
// d) The user has set a fetch size
if (this.resultFields != null && this.useCursorFetch && getResultSetType() == ResultSet.TYPE_FORWARD_ONLY
&& getResultSetConcurrency() == ResultSet.CONCUR_READ_ONLY && getFetchSize() > 0) {
packet.writeByte(OPEN_CURSOR_FLAG);
} else {
packet.writeByte((byte) 0); // placeholder for flags
}
那如何开启游标功能呢?以下是 JDBC 部分功能在进行预处理是开启游标的示例:
public static void testPrepareStmt() {
Connection conn = null;
PreparedStatement stmt = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:8066/poc?useCursorFetch=true", "root", "123456");
stmt = conn.prepareStatement("select long_col_1, long_col_2 from problemTable where to_days(create_time) <= to_days(now()) and id = ?");
stmt.setFetchSize(10);
stmt.setInt(1, 2);
ResultSet rs = stmt.executeQuery();
int count = 0;
while(rs.next()){
++count;
System.out.println("########### row " + count + " ###################");
System.out.println("long_col_1 : " + rs.getString(1));
System.out.println("long_col_2 : " + rs.getString(2));
System.out.println();
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException ce) {
ce.printStackTrace();
} finally {
try {
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
尾声 ![]()
#1122 和#1124 两个Bug从被定位到抓获认罪只用了5天,随后 DBLE 社区发布 DBLE 2.19.03.0 版本,将真相大白于天下。 我们始终相信:真相只有一个!至此DBLE又踩平了一个 MyCat 的坑。
本文围绕DBLE展开,它是企业级开源分布式中间件。从两名用户提交的Bug切入,回顾了MySQL Prepared Statement协议,介绍了DBLE对该协议的实现方式。分析了两个Bug产生的原因并给出解决方案,最后DBLE社区发布新版本解决问题,还踩平了MyCat的一个坑。
239

被折叠的 条评论
为什么被折叠?



