最近在 MySQL 中新建了一个数据库帐号,然后配置成允许所有 IP 都可以登陆,但是当尝试从本地登陆时竟然报错 … … 用户名和密码都是正确的 WTF
然后仔细研究了下,发现别有天地。
简介
通过如下方式新建了一个帐号,然后登陆却失败了。
mysql> GRANT ALL ON *.* TO 'foobar'@'%' IDENTIFIED BY '123456';
Query OK, 0 rows affected (0.01 sec)
mysql> SELECT user, host, password FROM mysql.user WHERE user like 'foobar%';
+--------+------+-------------------------------------------+
| user | host | password |
+--------+------+-------------------------------------------+
| foobar | % | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+--------+------+-------------------------------------------+
1 row in set (0.00 sec)
$ mysql -ufoobar -h127.0.0.1 -P3307 -p'123456'
ERROR 1045 (28000): Access denied for user 'foobar'@'localhost' (using password: YES)
需要注意 %
和 localhost
的配置,简单来说,%
是一个通配符,用以匹配所有的 IP 地址,但是不能匹配到 locahost
这个特殊的域名。
也就是说,如果要允许本地登录,单纯只配置一个 %
是不够的 (应该是说对这种方式是不够的),需要同时配置一个 locahost
的账号。
Why
在初始化完数据库之后,登录后,通过如下命令查看会发现默认会存在 4 个账号,其对应的用户名都是 root
,而 host
的配置包括了 locahost
主机名
127.0.0.1
::1
。
mysql> SELECT user, host, password FROM mysql.user;
+------+-------------+----------+
| user | host | password |
+------+-------------+----------+
| root | localhost | |
| root | linux.local | |
| root | 127.0.0.1 | |
| root | ::1 | |
+------+-------------+----------+
4 rows in set (0.00 sec)
在本地登录 MySQL 数据库时,可以通过 -h
指定参数,有 -hlocalhost
和 -h127.0.0.1
两种方式,如果开启了 Unix Domain Socket ,那么还可以通过 -S
参数指定。
简单来说,这四个账号都是用于本地登录的,但是有啥区别呢?
locahost VS. 127.0.0.1
这个概念需要从两个角度看,一个是从操作系统角度,一个是从 MySQL 用户管理的角度。
操作系统
首先 localhost
是一个域名,而 127.0.0.1
是一个 IPv4 的地址;前者可以调用 gethostname()
解析出对应的 IP 地址,而后者调用该函数时,因为本身就是个 IP 地址,会直接返回。
一般来说,localhost
指向的是 127.0.0.1
这个 IPv4 地址;如果支持 IPv6 ,那么实际上还可以指向 [::1]
。
大多数的操作系统通过 hosts
文件制定,其中 Linux 在 /etc/hosts
文件中指定,一般内容如下。
127.0.0.1 localhost localhost.localdomain
::1 localhost localhost.localdomain
此时执行 ping localhost
域名会被解析成 127.0.0.1
;也可以将上述文件中的 127.0.0.1
修改为 8.8.8.8
,那么再执行 ping localhost
,那么会被解析成 8.8.8.8
。
当然,正常人一般不会这么办。
127.0.0.1
这个地址通常分配给 loopback
虚拟网口,用于本机中各个应用之间的网络交互,只要操作系统的协议栈正常,那么 loopback
就能工作。
在 Linux 中可以通过 ifconfig lo
命令查看,可以看到其对应的掩码是 255.0.0.0
,也就意味着 127.*
整个网段是都可以使用的,执行 ping 127.1.1.1
也是可以的。
$ ifconfig lo
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 126182 bytes 522268651 (498.0 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 126182 bytes 522268651 (498.0 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
其中 lo
设备与其它的物理设备相同,也可以通过 ifconfig lo 192.168.128.1
命令进行修改。当然,正常人一般不会这么办。
数据库
这个比较复杂,简单来说,locahost
一般意味着使用的是 Unix Domain Socket
,此时是不会经过网络防火墙的。
如果 skip_name_resolve
配置关闭,那么 127.0.0.1
这类地址也会被解析成 locahost
。
如下详细介绍。
连接方式
如果不指定主机或者使用 -hlocalhost
,实际上会有限尝试使用 Unix Domain Socket 连接 (实际还需要保证没有使用 --protocol=TCP
参数),默认的是 /var/lib/mysql/mysql.sock
,如果 Sock 地址修改了,也可以通过 -S PATH
参数指定。
也就是说,在通过 mysql 客户端访问数据库时,如果指定了 -h<IP>
参数,那么会通过 TCP/IP 方式连接数据库。
最简单的,如果要要强制使用 TCP/IP 连接到本地服务器,那就使用 IP 地址 127.0.0.1
而不是主机名 localhost
。
区别
通过 TCP/IP 方式进来的连接,MySQL 服务器接收到的来源主机是 127.0.0.1
;如果采用的是 UNIX Domain Socket 方式,那么 MySQL 服务器接收到的来源主机是 localhost
。
另外,对于 TCP/IP 方式来说,如果关闭了 skip_name_resolve
选项,那么会尝试将获取到的 IP 地址解析成域名。
skip_name_resolve
参数在调优时,一般建议开启,也就是说禁止域名解析,可以通过SHOW VARIABLES LIKE '%skip_name_resolve%';
命令查看是否开启。 修改时,可以直接在my.cnf
配置文件的[mysqld]
字段中添加skip-name-resolve
即可。
总结
在 MySQL 中 localhost
一般是用来标示 Unix Domain Socket ,如果将 skip_name_resolve
关闭,那么 127.0.0.1
也可能会被反解析成 localhost
。