MySQL实战45讲学习笔记:第四十二讲

1、本节概述

在 MySQL 里面,grant 语句是用来给用户赋权的。不知道你有没有见过一些操做文档里面提到,grant 以后要立刻跟着执行一个 flush privileges 命令,才能使赋权语句生效。
我最开始使用 MySQL 的时候,就是照着一个操做文档的说明按照这个顺序操做的。mysql

那么,grant 以后真的须要执行 flush privileges 吗?若是没有执行这个 flush 命令的话,赋权语句真的不能生效吗?sql

接下来,我就先和你介绍一下 grant 语句和 flush privileges 语句分别作了什么事情,而后再一块儿来分析这个问题。数据库

为了便于说明,我先建立一个用户:数组

create user 'ua'@'%' identified by 'pa';

这条语句的逻辑是建立一个用户’ua’@’%’,密码是 pa。注意,在 MySQL 里面,用户名 (user)+ 地址 (host) 才表示一个用户,所以 ua@ip1 和 ua@ip2 表明的是两个不一样的用户。bash

这条命令作了两个动做:session

1. 磁盘上,往 mysql.user 表里插入一行,因为没有指定权限,因此这行数据上全部表示权限的字段的值都是 N;
2. 内存里,往数组 acl_users 里插入一个 acl_user 对象,这个对象的 access 字段值为0。ide

图 1 就是这个时刻用户 ua 在 user 表中的状态。工具

图 1 mysql.user 数据行spa

在 MySQL 中,用户权限是有不一样的范围的。接下来,我就按照用户权限范围从大到小的顺序依次和你说明。操作系统

2、全局权限

全局权限,做用于整个 MySQL 实例,这些权限信息保存在 mysql 库的 user 表里。若是我要给用户 ua 赋一个最高权限的话,语句是这么写的:

grant all privileges on *.* to 'ua'@'%' with grant option;

这个 grant 命令作了两个动做:

1. 磁盘上,将 mysql.user 表里,用户’ua’@’%'这一行的全部表示权限的字段的值都修改成‘Y’;
2. 内存里,从数组 acl_users 中找到这个用户对应的对象,将 access 值(权限位)修改成二进制的“全 1”。

在这个 grant 命令执行完成后,若是有新的客户端使用用户名 ua 登陆成功,MySQL 会为新链接维护一个线程对象,而后从 acl_users 数组里查到这个用户的权限,并将权限值拷
贝到这个线程对象中。以后在这个链接中执行的语句,全部关于全局权限的判断,都直接使用线程对象内部保存的权限位。

基于上面的分析咱们能够知道:

1. grant 命令对于全局权限,同时更新了磁盘和内存。命令完成后即时生效,接下来新建立的链接会使用新的权限。
2. 对于一个已经存在的链接,它的全局权限不受 grant 命令的影响。

须要说明的是,通常在生产环境上要合理控制用户权限的范围。咱们上面用到的这个grant 语句就是一个典型的错误示范。若是一个用户有全部权限,通常就不该该设置为全部 IP 地址均可以访问。

若是要回收上面的 grant 语句赋予的权限,你可使用下面这条命令:

revoke all privileges on *.* from 'ua'@'%';

这条 revoke 命令的用法与 grant 相似,作了以下两个动做:

1. 磁盘上,将 mysql.user 表里,用户’ua’@’%'这一行的全部表示权限的字段的值都修改成“N”;

2. 内存里,从数组 acl_users 中找到这个用户对应的对象,将 access 的值修改成 0。

3、db 权限

一、grant 命令工做流程

除了全局权限,MySQL 也支持库级别的权限定义。若是要让用户 ua 拥有库 db1 的全部权限,能够执行下面这条命令:

grant all privileges on db1.* to 'ua'@'%' with grant option;

基于库的权限记录保存在 mysql.db 表中,在内存里则保存在数组 acl_dbs 中。这条grant 命令作了以下两个动做:

1. 磁盘上,往 mysql.db 表中插入了一行记录,全部权限位字段设置为“Y”;
2. 内存里,增长一个对象到数组 acl_dbs 中,这个对象的权限位为“全 1”。

图 2 就是这个时刻用户 ua 在 db 表中的状态。

图 2 mysql.db 数据行

每次须要判断一个用户对一个数据库读写权限的时候,都须要遍历一次 acl_dbs 数组,根据 user、host 和 db 找到匹配的对象,而后根据对象的权限位来判断。
也就是说,grant 修改 db 权限的时候,是同时对磁盘和内存生效的

二、grant 操做对于已经存在的链接的影响,在全局权限和基于 db 的权限效果是不一样的。

grant 操做对于已经存在的链接的影响,在全局权限和基于 db 的权限效果是不一样的。接下来,咱们作一个对照试验来分别看一下。

图 3 权限操做效果

须要说明的是,图中 set global sync_binlog 这个操做是须要 super 权限的。

能够看到,虽然用户 ua 的 super 权限在 T3 时刻已经经过 revoke 语句回收了,可是在T4 时刻执行 set global 的时候,权限验证仍是经过了。这是由于 super 是全局权限,这
个权限信息在线程对象中,而 revoke 操做影响不到这个线程对象。

而在 T5 时刻去掉 ua 对 db1 库的全部权限后,在 T6 时刻 session B 再操做 db1 库的表,就会报错“权限不足”。这是由于 acl_dbs 是一个全局数组,全部线程判断 db 权限
都用这个数组,这样 revoke 操做立刻就会影响到 session B。

这里在代码实现上有一个特别的逻辑,若是当前会话已经处于某一个 db 里面,以前 use这个库的时候拿到的库权限会保存在会话变量中。

你能够看到在 T6 时刻,session C 和 session B 对表 t 的操做逻辑是同样的。可是session B 报错,而 session C 能够执行成功。这是由于 session C 在 T2 时刻执行的 use
db1,拿到了这个库的权限,在切换出 db1 库以前,session C 对这个库就一直有权限。

4、表权限和列权限

除了 db 级别的权限外,MySQL 支持更细粒度的表权限和列权限。其中,表权限定义存放在表 mysql.tables_priv 中,列权限定义存放在表 mysql.columns_priv 中。这两类权
限,组合起来存放在内存的 hash 结构 column_priv_hash 中。

这两类权限的赋权命令以下:

create table db1.t1(id int, a int);

grant all privileges on db1.t1 to 'ua'@'%' with grant option;
GRANT SELECT(id), INSERT (id,a) ON mydb.mytbl TO 'ua'@'%' with grant option;

跟 db 权限相似,这两个权限每次 grant 的时候都会修改数据表,也会同步修改内存中的hash 结构。所以,对这两类权限的操做,也会立刻影响到已经存在的链接。

看到这里,你必定会问,看来 grant 语句都是即时生效的,那这么看应该就不须要执行flush privileges 语句了呀。

答案也确实是这样的。

flush privileges 命令会清空 acl_users 数组,而后从 mysql.user 表中读取数据从新加载,从新构造一个 acl_users 数组。也就是说,以数据表中的数据为准,会将全局权限内存数组从新加载一遍。

一样地,对于 db 权限、表权限和列权限,MySQL 也作了这样的处理。

也就是说,若是内存的权限数据和磁盘数据表相同的话,不须要执行 flush privileges。而若是咱们都是用 grant/revoke 语句来执行的话,内存和数据表原本就是保持同步更新的。

所以,正常状况下,grant 命令以后,没有必要跟着执行 flush privileges 命令。

5、flush privileges 使用场景

那么,flush privileges 是在何时使用呢?显然,当数据表中的权限数据跟内存中的权限数据不一致的时候,flush privileges 语句能够用来重建内存数据,达到一致状态。

这种不一致每每是由不规范的操做致使的,好比直接用 DML 语句操做系统权限表。咱们来看一下下面这个场景:

图 4 使用 flush privileges

能够看到,T3 时刻虽然已经用 delete 语句删除了用户 ua,可是在 T4 时刻,仍然能够用ua 链接成功。缘由就是,这时候内存中 acl_users 数组中还有这个用户,所以系统判断时
认为用户还正常存在。

在 T5 时刻执行过 flush 命令后,内存更新,T6 时刻再要用 ua 来登陆的话,就会报错“没法访问”了。

直接操做系统表是不规范的操做,这个不一致状态也会致使一些更“诡异”的现象发生。好比,前面这个经过 delete 语句删除用户的例子,就会出现下面的状况:

图 5 不规范权限操做致使的异常

能够看到,因为在 T3 时刻直接删除了数据表的记录,而内存的数据还存在。这就致使了:

1. T4 时刻给用户 ua 赋权限失败,由于 mysql.user 表中找不到这行记录;
2. 而 T5 时刻要从新建立这个用户也不行,由于在作内存判断的时候,会认为这个用户还存在。

6、小结

今天这篇文章,我和你介绍了 MySQL 用户权限在数据表和内存中的存在形式,以及grant 和 revoke 命令的执行逻辑。

grant 语句会同时修改数据表和内存,判断权限的时候使用的是内存数据。所以,规范地使用 grant 和 revoke 语句,是不须要随后加上 flush privileges 语句的。

flush privileges 语句自己会用数据表的数据重建一分内存权限数据,因此在权限数据可能存在不一致的状况下再使用。而这种不一致每每是因为直接用 DML 语句操做系统权限表
致使的,因此咱们尽可能不要使用这类语句。

另外,在使用 grant 语句赋权时,你可能还会看到这样的写法:

grant super on *.* to 'ua'@'%' identified by 'pa';

这条命令加了 identified by ‘密码’, 语句的逻辑里面除了赋权外,还包含了:

1. 若是用户’ua’@’%'不存在,就建立这个用户,密码是 pa;
2. 若是用户 ua 已经存在,就将密码修改为 pa。这也是一种不建议的写法,由于这种写法很容易就会不慎把密码给改了。

“grant 以后随手加 flush privileges”,我本身是这么使用了两三年以后,在看代码的时候才发现其实并不须要这样作,那已是 2011 年的事情了。

去年我看到一位小伙伴这么操做的时候,指出这个问题时,他也以为很神奇。由于,他和我同样看的第一份文档就是这么写的,本身也一直是这么用的。

因此,今天的课后问题是,请你也来讲一说,在使用数据库或者写代码的过程当中,有没有遇到过相似的场景:误用了很长时间之后,因为一个契机发现“啊,原来我错了这么久”?

你能够把你的经历写在留言区,我会在下一篇文章的末尾选取有趣的评论和你分享。感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一块儿阅读。

7、上期问题时间

上期的问题是,MySQL 解析 statement 格式的 binlog 的时候,对于 load data 命令,解析出来为何用的是 load data local。

这样作的一个缘由是,为了确保备库应用 binlog 正常。由于备库可能配置了

secure_file_priv=null,因此若是不用 local 的话,可能会导入失败,形成主备同步延迟。

另外一种应用场景是使用 mysqlbinlog 工具解析 binlog 文件,并应用到目标库的状况。你可使用下面这条命令 :

mysqlbinlog $binlog_file | mysql -h$host -P$port -u$user -p$pwd

把日志直接解析出来发给目标库执行。增长 local,就能让这个方法支持非本地的 $host。

评论区留言点赞板:

@poppy 、@库淘淘 两位同窗提到了第一个场景;@王显伟 @lionetes 两位同窗帮忙回答了 @undifined 同窗的疑问,拷贝出来的文件要确保 MySQL 进程能够读。

相关文章
相关标签/搜索