连接数据库失败,难道MySQL 5.7客户端与8.0数据库不兼容?
- 前言
- 1. MySQL 8.0数据库连接失败
- 2. 连接失败与SSL相关
- 3. 为什么5.5/5.6连接MySQL 8.0是正常的,偏偏5.7客户端连接失败
- 3.1 MySQL 5.7客户端的变化
- 3.2 MySQL 8.0数据库端的变化
- 4. 为什么5.7客户端连接8.0.28失败,连接8.0.28之前版本数据库正常呢
- 5. 解决方案
前言
线上业务数据库升级到MySQL 8.0.28之后,业务侧使用MySQL 5.5版本的mysql_api连接数据库正常,但是我们管理端使用旧的MySQL 5.7客户端连接数据库却是失败的。难道MySQL 5.7的客户端与8.0的数据库之间不兼容? 这个问题可就比较严重了,可能成为数据库升级路上的拦路虎。一下就勾起了吹水老王极大的兴致,我们一起来分析一下。
1. MySQL 8.0数据库连接失败
我们线上将一套数据库从MySQL 5.7.26升级到MySQL 8.0.28之后,业务侧有两种程序语言,c 程序通过MYSQL_API连接数据库,java程序通过JDBC连接数据库,升级之后没有任何异常;但是,我们DB管理端的监控通过原有的MySQL 5.7客户端连接数据库失败。
JDBC与这个问题不相关,我们暂且抛去不谈;现在问题就是,MYSQL_API使用的MySQL 5.5客户端可以正常连接8.0.28数据库,MySQL 5.7客户端却不能。
为了明确具体问题,我们先做个测试,分别使用MySQL 5.5/5.6/5.7的客户端连接MySQL 8.0数据库。
代码语言:javascript复制# 使用MySQL 5.7的客户端连接MySQL 8.0.28时报错
# /usr/local/mysql/bin/mysql --version
/usr/local/mysql/bin/mysql Ver 14.14 Distrib 5.7.26, for el7 (x86_64) using EditLine wrapper
# /usr/local/mysql/bin/mysql -h9.135.1.1 -P3307 -utest -ptest
mysql: [Warning] Using a password on the command line interface can be insecure.
ERROR 2026 (HY000): SSL connection error: unknown error number
代码语言:javascript复制# 使用MySQL 5.5的客户端连接MySQL 8.0.28,连接成功。
# /bin/mysql --version
/bin/mysql.bak Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (x86_64) using readline 5.1
# /bin/mysql -h9.135.1.1 -P3307 -utest -ptest
Welcome to the MariaDB monitor. Commands end with ; or g.
Your MySQL connection id is 12
Server version: 8.0.28 MySQL Community Server - GPL
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
MySQL [(none)]> select version();
最终,得到以下结果:
- 使用MySQL 5.7的客户端或者mysql_api连接MySQL 8.0.28以上版本的数据库时失败;
- 而使用MySQL 5.5和5.6版本的客户端连接MySQL 8.0.28以上版本数据库却是正常的;
- 使用MySQL 5.5/5.6/5.7版本的客户端连接MySQL 8.0.28以下版本(如MySQL 8.0.23)都是正常的。
这就不得不让人怀疑,难道5.7版本的MySQL客户端与8.0版本数据库是否存在兼容性问题?
2. 连接失败与SSL相关
首先,报错信息为SSL connection error,可以猜想使用MySQL 5.7的客户端连接MySQL 8.0数据库时的报错与SSL相关。
我们来回忆一下数据库连接基础知识。 MySQL数据库的连接方式通常有两种,一种是通过TCP/IP连接,即通过数据库的端口进行通信,另外一种方式是通过本地SOCKET连接,即在客户端在数据库本机通过本地的socket文件连接数据库。SOCKET连接比TCP/IP连接方式要快,但是只适用于客户端和服务端在同一台unix主机的场景。
我们可以验证一下,MySQL 5.7的客户端使用socket方式连接MySQL 8.0数据库是否正常。
代码语言:javascript复制# 这里我们使用root@localhost用户连接数据库
# /usr/local/mysql/bin/mysql --version
/usr/local/mysql/bin/mysql Ver 14.14 Distrib 5.7.26, for el7 (x86_64) using EditLine wrapper
# /usr/local/mysql/bin/mysql -uroot -proot -S /data/MySQLData_3307/data/mysql.sock
Server version:8.0.28 MySQL Community Server - GPL
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
mysql> select version();
-----------
| version() |
-----------
| 8.0.28 |
-----------
1 row in set (0.00 sec)
可以看到,使用MySQL 5.7的客户端通过本地socket的方式可以正常连接MySQL 8.0.28以上版本的数据库。那么也就证明了5.7版本的客户端与8.0版本的数据库本身其实不存在兼容性问题。MySQL 5.7客户端连接失败只是与TCP的SSL加密连接相关。
3. 为什么5.5/5.6连接MySQL 8.0是正常的,偏偏5.7客户端连接失败
3.1 MySQL 5.7客户端的变化
MySQL 5.7客户端连接8.0数据库失败与SSL加密连接相关。实际上在我们的生产环境,MySQL客户端或者mysql_api连接数据库通常是不使用SSL的。既然MySQL 5.7的客户端与MySQL 5.5/5.6客户端在SSL方面的行为有差异,我们就来看看两者的默认行为有什么不同。
使用mysql --help查看MySQL客户端帮助中与SSL相关的配置,可以看到:MySQL 5.5客户端默认关闭了SSL,而在MySQL 5.7的客户端中默认是开启SSL的,并且多了一个ssl-mode参数配置SSL连接模式。
代码语言:javascript复制# MySQL 5.5 客户端默认关闭SSL
# /bin/mysql.bak --help | grep ssl
--ssl Enable SSL for connection (automatically enabled with
ssl FALSE
代码语言:javascript复制# MySQL 5.7 客户端默认开启SSL
# /usr/local/mysql/bin/mysql --help | grep ssl
--ssl-mode=name SSL connection mode.
--ssl Deprecated. Use --ssl-mode instead.
(Defaults to on; use --skip-ssl to disable.)
ssl TRUE
既然SSL配置在MySQL 5.7和之前版本的客户端中存在差异,那么我们可以再验证一下,MySQL 5.7在显式关闭SSL的情况下是否能够连接MySQL 8.0成功。
代码语言:javascript复制# /usr/local/mysql/bin/mysql --skip-ssl -h9.135.1.1 -P3307 -utest -ptest
Server version: 8.0.28 MySQL Community Server - GPL
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
mysql> select version();
-----------
| version() |
-----------
| 8.0.28 |
-----------
1 row in set (0.00 sec)
果然,在显式设置–skip-ssl禁用SSL的情况下,MySQL 5.7的客户端连接MySQL 8.0也是成功的。
至于MySQL 5.7的客户端SSL配置的变更,我们可以通过官方文档 配置MySQL使用加密连接 来确认一下。
By default, MySQL client programs attempt to establish an encrypted connection if the server supports encrypted connections, with further control available through the --ssl-mode
option:
- In the absence of an
--ssl-mode
option, clients attempt to connect using encryption, falling back to an unencrypted connection if an encrypted connection cannot be established. This is also the behavior with an explicit--ssl-mode=PREFERRED
option.
根据MySQL 5.7的文档描述,可以看出,MySQL 5.7的客户端如果没有显式配置–ssl-mode参数,并且数据库端支持SSL加密连接的话,那么客户端会尝试创建SSL加密连接;如果SSL加密连接失败则退而求其次使用不加密连接。
我们注意到,MySQL 5.7的客户端创建SSL加密连接有两个必要条件:1)客户端没有显式禁用SSL;2)数据库端支持SSL加密连接。
3.2 MySQL 8.0数据库端的变化
第一点已经很明确了,MySQL 5.7的客户端默认是开启SSL的。我们来看看第二点,数据库端支持SSL加密连接。
通过对比,我们发现MySQL 8.0 与 MySQL 5.7数据库端的默认配置也存在差异。MySQL 5.7的数据库中默认是禁用SSL的,而在MySQL 8.0中默认是开启SSL的。
代码语言:javascript复制# MySQL 5.7数据库中默认是禁用SSL的
mysql> show global variables like '%ssl%';
--------------- ----------
| Variable_name | Value |
--------------- ----------
| have_openssl | DISABLED |
| have_ssl | DISABLED |
代码语言:javascript复制# MySQL 8.0数据库中默认是启用SSL的
mysql> show global variables like '%ssl%';
------------------------------------- -----------------
| Variable_name | Value |
------------------------------------- -----------------
| have_openssl | YES |
| have_ssl | YES |
小结:MySQL 8.0数据库初始化时默认开启了SSL,之前版本的数据库默认是禁用SSL的。同时,MySQL 5.7之前版本的客户端默认禁用SSL,而MySQL 5.7版本的客户端在数据库端支持SSL的情况下会尝试创建SSL加密连接。所以,MySQL 5.5/5.6版本客户端不使用加密连接MySQL 8.0数据库,连接是成功的;而MySQL 5.7版本客户端使用SSL加密连接MySQL 8.0数据库,连接失败。
4. 为什么5.7客户端连接8.0.28失败,连接8.0.28之前版本数据库正常呢
这是由于在MySQL 8.0.28版本开始,数据库层默认的tls版本为TLSv1.2,并且不再支持旧版本的TLSv1和TLSv1.1连接;而在之前版本中的默认tls版本为TLSv1。
参考文档:Changes in MySQL 8.0.28
From MySQL 8.0.28, client programs, including MySQL Shell, that support a --tls-version
option for specifying TLS protocols for connections to the MySQL server cannot make a TLS/SSL connection with the protocol set to TLSv1 or TLSv1.1. If a client attempts to connect using these protocols, for TCP connections, the connection fails, and an error is returned to the client.
验证一下,对比MySQL 8.0.28数据库和MySQL 5.7客户端支持的TLS_VERSION。
代码语言:javascript复制# MySQL 8.0.28数据库中默认tls_version
mysql> show global variables like '%tls%';
------------------------ -----------------
| Variable_name | Value |
------------------------ -----------------
| admin_tls_version | TLSv1.2,TLSv1.3 |
| tls_version | TLSv1.2,TLSv1.3 |
------------------------ -----------------
代码语言:javascript复制# MySQL 5.7客户端支持的tls_version
# /usr/local/mysql/bin/mysql --help | grep tls
--tls-version=name TLS version to use, permitted values are: TLSv1, TLSv1.1
5. 解决方案
经过老王一番大胆设想小心求证,问题原因已经显而易见。综上,数据库端MySQL 8.0初始化时默认启用了SSL,并且从MySQL 8.0.28版本开始将默认TLS版本升级为TLSV1.2,并且不再支持旧版本的TLS;而MySQL 5.7版本的客户端包括mysql_api默认会尝试与数据库端以TLSV1或TLSV1.1版本的SSL建立加密连接,TLS版本在数据库层和客户端之间的不兼容导致MySQL 5.7的客户端无法连接MySQL 8.0.28以上数据库。
MySQL 5.7的客户端与8.0数据库本身并不存在兼容性问题,只是SSL版本不兼容而已。解决方案也就很简单了,要么在数据库层禁用SSL,要么在低版本客户端中禁用SSL即可。