分布式 | DBLE 如何实现 Prepared Statement 协议

原创: 鲍凤奇mysql


本文摘要:git

DBLE 是一款企业级的开源分布式中间件,江湖人送外号 “MyCat Plus”。Prepared Statement 协议是 MySQL 5.1 版本新加入的功能。MyCat 从1.6版本实现了 Prepared Statement 协议,但 MyCat 存在一些至今仍未修复的Bug。github

本文将从两名 DBLE 用户提交的Bug开始提及,详细解读 DBLE 是如何实现 Prepared Statement 协议的sql

 

事发当天 

2019年4月12日下午,GitHub获得举报,两名 DBLE 用户各发现了一个极为凶残的Bug。DBLE 社区片儿警立刻赶到案发现场进行取证并对Bug们开始展开调查。举报信息以下:数据库

BugOnehttps://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缓存

BugTwohttps://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 版本开始为服务器端预处理语句提供支持。此支持利用了高效的客户端/服务器二进制协议。将带有占位符的预准备语句用于参数值具备如下好处:

  1. 每次执行时解析语句的开销更少。一般,数据库应用程序处理大量几乎相同的语句,只更改子句中的文字或变量值,例如 WHERE 查询和删除,SET 更新和 VALUES 插入。

  2. 防止 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 的坑。

相关文章
相关标签/搜索