其他分享
首页 > 其他分享> > 分布式 | DBLE 如何实现 Prepared Statement 协议

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

作者:互联网

作者:鲍凤奇

 

本文摘要:

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

  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 游标需要满足下面条件:

这是因为 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 的坑。

 

推荐阅读:《技术分享 | MyCat的坑如何在分布式中间件DBLE上改善(内含视频链接)》

标签:协议,Prepared,DBLE,Statement,MySQL,Bug
来源: https://blog.csdn.net/ActionTech/article/details/95479270