mysql polardb 集群结果集重复

表结构

有一主一从的 polardb mysql 数据库集群,表结构如下:

CREATE TABLE `t` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(100) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO t(id, name) VALUES(1, 'tom');
INSERT INTO t(id, name) VALUES(2, 'jerry');

测试

jdbcUrl 使用集群的地址

JDK 20 执行如下代码(jdbcUrl 中使用的集群的地址):

public class Test {

    public static void main(String[] args) throws SQLException {
        String jdbcUrl = "jdbc:mysql://集群的地址:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Hongkong&allowMultiQueries=true";
        String username = "xxx";
        String password = "xxx";

        String sqlScript = """
                -- 查询
                select id from t;
                update t set id = id + 2;
                """;
        try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)){
            try (Statement statement = connection.createStatement();) {
                boolean hasResults = statement.execute(sqlScript);
                while (hasResults || statement.getUpdateCount() != -1) {
                    if (hasResults) {
                        // 查询语句
                        ResultSet resultSet = statement.getResultSet();
                        System.out.println(resultSet);
                        while (resultSet.next()) {
                            int id = resultSet.getInt("id");
                            System.out.println(id);
                        }
                    } else {
                        // 其他语句
                        int updateCount = statement.getUpdateCount();
                        System.out.println("受影响行数:" + updateCount);
                    }

                    System.out.println("-----------------------------------------------");
                    hasResults = statement.getMoreResults();
                }
            }
        }
    }
}

执行上述代码中

执行上述代码可能有两种结果:

1、抛异常

猜测是请求被转发到只读节点导致的?

mysql polardb 集群结果集重复_第1张图片

2、脚本中只有两条 SQL 语句,但结果却有三个,且前两个结果集是重复的。

mysql polardb 集群结果集重复_第2张图片

在 SQL 洞察中发现,两条语句被只读节点和主节点都执行了,根据执行时间可以看出是只读节点先成功执行了 select,然后执行 update 失败了(因为只读节点不允许修改),然后是主节点成功执行了 selectupdate

个人猜测上图中

第 1 个结果是只读节点 select 的结果集

第 2 结果是主节点 select 的结果集

第 3 个结果是主节点 update 的受影响行数

三个结果被数据库代理合并后返回给了客户端。

mysql polardb 集群结果集重复_第3张图片

mysql polardb 集群结果集重复_第4张图片

如果只读节点和主节点都执行了相同的 SQL,就跟文档什么是读写分离 (aliyun.com) 中描述的请求转发逻辑相悖了。代码中的 sqlScript 显然是一个 Multi Statements,应该只发往主节点,sqlScript 中的 update 是 DML,也应该只发往主节点。

mysql polardb 集群结果集重复_第5张图片

jdbcUrl 使用主节点的地址

结果是正常的

String jdbcUrl = "jdbc:mysql://主节点地址:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Hongkong&allowMultiQueries=true";

mysql polardb 集群结果集重复_第6张图片

jdbcUrl 使用集群的地址,但加 /*FORCE_MASTER*/

结果是正常的

String jdbcUrl = "jdbc:mysql://集群的地址:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Hongkong&allowMultiQueries=true";
String sqlScript = """
        /*FORCE_MASTER*/-- 查询
        select id from t;
        update t set id = id + 2;
        """;

mysql polardb 集群结果集重复_第7张图片

jdbcUrl 使用集群的地址,但将 sqlScript 中第一句注释删掉

结果是正常的

String jdbcUrl = "jdbc:mysql://集群的地址:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Hongkong&allowMultiQueries=true";
String sqlScript = """
        select id from t;
        update t set id = id + 2;
        """;

mysql polardb 集群结果集重复_第8张图片

jdbcUrl 使用集群的地址,但将 sqlScript 中的 update 换成 select

结果是正常的

String jdbcUrl = "jdbc:mysql://集群的地址:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Hongkong&allowMultiQueries=true";
String sqlScript = """
        -- 查询
        select id from t;
        select id from t;
        """;

mysql polardb 集群结果集重复_第9张图片

jdbcUrl 使用集群的地址,但将 sqlScript 中的注释放在 selectupdate 语句中间

结果不正常,又是 3 个结果

String jdbcUrl = "jdbc:mysql://集群的地址:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Hongkong&allowMultiQueries=true";
String sqlScript = """
        select id from t;
        -- 查询
        update t set id = id + 2;
        """;

mysql polardb 集群结果集重复_第10张图片

jdbcUrl 使用集群的地址,但将 sqlScript 中的注释放在 selectupdate 语句的后面

结果是正常的,最后的 受影响行数:0-- 查询 的结果

String jdbcUrl = "jdbc:mysql://集群的地址:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Hongkong&allowMultiQueries=true";
String sqlScript = """
        select id from t;
        update t set id = id + 2;
        -- 查询
        """;

mysql polardb 集群结果集重复_第11张图片

问题

上述测试中暴露出 polardb mysql 的几个问题:

  1. SQL 脚本为什么被同时转发到了主节点和只读节点(一个 sql 只会被转发到一个节点才对),最后还进行了结果集合并?
  2. 为什么只有在 SQL 脚本中有在开头或中间的注释,且 select 后面有 DML 语句时才能触发这个 bug?
  3. DML 被转发到了只读节点(文档中写的是只发往主节点)
  4. Multi Statements 被转发到了只读节点(文档中写的是只发往主节点)

临时解决方案

  1. 继续使用集群地址,但将只读节点从负载均衡中剔除
  2. 继续使用集群地址,所有 SQL 前面手动加上 /*FORCE_MASTER*/
  3. 直接使用主节点地址

阿里云反馈

阿里云反馈说确实是 polardb 的 bug,会在后续版本中修复

你可能感兴趣的:(MySQL,mysql,数据库)