MySQL常用运维操作(二):grant赋权语句

创建一个用户:

sql 复制代码
create user 'ua'@'%' identified by 'pa';

这条语句的逻辑是创建一个用户'ua'@'%', 密码是pa。

注:在MySQL里面, 用户名(user)+地址(host)才表示一个用户, 因此 ua@ip1 和 ua@ip2代表的是两个不同的用户。

这条命令做了两个动作:

  1. 磁盘上, 往mysql.user表里插入一行, 由于没有指定权限, 所以这行数据上所有表示权限的字段的值都是N。

  2. 内存里, 往数组acl_users里插入一个acl_user对象, 这个对象的access字段值为0。

下图就是这个时刻用户ua在user表中的状态:

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

全局权限


全局权限, 作用于整个MySQL实例, 这些权限信息保存在mysql库的user表里。

如果我要给用户ua赋一个最高权限的话, 语句是这么写的:

sql 复制代码
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数组里查到这个用户的权限, 并将权限值拷贝到这个线程对象中(也就是说,只要新连接建立,其对应线程对象就会获取该用户的super权限)。之后在这个连接中执行的语句, 所有关于全局权限的判断, 都直接使用线程对象内部保存的权限位。

基于上面的分析可知:

  1. grant 命令对于全局权限, 同时更新了磁盘和内存。 命令完成后即时生效, 接下来新创建的连接会使用新的权限。

  2. 对于一个已经存在的连接, 它的全局权限不受grant命令的影响。

问:grant语句赋予的权限,如何回收?

答:可执行如下命令进行回收:revoke all privileges on *.* from 'ua'@'%';

revoke命令的用法和grant命令类似,做了如下两个动作:

  1. 磁盘上, 将mysql.user表里, 用户'ua'@'%'这一行的所有表示权限的字段的值都修改为"N"。

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

注:revoke命令只能回收当前线程对象的权限。

DB权限


MySQL也支持库级别的权限定义。

如果要让用户ua拥有库db1的所有权限, 可以执行下面这条命令:

sql 复制代码
grant all privileges on db1.* to 'ua'@'%' with grant option;

基于库的权限记录保存在mysql.db表中, 在内存里则保存在数组acl_dbs中。

这条grant命令做了如下两个动作:

  1. 磁盘上, 往mysql.db表中插入了一行记录, 所有权限位字段设置为"Y"。

  2. 内存里, 增加一个对象到数组acl_dbs中, 这个对象的权限位为"全1"。

用户ua在db表中的状态:

重点:每次需要判断一个用户对一个数据库读写权限的时候, 都需要遍历一次acl_dbs数组, 根据user、 host和db找到匹配的对象, 然后根据对象的权限位来判断。(而super权限,只在创建连接时,获取一次)

也就是说,grant修改db权限的时候, 是同时对磁盘和内存生效的。

grant操作对于已存在的连接的影响,在全局权限和基于DB权限的效果不同。

示例:假设有如下序列:

注:图中set global sync_binlog这个操作是需要super权限的。

上图说明:

  1. 在T3时刻,使用revoke语句回收用户ua的super权限。

  2. 在T4时刻,执行set global时,权限验证通过。这是因为super是全局权限,这个权限信息在线程对象中,而revoke操作影响不到session B、session C线程对象。(super权限,只在创建连接时,其线程对象获取一次)

  3. 在T5时刻,使用revoke语句回收用户ua的db1库权限。

  4. 在T6时刻,session B操作db1库,此时会报错:"权限不足"。这是因为acl_dbs是一个全局数组,所有线程判断db权限都用这个数组,这样revoke命令就会马上影响到session B。(db权限,每次需要判断一个用户对一个数据库是否有读写权限时,都需要获取该权限)

  5. 在T6时刻,session C和session B对表t的操作逻辑是一样的。但是session B报错,而session C可以执行成功。这是因为session C在T2时刻执行的use db1,拿到了这个库的权限,在切换db1库之前,session C对这个库就一直有权限。(DB权限在代码实现上有一个特别的逻辑,如果当前会话已经处于某一个db里面,之前use这个库时拿到的权限会保存在会话变量中)

表权限和列权限


MySQL支持更细粒度的表权限和列权限。

表权限定义存放在表mysql.tables_priv中, 列权限定义存放在表mysql.columns_priv中。 这两类权限, 组合起来存放在内存的hash结构column_priv_hash中。

两类权限的赋权命令:

sql 复制代码
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结构。

因此,对这两类权限的操作,也会马上影响到已经存在的连接。

问1:flush privileges命令的作用是什么?

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

问2:既然grant语句都是即时生效的,那是不是就不需要执行flush privileges了呢?

答:是的。如果内存的权限数据和磁盘数据表相同的话, 不需要执行flush privileges。 而如果我们都是用grant/revoke语句来执行的话, 内存和数据表本来就是保持同步更新的。

因此, 正常情况下, grant命令之后, 没有必要跟着执行flush privileges命令。

flush privileges使用场景


当数据表中的权限数据跟内存中的权限数据不一致的时候, flush privileges语句可以用来重建内存数据, 达到一致状态。

问:什么情况下数据表中的权限数据和内存中的权限数据不一致呢?

答:这种不一致往往是由不规范的操作导致的。比如直接用DML语句操作系统权限表,场景如下:

T3时刻虽然已经用delete语句删除了用户ua, 但是在T4时刻, 仍然可以用ua连接成功。 原因就是, 这时候内存中acl_users数组中还有这个用户, 因此系统判断时认为用户还正常存在。

T3时刻虽然已经用delete语句删除了用户ua, 但是在T4时刻, 仍然可以用ua连接成功。 原因就是, 这时候内存中acl_users数组中还有这个用户, 因此系统判断时认为用户还正常存在。

直接操作系统表是不规范的操作,这个不一致状态也会导致一些更"诡异"的现象发生。比如上面的delete语句删除用户的例子,就会出现如下情况:

由于在T3时刻直接删除了数据表的记录, 而内存的数据还存在。 这就导致了:

  1. T4时刻给用户ua赋权限失败, 因为mysql.user表中找不到这行记录。

  2. 而T5时刻要重新创建这个用户也不行, 因为在做内存判断的时候, 会认为这个用户还存在。

小结:思考题


总结规范:
1)grant语句会同时修改数据表和内存, 判断权限的时候使用的是内存数据。 因此, 规范地使用grant和revoke语句, 是不需要随后加上flush privileges语句的。

2)flush privileges语句本身会用数据表的数据重建一份内存权限数据, 所以在权限数据可能存在不一致的情况下再使用。 而这种不一致往往是由于直接用DML语句操作系统权限表导致的, 所以我们尽量不要使用这类语句。

3)grant赋权语句另一种写法如下,这条命令加了identified by'密码', 语句的逻辑里面除了赋权外, 还包含了:

sql 复制代码
grant super on *.* to 'ua'@'%' identified by 'pa';
  • 如果用户'ua'@'%'不存在, 就创建这个用户, 密码是pa。
  • 如果用户ua已经存在, 就将密码修改成pa。

注:这种写法不建议,因为其很容易就会不慎把密码修改了。

相关推荐
aherhuo3 分钟前
持续集成工具Jenkins(一)
linux·运维·jenkins
秋月的私语13 分钟前
c#实现当捕获异常时自动重启程序
运维·c#
计算机毕设定制辅导-无忧学长2 小时前
Nginx 反向代理与负载均衡配置实践
运维·nginx·负载均衡
小屁不止是运维2 小时前
麒麟操作系统服务架构保姆级教程(十三)tomcat环境安装以及LNMT架构
java·运维·架构·tomcat·负载均衡
小安运维日记2 小时前
CKS认证 | Day1 K8s集群部署与安全配置
运维·网络·安全·容器·kubernetes
刘争Stanley4 小时前
Android系统开发(六):从Linux到Android:模块化开发,GKI内核的硬核科普
android·linux·运维·内核·镜像·gki·kmi
南棱笑笑生4 小时前
20250121在Ubuntu20.04.6下使用Linux_Upgrade_Tool工具给荣品的PRO-RK3566开发板刷机
linux·运维·服务器
豆是浪个6 小时前
Linux(Centos 7.6)命令详解:dos2unix
linux·运维·服务器
s_little_monster8 小时前
【Linux】权限
linux·运维·数据库·经验分享·学习·学习方法
致奋斗的我们10 小时前
Linux容器(初学了解)
linux·运维·服务器·网络·容器·shell·openeurler