【GaussDB】使用MySQL客户端连接到GaussDB的M-Compatibility数据库
概述
GaussDB 从内核 505.1 版本(2024年4月发布)起,新增了一种新的兼容模式 M-Compatibility,这是一种整体上比之前的B模式对MySQL兼容性更高的模式。这种M模式在建库时会自动使用模板库 templatem。
之所以需要再新增一种M模式,我猜测是因为GaussDB发现有些MySQL兼容性已经完全违背了内核的SQL及存储逻辑,无法通过参数调整进行兼容,为了不影响原有B模式的用户,必须新增一种兼容模式来隔离之前的逻辑。
新的这个M兼容模式,可以用MySQL客户端进行连接,但是实际使用过程中,可能会有一些坑在文档中并未详细说明,本文就来介绍一下"使用MySQL客户端连接到GaussDB的M-Compatibility数据库"的正确姿势。
测试环境:本文使用的GaussDB内核版本为 506.0 SPC0100
建库操作
指定M兼容模式建库:
CREATE DATABASE mysql DBCOMPATIBILITY='M';
修改参数
参考文档
添加客户端IP到数据库白名单
将客户端IP添加到数据库白名单,具体操作请联系管理员处理。
设置兼容性参数
请参见《管理员指南》中的"配置运行参数"章节,配置相关GUC参数并重启数据库。
参数配置:
- 配置协议监听的TCP端口号,设置GUC参数
plat_compat_server_port
为合理端口 - 如果使用到RSA,需要设置GUC参数
plat_compat_allow_public_key_retrieval
为on
- 将GUC参数
m_format_dev_version
设置为"s2"
执行命令:
gs_guc reload -I all -N all -c "plat_compat_server_port=3306"
gs_guc reload -I all -N all -c "plat_compat_allow_public_key_retrieval=on"
gs_guc reload -I all -N all -c "m_format_dev_version='s2'"
创建连接用户(可选)
CREATE USER mysql_user PASSWORD 'Gaussdb@123' SYSADMIN MONADMIN;
连接测试
初始连接问题
使用MySQL客户端连接时出现报错:
错误信息:
plat_compat_b_conn_handshake, handshake error.
Java代码连接测试
尝试手写一段Java代码进行连接:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class MySQLCreateTable {
public static void main(String[] args) {
String url = "jdbc:mysql://192.168.163.119:3306/mysql";
String user = "mysql_user";
String password = "Gaussdb@123";
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement()) {
String sql = "CREATE TABLE IF NOT EXISTS users2 (" +
"id INT PRIMARY KEY AUTO_INCREMENT," +
"name VARCHAR(100) NOT NULL," +
"email VARCHAR(100) NOT NULL UNIQUE" +
")";
stmt.executeUpdate(sql);
System.out.println("Table created successfully.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果: 也报错了
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:827)
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:447)
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:237)
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:199)
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:681)
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:229)
at MySQLCreateTable.main(MySQLCreateTable.java:11)
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151)
at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:167)
at com.mysql.cj.protocol.a.NativeProtocol.negotiateSSLConnection(NativeProtocol.java:340)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.negotiateSSLConnection(NativeAuthenticationProvider.java:777)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.proceedHandshakeWithPluggableAuthentication(NativeAuthenticationProvider.java:486)
at com.mysql.cj.protocol.a.NativeAuthenticationProvider.connect(NativeAuthenticationProvider.java:202)
at com.mysql.cj.protocol.a.NativeProtocol.connect(NativeProtocol.java:1348)
at com.mysql.cj.NativeSession.connect(NativeSession.java:163)
at com.mysql.cj.jdbc.ConnectionImpl.connectOneTryOnly(ConnectionImpl.java:947)
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:817)
... 6 more
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
at java.base/sun.security.ssl.HandshakeContext.<init>(HandshakeContext.java:172)
at java.base/sun.security.ssl.ClientHandshakeContext.<init>(ClientHandshakeContext.java:103)
at java.base/sun.security.ssl.TransportContext.kickstart(TransportContext.java:240)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:448)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:426)
at com.mysql.cj.protocol.ExportControlled.performTlsHandshake(ExportControlled.java:316)
at com.mysql.cj.protocol.StandardSocketFactory.performTlsHandshake(StandardSocketFactory.java:188)
at com.mysql.cj.protocol.a.NativeSocketConnection.performTlsHandshake(NativeSocketConnection.java:99)
at com.mysql.cj.protocol.a.NativeProtocol.negotiateSSLConnection(NativeProtocol.java:331)
... 13 more
解决方案
查看官方文档《基于MySQL JDBC开发-开发步骤-连接数据库-以常规方式连接》,发现文档中的连接串是:
String sourceURL = "jdbc:mysql://$ip:$port/database?useSSL=false&allowPublicKeyRetrieval=true";
多了两个参数,逐步测试:
步骤1:添加 useSSL=false
先加一个 useSSL=false
参数测试。
结果: 出现了和mogeaver一样的报错。
步骤2:添加 allowPublicKeyRetrieval=true
再把另一个参数也加上去 allowPublicKeyRetrieval=true
。
结果: ✅ 执行成功了!
验证其他客户端
在mogeaver里也加上这个参数 allowPublicKeyRetrieval=true
。
结果: ✅ 也连接成功了!
说明: 但是mogeaver里的useSSL为true,之所以没有报错,是因为mogeaver里还自动加上了
enabledTLSProtocols=TLSv1,TLSv1.1,TLSv1.2
连接参数总结
根据测试,自己写的Java代码,连接时必须加上:
- 方案一:
useSSL=false&allowPublicKeyRetrieval=true
- 方案二:
useSSL=true&enabledTLSProtocols=TLSv1,TLSv1.1,TLSv1.2
常见问题及解决
如果是普通的测试人员,测到这里,就会认为连接测试这一项已经测试通过了,但是换几个不同的环境里的GaussDB去测,还可能会出现这样的报错:
问题:Access denied for user
错误信息:
Access denied for user
注意: 如果是用户名密码错误,也会报这个错,所以先要用gsql测试一下用户名密码是否正确,确认用户名密码没有写错后,那么问题就可能发生在密码加密方式上。
原因分析
在官方文档[《配置客户端接入认证》](https://support.huawei.com/carrier/docview?nid=DOC1101458028&path=PBI1-253383977/PBI1-23710112/PBI1-23710137/PBI1-256197501&partNo=k00e)这个章节里写着:
外部客户端使用M-Compatibility兼容协议远程连接M-Compatibility数据库时,只支持 sha256认证方式
于是去检查gs_hba.conf,发现无论是配置成md5还是sha256,Access denied for user
这个问题都依然存在。
然后继续翻官方文档,发现在《设置密码安全策略》这个章节里写着:
使用M-Compatibility兼容协议连接数据库时,仅支持sha256加密模式,即
password_encryption_type = 2
也就是说,必须确保数据库用户的密码存储时,必须是仅存储sha256密文,不能支持其他方式。
解决步骤
- 修改密码加密参数:
gs_guc reload -I all -N all -c "password_encryption_type=2"
- 重新设置用户密码:
ALTER USER mysql_user PASSWORD 'mysql_123';
到此,使用MySQL客户端连接GaussDB就顺利了...吗?
进阶配置:RSA加密支持
搜索官方文档,发现还有这么一个章节:《使用RSA加密登录密码》
RSA加密登录配置要求:
- 设置
plat_compat_allow_public_key_retrieval
参数值为on
- 设置
require_ssl
参数值为off
- 设置 hba type 参数值为
host
或hostnossl
也就是说,如果 require_ssl
为 on
,那么本文的配置方式也会连接不上去了。
连接方式总结
综合分散在文档各个地方的信息来看,连接应该是可以用RSA或者不用RSA:
服务端配置 | 客户端配置 | 说明 |
---|---|---|
plat_compat_allow_public_key_retrieval=off | useSSL=true&enabledTLSProtocols=TLSv1,TLSv1.1,TLSv1.2 | 禁用RSA,使用SSL |
plat_compat_allow_public_key_retrieval=on | useSSL=false&allowPublicKeyRetrieval=true | 使用RSA,不使用SSL |
plat_compat_allow_public_key_retrieval=on | useSSL=true&enabledTLSProtocols=TLSv1,TLSv1.1,TLSv1.2 | 允许使用RSA,但仍然使用SSL |
实测也的确如此。
补充说明
另外,数据库里还有个参数 plat_compat_default_database
,可以配置MySQL协议连过来需要连接哪个库。
- 在没有设置这个参数时: JDBC连接串里的dbname就真的是GaussDB里面的数据库名称
- 如果设置了这个参数: JDBC连接串里的dbname就是
plat_compat_default_database
指定的库下的schema名称
重要声明
⚠️ 版本说明:本文这里的这些内容,仅为在 506.0 SPC0100 集中式版本上测试的结果,不代表新版本的表现(目前已知有些东西在新版本改了)。