用 Navicat 连 WSL2 里的数据库

先给结论:四张连接这么填

  • 场景:数据库装在 WSL2 的 Ubuntu 26.04 里,Navicat 装在 Windows 上,两边隔着虚拟网卡,所以「填什么地址」这件事比平时容易踩坑。本机实测的四个服务是 MySQL 9.7.4、Redis 8.2.8、MongoDB 9.0.4、PostgreSQL 18.6,都是 active + enabled(开机自启)。
  • 四张连接的字段,一次列全(密码一律写成 password,取法见下一节,别把明文写进任何会提交的文件):

    服务 类型 主机 端口 用户名 密码 还要设的 实测依据
    MySQL 9.7.4 无 127.0.0.1 3306 root password SSL 页不用动 ss -ltnp 里 mysqld 在 *:3306;current_user() 回 root@localhost
    Redis 8.2.8 单机 127.0.0.1 6379 无 password 「验证」不能留 None redis_mode:standalone、cluster-enabled:no、没有 sentinel 进程
    MongoDB 9.0.4 Standalone 127.0.0.1 27017 admin password 验证数据库 = admin setName 为空、replSetGetStatus 报 NoReplicationEnabled;用户只有 admin.admin(角色 root@admin)
    PostgreSQL 18.6 PostgreSQL 127.0.0.1 5600(不是 5432,原因见后面那节) postgres 或 django password 初始数据库填 postgres / django_note SHOW port 回 5600、SHOW listen_addresses 回 *;127.0.0.1:5600 实测认证通过
  • 四条铁律,后面每一节都是在证明它们:

    1. 主机填 127.0.0.1,不要填 localhost,更不要填 WSL 那个 IP。
    2. Redis 的「验证」和 MongoDB 的「验证数据库」是最容易漏的两格,漏了分别报 NOAUTH / code 18 Authentication failed。
    3. 连不上先怀疑 WSL 没开着,而不是密码错——端口转发是 WSL 运行时才存在的。
    4. PostgreSQL 是唯一一个「按默认 5432 填就连不上」的服务:5432 落在 Windows 的保留端口区间里,wslrelay 绑不上去。把服务端口挪到区间外(本机用 5600)就能照旧填 127.0.0.1,细节和实测在那一节里。

这套环境长什么样:Windows 侧的端口是谁在听

  • WSL2 默认是 NAT 网络:Linux 有自己的虚拟网卡和 IP,理论上 Windows 要填那个 IP 才能连。但 Ubuntu 里的三个服务都没监听那个地址,所以真走 IP 反而连不上。真正把 Windows 的连接搬进 Linux 的是 wslrelay.exe。

  • 实测(Windows 上跑 netstat,再按 PID 反查进程名):

    1
    2
    netstat -ano -p TCP | findstr LISTENING
    tasklist /FI "PID eq 61456" /FO CSV /NH
    1
    2
    3
    4
    5
    TCP    127.0.0.1:3306    0.0.0.0:0    LISTENING    61456
    TCP 127.0.0.1:6379 0.0.0.0:0 LISTENING 61456
    TCP 127.0.0.1:27017 0.0.0.0:0 LISTENING 61456
    TCP 127.0.0.1:33060 0.0.0.0:0 LISTENING 61456
    "wslrelay.exe","61456","Console","8","12,316 K"
  • 四个端口(MySQL 的 3306、X 协议 33060、Redis 的 6379、MongoDB 的 27017)都由同一个 wslrelay.exe 在 127.0.0.1 上代收,再转进 WSL。这就是「填 127.0.0.1 一定能连」的原因。

  • 反过来,Linux 侧三个服务各自绑了什么(ss -ltnp 实测):

    1
    2
    3
    4
    LISTEN  127.0.0.1:6379    users:(("redis-server",pid=33178))
    LISTEN [::1]:6379 users:(("redis-server",pid=33178))
    LISTEN 127.0.0.1:27017 users:(("mongod",pid=3598))
    LISTEN *:3306 users:(("mysqld",pid=16586))
    • mysqld 是 *(IPv4/IPv6 全绑);redis 绑 127.0.0.1 -::1(回环两个族都有);mongod 只有 127.0.0.1,没有 IPv6。这个差别正是后面「MongoDB 连接慢 2 秒」的根因。
  • password 怎么取(明文只存在 WSL 里,不进仓库):

    1
    wsl -d Ubuntu-26.04 -u root -- cat /root/.db_pw
    1
    三个服务用的是同一个password,长度 10。Navicat 的密码框直接粘进去即可。

MySQL 9.7.4:填法与实测依据

各字段填什么

字段 填 为什么(实测)
主机 127.0.0.1 wslrelay 代收;填 WSL IP 会变成 Access denied for user 'root'@'192.168.96.1'
端口 3306 select @@port 回 3306(另有 X 协议 33060,Navicat 用不到)
用户名 root mysql.user 里只有 root@localhost 这一条 root 记录
密码 password 空密码回 1045 ... (using password: NO),错密码回 1045 ... (using password: YES)
SSL 不动 require_secure_transport = OFF,明文也允许;默认会自动协商 TLS

连上之后看到什么

1
2
3
4
5
6
7
8
9
10
11
12
13
import pymysql

conn = pymysql.connect(host="127.0.0.1", port=3306, user="root", password="password")
cur = conn.cursor()
cur.execute("select current_user(), user(), @@version, @@port")
print(cur.fetchone())
cur.execute("select table_schema, count(*) from information_schema.tables "
"where table_schema not in ('information_schema','performance_schema','sys','mysql') "
"group by table_schema")
print(cur.fetchall())
cur.execute("show status like 'Ssl_cipher'")
print(cur.fetchone())
conn.close()
1
2
3
('root@localhost', 'root@localhost', '9.7.4', 3306)
(('note_demo', 23), ('py_demo', 5))
('Ssl_cipher', 'TLS_AES_128_GCM_SHA256')
  • 注意第一行:从 Windows 连进来,current_user() 和 user() 都是 root@localhost。wslrelay 转发进来的连接在 MySQL 眼里来源就是本机回环,所以正好匹配 root@localhost 这条授权记录,不需要再建一个 'root'@'%'。

  • 服务端在握手包里就交代了自己(Navicat 点连接时收到的第一个包):

    1
    2
    3
    4
    5
    6
    包长 73 字节,序号 0
    协议版本 : 10
    服务器版本: 9.7.4
    认证插件 : caching_sha2_password
    默认字符集 id: 767(utf8mb4_0900_ai_ci)
    capability: 0x15dfffff(含支持 SSL、插件认证、连接属性)

认证插件是 caching_sha2_password

1
select user, host, plugin from mysql.user where user not like 'mysql.%';
1
2
3
note_remote  %          caching_sha2_password
pyuser % caching_sha2_password
root localhost caching_sha2_password
  • 三个账号全是 caching_sha2_password,MySQL 9.x 也已经没有 mysql_native_password 这个插件了(information_schema.plugins 里只剩 sha256_password 与 caching_sha2_password 是 ACTIVE)。所以别指望「把插件改回旧的」来迁就老客户端,要迁就的是客户端本身。
  • 这个插件要求凭据不能明文裸传:要么走 TLS,要么走 RSA 公钥交换(--get-server-public-key / 驱动里的 allowPublicKeyRetrieval)。本机 TLS 是可用的(tls_version = TLSv1.2,TLSv1.3),Navicat、pymysql、mysql-connector 默认就会协商 TLS,实测会话是 TLS_AES_128_GCM_SHA256;用 --ssl-mode=DISABLED 强制明文也照样连得上(因为 require_secure_transport = OFF)。
  • 老版本 Navicat 报 Authentication plugin 'caching_sha2_password' cannot be loaded 就是不支持这个插件,升级 Navicat 即可,不要改服务器。

另外两个 % 账号连不上是正常的

  • mysql.user 里的 pyuser、note_remote 是写其它文章时建的示例账号,password和 root 不是同一个,用统一password去连会回 1045。Navicat 就用 root。

Redis 8.2.8:类型选「单机」,验证不能留 None

为什么是「单机」

1
2
3
4
5
6
redis_version:8.2.8
redis_mode:standalone
config get cluster-enabled -> no
CLUSTER INFO -> ERR This instance has cluster support disabled
pgrep redis-sentinel -> 没有进程
ss -ltnp -> 只有 1 个 redis-server 127.0.0.1:6379
  • 三条硬指标(redis_mode、cluster-enabled、有没有 sentinel 进程)都指向单机。「集群」要 cluster-enabled yes 且多实例分片;「哨兵」要额外的 26379 监控进程。这台机器上两者都不满足,选了只会连不上或反复重试。

各字段填什么

字段 填 实测依据
类型 单机 见上
主机 127.0.0.1 bind 127.0.0.1 -::1 + protected-mode yes;填 WSL IP 直接 ConnectionRefused
端口 6379 tcp_port:6379,tls-port 是 0(所以 TLS 那页不用开)
验证 需要密码(不是 None) 设了 requirepass;不带密码回 NOAUTH Authentication required.
用户名 default(或留空) ACL LIST 只有一条:user default on ... ~* &* +@all
密码 password 带密码 AUTH 回 +OK、PING 回 +PONG

连上之后看到什么

1
2
3
4
5
6
7
import redis

r = redis.Redis(host="127.0.0.1", port=6379, password="password", db=0)
print(r.ping(), r.info("server")["redis_mode"])
print(r.config_get("databases"), r.dbsize())
print(r.client_info()["addr"], "->", r.client_info()["laddr"])
r.close()
1
2
3
True standalone
{'databases': '16'} 0
127.0.0.1:54844 -> 127.0.0.1:6379
  • databases 是 16,所以 Navicat 连上后左侧会有 0~15 共 16 个库;dbsize 是 0,键空间是空的,属正常(学习实例,数据都是随写随清的)。
  • client_info()["addr"] 是 127.0.0.1:54844——和 MySQL 一样,Redis 看到的来源也是回环地址。

填错的两种报错

1
2
3
不带密码:AuthenticationError - HELLO must be called with the client already authenticated,
otherwise the HELLO <proto> AUTH <user> <pass> option can be used ...
密码填错:AuthenticationError - invalid username-password pair or user is disabled.
  • 第一条是 redis-py 用 RESP3 握手时的说法,本质就是「没认证」;Navicat 里对应的现象是点连接直接报 NOAUTH。

MongoDB 9.0.4:Standalone + 验证数据库填 admin

类型为什么是 Standalone、SRV 为什么不勾

1
2
3
4
db.serverStatus().set.name        -> 无(不是副本集)
replSetGetStatus -> 报错 NoReplicationEnabled - not running with --replSet
sh.status() -> 报错(不是 mongos,没开分片)
hello 返回 -> me / setName / hosts 都是空,isWritablePrimary: true
  • 单机形态下 hello 里根本没有 me/hosts 这些拓扑字段,客户端不需要再去发现别的节点,所以类型就是 Standalone。
  • SRV 记录不勾:勾上走的是 mongodb+srv:// 那套 DNS 种子列表(MongoDB Atlas 之类用),本机没有 _mongodb._tcp 记录,勾了只会在 DNS 查询上失败。

各字段填什么

字段 填 实测依据
类型 Standalone 见上
SRV 记录 不勾 本机没有 SRV 记录
主机 127.0.0.1 bindIp: 127.0.0.1;WSL IP 与 ::1 都是 ConnectionRefused
端口 27017 ss -ltnp 里 mongod 就在 127.0.0.1:27017
验证 Password 见下面「这台其实没开鉴权」一条,选 None 其实也连得上
验证数据库 admin 用户存在 admin 库的 system.users 里,authSource 填错立刻 code 18
用户名 admin(不是 root) admin.system.users 只有一条:_id = admin.admin,角色 root@admin
密码 password connectionStatus 回 admin@admin + root@admin

连上之后看到什么

1
2
3
4
5
6
7
8
9
import pymongo

cli = pymongo.MongoClient("mongodb://admin:password@127.0.0.1:27017/?authSource=admin")
print(cli.admin.command("ping").get("ok"))
me = cli.admin.command("connectionStatus")["authInfo"]
print([(u["user"], u["db"]) for u in me["authenticatedUsers"]],
[(x["role"], x["db"]) for x in me["authenticatedUserRoles"]])
print(cli.list_database_names())
cli.close()
1
2
3
1.0
[('admin', 'admin')] [('root', 'admin')]
['admin', 'config', 'local']
  • 只有 admin、config、local 三个库,集合也都是系统自带的(system.users、system.version、system.sessions、startup_log)。note_demo、py_demo 那两个是 MySQL 的库,别在 MongoDB 里找。

这台其实没开鉴权:两种填法都能连

1
2
3
4
/etc/mongod.conf 里只有 net.port / net.bindIp,没有 security 段
getCmdLineOpts 的 parsed.security -> 空
不带任何凭据:ping -> ok=1.0,connectionStatus 的 authenticatedUsers 为空,
而且能 listDatabases、能读 admin.system.users
  • 也就是说 验证选 None 也能连上,而且一进去就是全部权限(因为只绑 127.0.0.1,风险可控,本机学习够用)。
  • 但既然 admin 账号已经建好了,建议还是按上表填 Password,行为和其它两台一致。
  • 以后要把 bindIp 改成 0.0.0.0 之前,必须先加 security.authorization: enabled 再重启 mongod,否则等于把库裸奔到局域网上。

PostgreSQL 18.6:默认端口 5432 被 Windows 保留了

先说现象

  • Navicat 里按最直觉的方式填(主机 127.0.0.1、端口 5432、用户 postgres),点「测试连接」回:

    1
    2
    connection to server at "127.0.0.1", port 5432 failed: Connection refused (0x0000274D/10061)
    Is the server running on that host and accepting TCP/IP connections?
  • 但服务其实活得好好的:WSL 里 systemctl is-active postgresql@18-main 是 active,ss -ltn 能看到 0.0.0.0:5432 和 [::]:5432 在听;在 WSL 内部 psql 直接就能连。问题不在 PostgreSQL,在 Windows 这一侧。

排查:Windows 上到底有没有人在听 5432

1
netstat -ano | findstr ":5432"
1
(没有任何 LISTENING 行)
  • 对比另外三个服务,它们的转发监听都在:

    1
    2
    3
    TCP    127.0.0.1:3306    0.0.0.0:0    LISTENING    27796
    TCP 127.0.0.1:6379 0.0.0.0:0 LISTENING 27796
    TCP 127.0.0.1:27017 0.0.0.0:0 LISTENING 27796
  • 也就是说 wslrelay.exe 没能给 5432 建监听。原因在这条命令的输出里(Windows 会随机保留一批 TCP 端口给 Hyper-V/WSL 自己用):

    1
    netsh int ipv4 show excludedportrange protocol=tcp
    1
    2
    3
    4
    5
    6
    7
    开始端口    结束端口
    4902 5001
    5172 5271
    5284 5383
    5384 5483 <-- 5432 在这里面
    5484 5583
    50000 50059 *
  • 结论:5432 落在保留区间 5384-5483 内,wslrelay 想绑也绑不上,于是 Windows 侧的 127.0.0.1:5432 根本没人听,连接被拒。

  • 顺带纠正两个容易误判的猜测:这不是「服务起晚了没登记」——实测 wsl --shutdown 重启整个 WSL、并且等四个端口都监听就绪之后,127.0.0.1:5432 依然没有 LISTENING;也不是防火墙拦的,因为同一台机器上换个端口立刻就通。

修法一(推荐):把 PostgreSQL 的端口挪到保留区间之外

  • 本机选 5600(在 5484-5583 之后、7409 之前,实测可用)。改两处再重启:

    1
    2
    3
    # /etc/postgresql/18/main/postgresql.conf
    port = 5600
    listen_addresses = '*'
    1
    sudo systemctl restart postgresql@18-main
  • 验证(实测):

    1
    2
    3
    SHOW port            -> ('5600',)
    SHOW listen_addresses -> ('*',)
    SELECT version() -> PostgreSQL 18.6 (Ubuntu 18.6-0ubuntu0.26.04.1) on x86_64-pc-linux-gnu
    1
    netstat -ano | findstr ":5600"
    1
    TCP    127.0.0.1:5600    0.0.0.0:0    LISTENING    27796   # wslrelay 这次绑上了
  • 之后 Navicat 就按常规填:主机 127.0.0.1、端口 5600,实测从 Windows 连进去 current_user 是 postgres。

修法二:端口不动,主机填 WSL 的 IP

  • wsl hostname -I 拿到本机是 192.168.104.119,用它连 5432 实测也通(psycopg 回 认证 OK)。
  • 但这是唯一一个要填 WSL IP 的服务,和前面那条「主机一律填 127.0.0.1」的铁律相反;而且 NAT 模式下这个 IP 每次重启可能变,所以只适合临时救急。

修法三:管理员加一条端口代理

1
netsh interface portproxy add v4tov4 listenaddress=127.0.0.1 listenport=5432 connectaddress=192.168.104.119 connectport=5432
  • 需要管理员权限;普通权限执行直接回 请求的操作需要提升(作为管理员运行)(实测)。而且 WSL IP 变了这条规则就失效,所以不如改端口干净。

换到 5600 之后仍然 refused?先看 WSL 在不在跑

  • 实测复现过一次:端口已经改成 5600,Navicat 还是回同样的 Connection refused (0x0000274D/10061),只是主机端口换成 5600:

    1
    connection to server at "127.0.0.1", port 5600 failed: Connection refused (0x0000274D/10061)
  • 这时候 wsl -l -v 的答案是 Stopped,而 Windows 侧 netstat -ano | findstr LISTENING 里四个端口一行都没有(3306/6379/27017/5600 全消失)。也就是说这不是端口的问题,是整个 WSL 实例被回收停掉了,wslrelay 随之退出,所有转发都不存在了——此刻你连 MySQL 也一样连不上。

  • 根因在这台机器的 .wslconfig:[general] 段写了 instanceIdleTimeout=60000,意思是最后一个 WSL 会话退出 1 分钟后,发行版实例就停止([wsl2] 的 vmIdleTimeout=600000 是虚拟机层面的 10 分钟回收)。所以只要没有开着的 WSL 终端,端口转发随时会消失。

  • 两个动作,任选或都做:

    1. 连之前先把 WSL 拉起来(实测这样就立刻能连):

      1
      2
      3
      wsl -d Ubuntu-26.04          # 或者任何一条 wsl 命令,跑完实例就活着
      wsl -l -v # 应当是 Running
      netstat -ano | findstr ":5600"
      1
      2
      3
      4
      5
      Ubuntu-26.04    Running    2
      TCP 127.0.0.1:3306 0.0.0.0:0 LISTENING 17508
      TCP 127.0.0.1:6379 0.0.0.0:0 LISTENING 17508
      TCP 127.0.0.1:27017 0.0.0.0:0 LISTENING 17508
      TCP 127.0.0.1:5600 0.0.0.0:0 LISTENING 17508 # 同一个 wslrelay PID 代收

      拉起之后从 Windows 实测:psycopg 连 127.0.0.1:5600 回 ('postgres',) + ('5600',),认证通过。

    2. 把 instanceIdleTimeout 调大(例如 600000,或整行删掉用默认值),改完要 wsl --shutdown 一次才生效;代价是 WSL 会常驻、占着内存。

  • 一句话记法:Connection refused + 四个端口全消失 = WSL 停了;Connection refused + 只有 5432 消失 = 保留端口区间;能连上但报 FATAL = 账号/库名/口令的问题。

各字段填什么

字段 填 实测依据
主机 127.0.0.1 端口挪到 5600 后 wslrelay 会代收;仍是 5432 时这里连不上
端口 5600(或自己的选择,只要在保留区间外) SHOW port 回 5600
初始数据库 postgres(或 django_note) SELECT datname FROM pg_database WHERE datistemplate=false 回 ['postgres', 'django_note']
用户名 postgres 或 django pg_roles 里两个都 rolcanlogin = t
密码 password Ubuntu 装的包默认不给 postgres 角色设口令(只有本地 peer 认证),走 TCP 必须先设:ALTER ROLE postgres WITH PASSWORD 'password';
SSL / SSH / HTTP 页 不用动 明文 + scram 即可

pg_hba.conf 里真正起作用的是这两类行(实测本机现状):

1
2
3
host    all    all    127.0.0.1/32    scram-sha-256
host all all ::1/128 scram-sha-256
host all all 0.0.0.0/0 scram-sha-256 # 从 Windows 侧进来时命中的是这条

连上之后看到什么

1
2
3
4
5
6
7
8
9
10
import os

import psycopg

with psycopg.connect(host="127.0.0.1", port=5600, dbname="postgres",
user="postgres", password=os.environ["DB_PW"]) as conn:
print(conn.execute("SHOW port").fetchone()) # ('5600',)
print(conn.execute("SELECT current_user").fetchone())
print([r[0] for r in conn.execute(
"SELECT datname FROM pg_database WHERE datistemplate=false").fetchall()])
1
2
3
('5600',)
('postgres',)
['postgres', 'django_note']

用 django 角色连自己的库也通:SELECT current_database(), current_user 回 ('django_note', 'django')。

PostgreSQL 与 MySQL 的两个习惯差异(都会踩)

  • current_user 不是函数:SELECT current_user() 直接报 SyntaxError: syntax error at or near "(",写 SELECT current_user 就行(同类还有 session_user、user)。

  • 库不存在是独立错误:填错初始数据库回 FATAL: database "meicu" does not exist,和口令错误是两行不同的报文,别混为一谈:

    1
    2
    口令错:FATAL: password authentication failed for user "postgres"
    库名错:FATAL: database "meicu" does not exist

为什么主机只能填 127.0.0.1:顺带解释「连 MongoDB 慢 2 秒」

  • 先说两个前提,都是实测:

    1. Windows 上 localhost 的解析顺序是 IPv6 在前:getaddrinfo("localhost", 27017) 返回 ['::1', '127.0.0.1']。

    2. 这台 Windows 上 「连一个没人听的端口」不是立刻被拒,要固定等约 2.03~2.06 秒 才返回 ConnectionRefusedError:

      1
      2
      3
      4
      127.0.0.1     :47000   2.059 s  ConnectionRefusedError
      ::1 :49000 2.054 s ConnectionRefusedError
      192.168.104.119:27017 2.059 s ConnectionRefusedError
      127.0.0.1 :80 0.001 s 连上(有人听,对照)
  • 两条合起来就解释了「MySQL/Redis 快、MongoDB 慢」。三个服务对 ::1 的态度不同(上一节那张 ss 表),于是同样写 localhost,耗时完全不同:

    1
    2
    3
    4
    5
    6
    7
    8
    import socket
    import time

    for label, port in [("MySQL", 3306), ("Redis", 6379), ("MongoDB", 27017)]:
    for host in ["localhost", "127.0.0.1"]:
    s = time.perf_counter()
    socket.create_connection((host, port), timeout=8).close()
    print("%-8s %-10s %.3f s" % (label, host, time.perf_counter() - s))
    1
    2
    3
    4
    5
    6
    MySQL    localhost  0.000 s
    MySQL 127.0.0.1 0.006 s
    Redis localhost 0.001 s
    Redis 127.0.0.1 0.000 s
    MongoDB localhost 2.051 s
    MongoDB 127.0.0.1 0.014 s
    • 这几个数每次跑都会小幅浮动(毫秒级那几行有时是 0.000,有时是 0.02),但量级不会变:只有 MongoDB 的 localhost 稳定在 2 秒上下。
  • MySQL/Redis 的 ::1 上有人听,所以 localhost 第一次就成功;mongod 没绑 ::1,第一次要等 2 秒被拒,才回落到 127.0.0.1。

  • Navicat 侧的证据在 mongod 自己的日志里(/var/log/mongodb/mongod.log,客户端标识是 driver: mongoc 1.26.1)。同一批连接之间的间隔,快的时候是毫秒,慢的时候正好卡在 2 秒:

    1
    2
    3
    一批 3 条:conn365 +0.000 s → conn366 +2.043 s → conn367 +2.043 s
    一批 3 条:conn380 +0.000 s → conn381 +2.057 s → conn382 +2.074 s
    一批 2 条:conn384 +0.000 s → conn385 +0.000 s (填 127.0.0.1 时)

    而认证本身只花 0.7~2.2 毫秒(SCRAM-SHA-256,conversation_duration.micros 实测 702~2192),服务端一点都不慢。

  • 两个修法,任选其一:

    1. 改 Navicat:主机填 127.0.0.1。改完上面那张表里 MongoDB 那一行就从 2.073 秒变 0.001 秒,这是最省事、不动服务器的做法。
    2. 改 mongod:/etc/mongod.conf 里把 bindIp: 127.0.0.1 改成 bindIp: 127.0.0.1,::1,再 systemctl restart mongod。这样 localhost 也能秒连,和 MySQL/Redis 行为一致。
  • 至于 WSL 的 IP(本机是 192.168.104.119)为什么不能用:三个服务都没绑它。实测 192.168.104.119:6379、:27017 是 ConnectionRefused(还要等那 2 秒);192.168.104.119:3306 虽然 TCP 能通(mysqld 绑的是 *),但 MySQL 会把它当成另一个来源主机而拒绝授权:

    1
    (1045, "Access denied for user 'root'@'192.168.96.1' (using password: YES)")

    192.168.96.1 是 WSL 眼里的 Windows 网关地址——也就是说这条连接确实进到了 MySQL,只是不匹配任何授权记录。

Python 客户端的等价写法:先跑通脚本,再去点 Navicat

  • 排错时先用 Python 连一次,能把「网络/端口问题」和「Navicat 填错格子」分开。下面四段都在本机跑通过,password用环境变量传,不要硬编码。
1
2
3
4
5
6
7
8
9
10
11
# MySQL:pymysql 与官方驱动都通
import os

import pymysql

conn = pymysql.connect(host="127.0.0.1", port=3306, user="root",
password=os.environ["DB_PW"], connect_timeout=5)
cur = conn.cursor()
cur.execute("select current_user(), @@version")
print(cur.fetchone()) # ('root@localhost', '9.7.4')
conn.close()
1
2
3
4
5
6
7
8
9
# Redis:密码必须给,用户名不用(default 用户)
import os

import redis

r = redis.Redis(host="127.0.0.1", port=6379,
password=os.environ["DB_PW"], db=0, socket_timeout=5)
print(r.ping(), r.info("server")["redis_mode"]) # True standalone
r.close()
1
2
3
4
5
6
7
8
9
# MongoDB:用户名 admin,认证库 admin
import os

import pymongo

cli = pymongo.MongoClient("mongodb://admin:%s@127.0.0.1:27017/?authSource=admin"
% os.environ["DB_PW"], serverSelectionTimeoutMS=5000)
print(cli.admin.command("ping").get("ok"), cli.list_database_names()) # 1.0 ['admin', 'config', 'local']
cli.close()
1
2
3
4
5
6
7
8
9
10
# PostgreSQL:端口是 5600(5432 落在 Windows 保留区间里,上一节有解释)
import os

import psycopg

with psycopg.connect(host="127.0.0.1", port=5600, dbname="django_note",
user="django", password=os.environ["DB_PW"],
connect_timeout=8) as conn:
print(conn.execute("SELECT current_database(), current_user").fetchone(),
conn.execute("SHOW port").fetchone())
1
('django_note', 'django') ('5600',)
1
2
# 在 Windows 的命令行里把password喂给上面四段脚本(PowerShell)
$env:DB_PW = (wsl -d Ubuntu-26.04 -u root -- cat /root/.db_pw)
  • 四条连接各有一个「填错就必然失败」的代表性报错,见到就知道该改哪格:

    报错原文(截断) 服务 真正的原因
    1045 Access denied for user 'root'@'localhost' (using password: NO/YES) MySQL 密码没填 / 填错
    1045 Access denied for user 'root'@'192.168.96.1' MySQL 主机填了 WSL IP,来源不匹配 root@localhost
    NOAUTH Authentication required. / HELLO must be called with the client already authenticated Redis 「验证」留了 None
    invalid username-password pair or user is disabled. Redis 密码填错
    code 18, codeName AuthenticationFailed MongoDB 用户名写成 root,或「验证数据库」不是 admin
    ConnectionRefusedError (10061)(而且要先等 2 秒) 任一 主机填了 WSL IP,或服务真的没起
    FATAL: password authentication failed for user "postgres" PostgreSQL postgres 角色没设口令 / 口令填错
    FATAL: database "meicu" does not exist PostgreSQL 「初始数据库」那格填错
    connection to server at "127.0.0.1", port 5432 failed: Connection refused PostgreSQL 5432 在 Windows 保留区间里,wslrelay 绑不上——换端口

连不上时的排查顺序

  • 按这个顺序查,每步都有明确的判据,别一上来就怀疑密码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 第 1 步:WSL 在不在跑(不在跑,后面全免谈)
wsl -l -v

# 第 2 步:四个服务在不在
wsl -d Ubuntu-26.04 -u root -- systemctl is-active mysql redis mongod postgresql@18-main

# 第 3 步:Windows 侧端口有没有被转发(应当看到 5 行 LISTENING,同一个 PID)
netstat -ano -p TCP | findstr "127.0.0.1:3306 127.0.0.1:6379 127.0.0.1:27017 127.0.0.1:5600"

# 第 3.5 步:某个端口一直连不上时,先看它是不是被 Windows 保留了
netsh int ipv4 show excludedportrange protocol=tcp

# 第 4 步:绕开 GUI,先用 Python 连一次(上一节那四段)
# 第 5 步:Python 通而 Navicat 不通,才去逐格核字段:主机 / 类型 / 验证 / 验证数据库
1
2
3
4
第 2 步实测输出:
active
active
active
  • 几个容易忽略的点:
    • wslrelay 只在 WSL 运行期间存在。WSL 被关掉(wsl --shutdown 或空闲回收)后,netstat 里那五行会一起消失,Navicat 表现为「连不上」而不是「连得慢」。这台机器的 .wslconfig 里 instanceIdleTimeout=60000,最后一个会话退出 1 分钟就回收,所以别开着 WSL 终端不管却指望端口一直在;要用就先 wsl -d Ubuntu-26.04 拉一下。
    • Redis 的 protected-mode yes 只放行回环来源,这跟「主机填 127.0.0.1」是配套的;不要为了连上而把它关掉。
    • MongoDB 慢 2 秒不是故障,是 localhost 先试 ::1 再回落的代价,改填 127.0.0.1 就没了。
    • MySQL 的空闲断线:wait_timeout 与 interactive_timeout 实测都是 28800 秒(8 小时),Navicat 挂着连接过夜会被服务端掐掉,重连即可,不是配置错。
    • 这份笔记里所有连接都是本机 WSL 的测试实例。文章里那种 HOST: '101.xx.xx.xx' 的写法是线上生产库的示例,不要拿 Navicat 去连、更不要往里写数据。

参考