MySQL 8.0 报ERROR 1045 (28000): Access denied for user 'ODBC'@'localhost' (using password: NO),很多人在第一次见到它的时候都会懵一下——尤其是那一行'ODBC'@'localhost',看起来像是 MySQL 里存在一个叫 ODBC 的用户,于是下意识地去翻用户表、去创建账号,结果折腾半天毫无进展。我最早踩这个坑是在 Windows 服务器上给一个老系统配 ODBC 数据源,应用一启动就报这个错,当时我也以为是权限问题,找了一圈才发现问题根本不在 MySQL 这一侧,而是客户端在连接时压根没把用户名传过来。
这篇文章我打算把这个报错彻底讲透。从错误信息的读法、ODBC 用户从哪来,到不同场景下的修复方案,再到 MySQL 8.0 认证插件引发的一系列连锁问题,最后附上我实际排查中积累的速查表和避坑心得。无论你是刚装完 MySQL 8.0 的新手,还是被老应用 ODBC 连接折腾的运维,照着文章顺序走一遍,基本都能把问题定位出来。
1. 拆解报错:ERROR 1045 到底在说什么
1.1 错误信息里的三个关键片段
先把这个报错拆开看。ERROR 1045 (28000)是 MySQL 的客户端错误码,含义是身份认证失败,拿数据库术语说就是 Access denied。这个错误码从 MySQL 5.x 到 8.x 一直存在,遇到它说明服务器已经收到了你的连接请求,但在校验用户名和密码这一步把你拒了。
'ODBC'@'localhost'这一节是很多人看糊涂的地方。它表示MySQL 服务器在连接握手中收到的客户端用户名是 ODBC,来源主机是 localhost。这里有个很重要的点:报错里出现的用户名,是客户端主动告诉 MySQL 的,不是 MySQL 自己猜的。你的客户端工具、ODBC 驱动或者中间层组件在建立连接时,把用户名字段填成了 ODBC,服务器只是把这个名字拿去和权限表比对,比对不上就拒绝。
最后的(using password: NO)说的是这次连接请求没有附带任何密码。结合起来理解,整条报错翻译成人话就是:有个自称是 ODBC 的客户端,从本机发来连接请求,没带密码,MySQL 的权限表里找不到能匹配的账号,所以拒绝访问。
1.2 为什么会有 ODBC 这个用户名
ODBC 是 Open Database Connectivity 的缩写,Windows 上做数据库连接经常接触这套标准。问题就出在它身上:很长一段时间里,各大数据库的 ODBC 驱动都有一个兜底行为——如果连接字符串或数据源(DSN)里没有显式写用户名,驱动就会默认用 ODBC 作为用户名去请求连接。这个行为不是 MySQL 特有的,SQL Server、Oracle 的老 ODBC 驱动也有类似逻辑。
所以你看到的'ODBC'@'localhost'并不是 MySQL 的某个内置账号,纯粹是驱动在缺省情况下的默认用户名。知道这一点之后,这个报错的解决方向就很明确了:不要想着去 MySQL 里建一个叫 ODBC 的用户,而是要让客户端把正确的用户名传过去。当然,如果应用代码写死了用 ODBC 用户连接,那你确实可以创建一个 ODBC 账号来救急,但那是补丁式修法,我不建议,后面细说。
2. 定位原因:这类报错最常见的 4 个触发场景
2.1 命令行客户端没有指定用户名
最简单也最容易犯的一种:在 Windows 的命令行里直接敲mysql或者mysql -h localhost,没带-u参数。MySQL 客户端在缺少用户名时,会去读操作系统的当前用户名,但在某些 Windows 环境变量缺失、或者是在批处理脚本里调用时,这个值可能拿不到,最后就落到了 ODBC 上。
我在排查用户报障时见过不少这样的例子:应用服务器上写了个计划任务调用 MySQL 做数据导出,脚本里写的是mysql -h localhost < export.sql,完全没带用户信息,任务一跑就是 1045。这里的教训是,命令行连接必须显式写-u 用户名 -p,哪怕你本机只有一个 root,也别省这两个参数。
2.2 ODBC 数据源或连接串配置不完整
这是遇到'ODBC'@'localhost'报错的最大概率来源。Windows 的 ODBC 数据源管理器里创建连接时,有一栏叫 User,很多人填了服务器地址、端口、数据库名,唯独把 User 和 Password 留空了,测试连接的时候驱动就用 ODBC 兜底用户名,MySQL 端直接拒绝。
同理,如果你在代码里写连接字符串,漏掉UID或User字段,也会触发同样的行为。我见过一个 .NET 老项目,连接串本来是完整的,结果有人改配置时手滑删掉了 UID 那一行,应用一启动就全线报 1045,查了半天才发现是配置文件的问题。
这里给一组典型对比,方便对照排查:
| 连接串写法 | 结果 |
|---|---|
Server=localhost;Database=mydb; | 驱动默认用户 ODBC,大概率报 1045 |
Server=localhost;Database=mydb;Uid=root;Pwd=123456; | 正常连接 |
DRIVER={MySQL ODBC 8.0 Driver};SERVER=localhost;PORT=3306;DATABASE=mydb;USER=root;PASSWORD=123456;OPTION=3; | 正常连接 |
2.3 GUI 工具与旧版驱动不兼容
这个场景严格来说报的错不完全是'ODBC'@'localhost',但如果你用老版本的 MySQL ODBC 驱动(5.1、5.2、5.3 系列)去连 MySQL 8.0,很可能先碰到 1045,或者报Authentication plugin 'caching_sha2_password' cannot be loaded。MySQL 8.0 把默认认证插件换成了 caching_sha2_password,老驱动不认识这个插件,握手阶段就可能失败。
如果这时驱动在用户名缺失的情况下再用 ODBC 兜底,那 1045 和'ODBC'@'localhost'就叠加出现了。解决思路是升级驱动到 MySQL Connector/ODBC 8.0 以上,并显式配置好用户名密码,这两个问题一起解决。
2.4 Docker 部署 MySQL 时初始化没配好 root 密码
用 Docker 跑 MySQL 8.0 的人越来越多,这个场景也值得单独拿出来说。docker run创建容器时如果忘了设MYSQL_ROOT_PASSWORD环境变量,或者写成MYSQL_ALLOW_EMPTY_PASSWORD=yes,容器初始化出来的 root 账号密码状态就和你期望的不一样。后续在宿主机上配置 ODBC 连接时,如果连接串里用户名密码都不对,驱动兜底成 ODBC 用户名,报错当然逃不开 1045。
另外一个很隐蔽的坑:MYSQL_ROOT_PASSWORD只在数据目录首次初始化时生效。如果你之前已经跑过一个容器,后来删掉容器但保留了数据卷,再重新docker run时即使写了新的 root 密码也不起作用,因为密码已经写在卷里的授权表中了。
3. 快速修复:先别重置密码,试试这几步
3.1 命令行连接的正确姿势
先确认 MySQL 服务本身是正常的,然后用最标准的方式手工验证登录:
mysql -h localhost -P 3306 -u root -p输入密码后如果能进入,说明服务端没问题,问题出在客户端的连接配置上。这时再回头检查你的应用连接串、ODBC DSN 或者脚本,把用户名密码补全即可。
在 Linux 上还要注意一点:mysql -u root不带-p时,客户端依然会尝试无密码登录;如果 root 在auth_socket插件下,只有系统 root 用户通过本地 socket 能免密登录,普通场景下照样会被 1045 拒绝。
3.2 修复 Windows ODBC DSN
Windows 下打开 ODBC 数据源管理器的方式是:控制面板 → 管理工具 → ODBC 数据源(64位/32位根据应用来选),或者直接在运行框输odbcad32.exe。进去后找到你的 DSN 配置,重点检查这几个字段:
- User:必须显式填,不能留空
- Password:填对应账号的密码
- 如果填了 Database,要确保账号对这个库有权限
填完点 Test 测试,能通就说明问题解决。这里有个经常被忽略的细节:64 位系统上 32 位应用必须用 32 位的 ODBC 管理器配置 DSN,否则应用侧看到的配置和系统侧不一致,测试时明明通了,应用一跑还是老报错。
3.3 验证 MySQL 用户与授权信息
如果连接配置看着没问题,那就要确认一下 MySQL 里到底有没有你要用的账号,以及它允许从哪些主机登录:
SELECT user, host, plugin FROM mysql.user;这条命令把当前实例的所有账号列出来。重点看两件事:一是你要连接的账号是否存在,二是它允许的主机范围。很多新手会忽略host这一列,创建用户时写了'root'@'localhost',然后用mysql -h 192.168.x.x -u root -p去连,服务器看到的是来自远程主机 192.168.x.x 的 root,而授权表里只有 localhost,照样 1045。
如果账号确实不存在,创建并授权:
CREATE USER 'myapp'@'localhost' IDENTIFIED BY 'YourPass123!'; GRANT ALL PRIVILEGES ON mydb.* TO 'myapp'@'localhost'; FLUSH PRIVILEGES;注意:
CREATE USER和GRANT操作需要你有管理员权限。如果 root 本身也登不进去,那就直接跳到下一节的密码重置方案。
4. 终极兜底:root 也进不去时的密码重置方案
4.1 方法一:--skip-grant-tables 模式
这套方法的核心思路是让 MySQL 启动时跳过授权表检查,从而在不验证密码的情况下进入系统,然后重置密码。Linux 系统上操作如下:
先把 MySQL 停掉:
systemctl stop mysqld然后用跳过授权表的方式启动:
mysqld --skip-grant-tables --skip-networking &加上--skip-networking是为了防止其他主机趁这个间隙连进来,这个安全意识很重要,因为跳过授权表意味着服务器没有任何认证保护。如果是在 RHEL/CentOS 系上,mysqld 的启动参数可能受 systemd 限制,更稳妥的做法是修改/etc/my.cnf,在[mysqld]段下加一行skip-grant-tables,然后正常启动服务:
systemctl start mysqld接下来直接无密码进入:
mysql -uroot进入后先执行FLUSH PRIVILEGES,这一步在 MySQL 8.0 里是必须的。跳过授权表启动时,服务器不会加载授权表到内存,如果不先 FLUSH,直接执行ALTER USER会报ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement。这是我实际踩过的坑,网上很多老教程没提这点,照着做就会卡在这一步。
FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass123!';重置完后,把配置文件里加的skip-grant-tables删掉,再重启 MySQL:
systemctl restart mysqld4.2 方法二:--init-file 初始化脚本
如果你不想让服务器以无认证状态运行哪怕一分钟,可以用--init-file的方式。预先写一个包含重置语句的 SQL 文件,然后指定 MySQL 在启动时自动执行它。
先创建文件/tmp/mysql-reset.sql:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass123!';然后修改配置文件或在启动命令中指定:
mysqld --init-file=/tmp/mysql-reset.sql &MySQL 启动时会执行文件里的 SQL,执行完再正常加载权限系统。注意一点:--init-file文件里的 SQL 执行失败不会阻止 MySQL 启动,所以重置完之后最好验证一下新密码能不能登录。如果用的是ALTER USER语法,MySQL 8.0 也要求此时服务器已经加载授权表,所以不需要像 skip-grant-tables 那样先 FLUSH。
执行成功后删掉这个 SQL 文件,正常重启服务即可。
4.3 方法三:Docker 容器内的处理
Docker 场景下如果 root 密码不对,处理思路和普通环境略有差别。最推荐的做法是利用数据卷另起一个临时容器来重置。
假设原容器叫mysql8,先停止它:
docker stop mysql8然后用同样的数据卷启动一个临时容器,并让 mysqld 以跳过授权表方式运行:
docker run -d --name mysql8-reset --volumes-from mysql8 mysql:8.0 mysqld --skip-grant-tables --skip-networking等待几秒让它完成初始化,进入这个临时容器:
docker exec -it mysql8-reset mysql -uroot进入 MySQL 后执行:
FLUSH PRIVILEGES; ALTER USER 'root'@'localhost' IDENTIFIED BY 'NewPass123!';退出后清理临时容器,重新启动原容器:
docker stop mysql8-reset docker rm mysql8-reset docker start mysql8这里要提醒一下:以上操作依赖容器数据卷的挂载方式,--volumes-from会把原容器所有数据卷继承过来,确保 MySQL 的数据目录是同一个。如果你用的是具名卷,也可以直接--mount source=myvolume,target=/var/lib/mysql指定挂载,效果一样。
另有一种比较省事的思路是直接用
docker exec -it mysql8 mysql -uroot去碰碰运气。如果当初创建容器时设置了MYSQL_ROOT_PASSWORD但你忘了密码,并且数据卷是初次初始化,root 密码就是你设置的那个值;只有在你压根没设置过的情况下,才需要走上面这套重置流程。
4.4 MySQL 8.0 密码策略与认证插件的坑
重置密码时,如果你设置的密码太简单,MySQL 8.0 会直接给你颜色看:
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements这是 validate_password 组件在把关。默认的密码策略要求密码至少 8 位,而且要包含大小写字母、数字和特殊字符。所以重置 root 密码时不要一上来就设123456或root,直接按高强度规则来,一步到位。
如果你是给老应用重置密码,还得多考虑一步认证插件的兼容性。MySQL 8.0 的默认认证插件是 caching_sha2_password,如果你的应用用的是旧版客户端或旧版 ODBC 驱动,即使密码对了也可能连不上。这时候可以把这个用户的认证方式改成老协议:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'NewPass123!';改动前先确认这个实例上运行的业务是否有老客户端依赖。如果只是给自己用,建议直接用 MySQL 8.0 官方最新驱动,没必要为了兼容老组件而降低安全性。
5. MySQL 8.0 专属坑位:认证插件与客户端兼容性
5.1 caching_sha2_password 和 mysql_native_password 的区别
MySQL 5.7 及更早版本的默认认证插件是 mysql_native_password,传输密码时用的是 SHA1 算法,后来被发现存在安全隐患。MySQL 8.0 换成了 caching_sha2_password,基于 SHA256,安全性提升明显,同时对服务端性能也更友好——它在首次认证后会缓存认证结果,后续连接走缓存,不用每次都做全套密码哈希运算。
对终端用户来说,最大的感知差异是老的客户端和驱动认不出新插件。MySQL 官方自带的 8.0 客户端没问题,但如果你用的是很老的 Navicat 版本、旧版驱动,或者系统自带的很老的 ODBC 驱动,连接时就会失败,报错信息五花八门,常见的有:
Authentication plugin 'caching_sha2_password' cannot be loadedAccess denied for user 'xx'@'localhost'- 在某些工具里直接显示无法连接或者超时
5.2 给老客户端开绿灯的两个方向
如果你的业务确实没法升级客户端,有两个补救方向:
第一个方向是把个别用户的认证方式降级为 mysql_native_password,前面已经写了 SQL,不再重复。这种方式影响面小,只改你需要兼容的那个账号。
第二个方向是在配置文件中把实例默认认证方式改掉:
[mysqld] default_authentication_plugin=mysql_native_password改完重启后,新建的用户会默认使用老认证方式,但这不会影响已存在的用户。注意这个参数在 MySQL 8.0 中已被标记为废弃,不建议长期依赖,只适合过渡期使用。新项目的正确做法是用 8.0 的驱动和客户端,保持默认的 caching_sha2_password。
5.3 ODBC 驱动的选型建议
回到标题里的 ODBC,如果你是用 ODBC 方式连 MySQL 8.0,驱动版本必须在 8.0 以上。下载时注意区分 32 位和 64 位,这个安装细节直接影响连接结果。很多人装了 64 位驱动,但应用是 32 位的,ODBC 管理器里死活看不到驱动,或者连接报错。遇到这种情况,装一个对应位数版本,问题立刻消失。
驱动的架构选择可以参考下表:
| 应用位数 | 驱动位数 | ODBC 管理器入口 |
|---|---|---|
| 32 位应用 | 32 位驱动 | C:\Windows\SysWOW64\odbcad32.exe |
| 64 位应用 | 64 位驱动 | C:\Windows\System32\odbcad32.exe |
连接串里同样要写全参数,一个标准的 MySQL ODBC 8.0 连接串长这样:
DRIVER={MySQL ODBC 8.0 Driver};SERVER=127.0.0.1;PORT=3306;DATABASE=mydb;USER=root;PASSWORD=YourPass123!;OPTION=3;OPTION=3这个值不是随便写的,它表示启用一些兼容性选项,适合常规读写场景。具体每个 bit 位对应什么,官方文档里有说明,平时不用记,知道这个参数存在就够了。
6. 常见问题速查表与排查心得
6.1 高频报错对照速查表
把我和同事们实际处理过的高频报错整理成一张速查表,遇到类似问题可以先对号入座:
| 报错信息 | 真实含义 | 处理方式 |
|---|---|---|
Access denied for user 'ODBC'@'localhost' (using password: NO) | 客户端没带用户名和密码,驱动兜底用 ODBC | 检查连接串/DSN,显式填用户名密码 |
Access denied for user 'root'@'localhost' (using password: YES) | 用户名对,密码不对 | 重置密码或核对密码 |
Access denied for user 'root'@'localhost' (using password: NO) | 用户名对,但没带密码 | 加上-p参数并输入密码 |
Access denied for user 'root'@'192.168.1.10' | 账号存在但登录来源主机不被允许 | 创建'root'@'%'或对应网段账号 |
Authentication plugin 'caching_sha2_password' cannot be loaded | 客户端太旧,不认识新认证插件 | 升级驱动,或把用户改成 mysql_native_password |
ERROR 1819 (HY000): Your password does not satisfy... | 新密码不符合密码策略 | 按复杂度要求设置密码 |
Can't connect to MySQL server on 'localhost' (10061) | MySQL 服务没启动或端口没监听 | 检查服务状态和防火墙 |
6.2 我踩过的几个坑
第一个坑是 skip-grant-tables 模式下直接执行 ALTER USER,报 1290。这个问题在 MySQL 8.0 里特别容易遇到,因为 8.0 对授权表操作的检测更严格了。解决方案前面已经写了,先FLUSH PRIVILEGES再执行 ALTER,顺序不能反。
第二个坑是 Docker 场景下重置完密码后忘了清理临时容器,结果两个容器同时在跑,端口和资源都冲突。我建议重置流程走完后顺手把临时容器删掉,别留着。
第三个坑是密码重置完成后没有及时移除skip-grant-tables配置。这个比较危险,服务器处于无认证状态,任何人只要知道端口就能连进来。每次操作完,记得回头检查配置文件,确认没有残留的跳权选项。
第四个坑不太起眼,但很折磨人:Windows 下 ODBC 管理器里测连接是通的,应用里却还是报'ODBC'@'localhost'。原因通常是应用读的是 32 位 DSN,而你配的是 64 位 DSN,或者反过来。两个位数各配一遍,再测试基本能解决。
6.3 最后分享一套排查思路
根据我这几年处理各种 MySQL 连接报错的经验,遇到 1045 先别慌,按这个顺序走基本不会跑偏:
先确认报错里出现的用户名是不是你期望的那个。如果出现了 ODBC 这种意外用户名,几乎可以断定是客户端连接参数的问题,优先查连接串、DSN、脚本参数,别去动数据库。如果用户名正确,再用命令行手工验证一下密码和登录来源,能登进去就说明账号没问题,问题在应用侧的驱动或网络配置。如果命令行也登不进去,才考虑密码重置流程,按第 4 节的三种方式选一种适合的。
这套思路的核心就是先分清是客户端的问题还是服务端的问题,绝大多数 1045 其实都卡在客户端配置上,真正需要重置密码的情况反而少。技术排查最忌讳的就是一开始就往服务器上动刀,查清楚问题在哪一层,往往也就是几分钟的事。