前言
MySQL 鉴权的方式是 Username 和 Host 两个维度的,最近遇到了一些关于登录时到底是被 MySQL 识别为哪个账号的问题,因此简单梳理一下 MySQL 匹配账号的优先级。
简要分析
MySQL 在创建用户的时候,一般是需要指定用户名和来源 IP 的,比如:
mysql> show grants for test@'%';
+----------------------------------+
| Grants for test@% |
+----------------------------------+
| GRANT USAGE ON *.* TO 'test'@'%' |
+----------------------------------+
1 row in set (0.00 sec)
由于 Host 这个字段支持正则匹配,因此这个字段设置为 % 的时候,则代表所有的来源 IP 都能匹配到。
因为这个特性,所以有时候创建的账号会有如下的情况出现:
+------+---------------+
| user | host |
+------+---------------+
| test | % |
| test | 10.104.% |
| test | 10.104.56.136 |
+------+---------------+
参考官方文档的描述:
When multiple matches are possible, the server must determine which of them to use. It resolves this issue as follows:
1.Whenever the server reads the user table into memory, it sorts the rows.
2.When a client attempts to connect, the server looks through the rows in sorted order.
3.The server uses the first row that matches the client host name and user name.
The server uses sorting rules that order rows with the most-specific Host values first.
简而言之:MySQL 会按照 Host 的匹配精度,按降序排列同一个 Username 的所有账号,当 Client 端尝试登录 MySQL 的时候,会按照顺序依次这个 Username 下面所有的 Host 规则,直到匹配成功。
Host 这个字段不仅能填 IP,也能写域名,同样也能利用到正则表达式匹配:
由于域名指向的 IP 受 DNS 的影响,因此多数时候会用 IP,一般来说需要 DNS 来屏蔽一些后端细节的时候才会用域名来作为 Host 字段的值。
测试一下
使用如下操作创建三个用户,密码不做区分:
mysql> create user test@'%' identified by 'test';
Query OK, 0 rows affected (0.00 sec)
mysql> create user test@'10.104.56.136' identified by 'test';
Query OK, 0 rows affected (0.00 sec)
mysql> create user test@'10.104.%' identified by 'test';
Query OK, 0 rows affected (0.01 sec)
mysql>
mysql>
mysql>
mysql> select user,host from mysql.user where user='test' order by host desc;
+------+---------------+
| user | host |
+------+---------------+
| test | 10.104.56.136 |
| test | 10.104.% |
| test | % |
+------+---------------+
3 rows in set (0.00 sec)
mysql>
PS:order by desc 仅为展示上的考虑。
那么从两个不同的机器上(10.104.56.136 和 10.104.43.107)尝试登录 MySQL,按照文档的描述,匹配的优先级应该是:10.104.56.136->10.104.%->%
。那么有一台服务器能完整匹配到 IP,另外一台服务器匹配到的应该是10.104.%
。
实际操作一下看看效果:
root@debian:~# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.104.56.136 ......(省略)
root@debian:~# mysql -h192.168.1.100 -utest -ptest
......(省略)
Server version: 5.7.18-txsql-log 20200331
......(省略)
mysql> show grants;
+----------------------------------------------+
| Grants for test@10.104.56.136 |
+----------------------------------------------+
| GRANT USAGE ON *.* TO 'test'@'10.104.56.136' |
+----------------------------------------------+
1 row in set (0.00 sec)
mysql>
在另外一台机器上:
root@VM-43-107-debian:~# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.104.43.107 ......(省略)
root@debian:~# mysql -h192.168.1.100 -utest -ptest
......(省略)
Server version: 5.7.18-txsql-log 20200331
......(省略)
mysql> show grants;
+-----------------------------------------+
| Grants for test@10.104.% |
+-----------------------------------------+
| GRANT USAGE ON *.* TO 'test'@'10.104.%' |
+-----------------------------------------+
1 row in set (0.00 sec)
mysql>
换成本地的设备再试一下登录:
Tomo@MacBook-Pro ~ % mysql -hgz-cdb.sql.tencentcdb.com -utest -ptest -P59888
......(省略)
Server version: 5.7.18-txsql-log 20200331
......(省略)
mysql> show grants;
+----------------------------------+
| Grants for test@% |
+----------------------------------+
| GRANT USAGE ON *.* TO 'test'@'%' |
+----------------------------------+
1 row in set (0.02 sec)
mysql>
可以看到本地设备的 IP 由于和前两个匹配规则不一致,所以最后匹配到了 % 的这个账号。
总结一下
作为比较保险的办法,尽量少用 Host 来区分不同的账号,直接用不同的 Username 会比较好管理和维护,如果一定需要用 Host 来区分,那么至少也要用不同的密码,防止匹配到了错误的用户,导致权限不足引起业务上的问题。
课外题
localhost 和 127.0.0.1 算是常用的两个 Host,可以作为实践内容动手试一下,体验一下匹配的规律。在 MySQL 看来,精确的域名和精确的 IP 是同等地位的。