有一主一从的 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');
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、抛异常
猜测是请求被转发到只读节点导致的?
2、脚本中只有两条 SQL 语句,但结果却有三个,且前两个结果集是重复的。
在 SQL 洞察中发现,两条语句被只读节点和主节点都执行了,根据执行时间可以看出是只读节点先成功执行了 select
,然后执行 update
失败了(因为只读节点不允许修改),然后是主节点成功执行了 select
和 update
。
个人猜测上图中
第 1 个结果是只读节点 select
的结果集
第 2 结果是主节点 select
的结果集
第 3 个结果是主节点 update
的受影响行数
三个结果被数据库代理合并后返回给了客户端。
如果只读节点和主节点都执行了相同的 SQL,就跟文档什么是读写分离 (aliyun.com) 中描述的请求转发逻辑相悖了。代码中的 sqlScript
显然是一个 Multi Statements,应该只发往主节点,sqlScript
中的 update
是 DML,也应该只发往主节点。
结果是正常的
String jdbcUrl = "jdbc:mysql://主节点地址:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Hongkong&allowMultiQueries=true";
/*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;
""";
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;
""";
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;
""";
sqlScript
中的注释放在 select
和 update
语句中间结果不正常,又是 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;
""";
sqlScript
中的注释放在 select
和 update
语句的后面结果是正常的,最后的 受影响行数: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;
-- 查询
""";
上述测试中暴露出 polardb mysql 的几个问题:
select
后面有 DML 语句时才能触发这个 bug?/*FORCE_MASTER*/
阿里云反馈说确实是 polardb 的 bug,会在后续版本中修复