最近在帮团队迁移数据库到MySQL 8.0时,遇到了一个让人头疼的问题:明明用户名密码都正确,但就是连不上数据库,报错"ERROR 1045 (28000): Access denied for user"。这种情况在从MySQL 5.7升级到8.0时特别常见,根本原因在于MySQL 8.0引入了一个全新的默认认证插件caching_sha2_password。
这个新插件比老版的mysql_native_password更安全,但也带来了一些兼容性问题。我刚开始排查时也是一头雾水,后来才发现很多第三方工具和客户端库还没有完全适配这个新认证方式。比如用Python的MySQL Connector连接时,如果不做特殊配置,就会直接报错。
传统的mysql_native_password认证方式已经陪伴我们很多年了。它的工作流程很简单:
这种方式虽然简单,但存在安全隐患。SHA1算法已经被证明不够安全,而且整个认证过程容易被中间人攻击。
MySQL 8.0默认启用的caching_sha2_password做了重大改进:
实测下来,新插件的性能开销几乎可以忽略不计,但安全性提升非常明显。不过这也意味着所有客户端都必须支持新的认证协议,否则就会出现ERROR 1045。
很多团队在使用ODBC连接MySQL 8.0时会遇到问题。我最近就帮一个数据分析团队解决了这个问题。他们报的错误正是:
code复制ERROR 1045 (28000): Access denied for user 'ODBC'@'localhost' (using password: NO)
解决方法其实很简单:
完整的ODBC连接字符串示例:
code复制DRIVER={MySQL ODBC 8.0 Unicode Driver};SERVER=localhost;DATABASE=test;USER=odbc_user;PASSWORD=your_password;ALLOW PUBLIC KEY RETRIEVAL=TRUE;
对于Java应用,我推荐使用最新的MySQL Connector/J 8.0+。在连接配置中需要明确指定使用新认证插件:
properties复制jdbc:mysql://localhost:3306/db?useSSL=true&allowPublicKeyRetrieval=true
Python应用则需要确保mysql-connector-python版本在8.0.11以上:
python复制import mysql.connector
config = {
'user': 'scott',
'password': 'tiger',
'host': '127.0.0.1',
'database': 'employees',
'auth_plugin':'mysql_native_password' # 如果需要回退到旧认证
}
cnx = mysql.connector.connect(**config)
在MySQL 8.0中创建用户时,建议明确指定认证插件。比如要创建一个既能用新插件又能兼容老客户端的管理员用户:
sql复制CREATE USER 'admin'@'%' IDENTIFIED WITH caching_sha2_password BY 'secure_password';
GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION;
如果某些老应用实在无法适配,可以创建专门使用旧认证方式的用户:
sql复制CREATE USER 'legacy_app'@'192.168.1.%' IDENTIFIED WITH mysql_native_password BY 'old_password';
GRANT SELECT, INSERT, UPDATE ON app_db.* TO 'legacy_app'@'192.168.1.%';
当遇到权限问题时,我常用的排查命令组合是:
sql复制-- 查看用户是否存在
SELECT user, host FROM mysql.user WHERE user LIKE '%odbc%';
-- 检查认证方式和密码状态
SELECT user, host, plugin, authentication_string FROM mysql.user;
-- 查看具体权限
SHOW GRANTS FOR 'odbc'@'localhost';
如果发现权限配置有问题,记得修改后一定要执行FLUSH PRIVILEGES使更改生效。
新认证插件强烈建议启用SSL加密。检查当前SSL状态的命令:
sql复制SHOW VARIABLES LIKE '%ssl%';
要强制某用户必须使用SSL连接:
sql复制ALTER USER 'finance_user'@'%' REQUIRE SSL;
caching_sha2_password默认会缓存认证信息,但有时可能需要调整缓存策略:
sql复制-- 查看认证缓存状态
SHOW STATUS LIKE 'Caching_sha2_password%';
-- 调整缓存大小(单位:KB)
SET GLOBAL caching_sha2_password_auto_generate_rsa_keys_size = 2048;
去年我们为一家电商平台做MySQL 8.0升级时,设计了一套渐进式迁移方案:
sql复制CREATE USER 'app_user'@'10.0.%' IDENTIFIED WITH caching_sha2_password BY 'new_pass'
AND IDENTIFIED WITH mysql_native_password BY 'old_pass';
分批次更新应用连接配置,先测试新认证方式
监控连接错误日志,针对性解决问题:
bash复制# 查看MySQL错误日志
tail -f /var/log/mysql/error.log | grep -i 'caching_sha2_password'
这套方案最终实现了零停机的平滑迁移,期间业务完全没有感知。
遇到认证问题时,我建议按照这个流程排查:
sql复制SELECT VERSION();
sql复制SHOW VARIABLES LIKE 'default_authentication_plugin';
sql复制SELECT user, host, plugin,
IF(authentication_string='', 'NO', 'YES') as has_password
FROM mysql.user;
bash复制mysql -u testuser -p -h 127.0.0.1
bash复制sudo getsebool -a | grep mysql
sudo iptables -L -n | grep 3306
bash复制sudo grep -i 'access denied' /var/log/mysql/error.log
对于需要特别安全要求的场景,可以考虑这些配置:
sql复制SET GLOBAL default_authentication_plugin='mysql_native_password';
sql复制CREATE USER 'dev'@'192.168.1.%' IDENTIFIED WITH caching_sha2_password BY 'dev_pass' REQUIRE SSL;
CREATE USER 'prod'@'10.0.%' IDENTIFIED WITH caching_sha2_password BY 'prod_pass' REQUIRE X509;
sql复制INSTALL COMPONENT 'file://component_connection_control';
SET GLOBAL connection_control_failed_connections_threshold = 3;
SET GLOBAL connection_control_min_connection_delay = 1000;
针对不同编程语言,这是经过验证的适配方案:
PHP:
php复制$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
$options = [
PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => false,
PDO::MYSQL_ATTR_SSL_KEY => '/path/to/client-key.pem',
PDO::MYSQL_ATTR_SSL_CERT => '/path/to/client-cert.pem',
PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca-cert.pem'
];
$pdo = new PDO($dsn, 'username', 'password', $options);
Node.js:
javascript复制const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'test',
ssl: {
rejectUnauthorized: true,
ca: fs.readFileSync('/path/to/ca-cert.pem')
},
authPlugins: {
caching_sha2_password: () => require('mysql2/lib/auth/sha2_password')
}
});
C#/.NET:
csharp复制var builder = new MySqlConnectionStringBuilder
{
Server = "localhost",
Database = "test",
UserID = "user",
Password = "password",
SslMode = MySqlSslMode.Required,
AllowPublicKeyRetrieval = true
};
using var connection = new MySqlConnection(builder.ConnectionString);
connection.Open();
如果经过以上所有尝试还是无法解决连接问题,可以考虑这个终极方案:
sql复制SELECT CONCAT('SHOW GRANTS FOR \'', user, '\'@\'', host, '\';')
FROM mysql.user INTO OUTFILE '/tmp/grants.sql';
sql复制ALTER USER 'problem_user'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
ini复制# 在my.cnf中添加
[mysqld]
default_authentication_plugin=mysql_native_password
逐步排查客户端兼容性问题
等所有客户端都升级适配后,再切换回新认证方式
这个方案虽然不够优雅,但在生产环境紧急情况下确实能快速解决问题。我在处理某金融机构的核心系统升级时就用了这个方法,为他们争取到了足够的客户端升级时间。