MySQL -- 权限
创建用户
1 | CREATE USER 'ua'@'%' IDENTIFIED BY 'pa'; |
- 用户名+地址才表示一个用户,
ua@ip1
和ua@ip2
代表的是两个不同的用户 - 在磁盘上,往
mysql.user
表里插入一行,由于没有指定权限,所有表示权限的字段都是N - 在内存里,往数组
acl_users
里插入一个acl_user
对象,该对象的access
字段的值为0
1 | mysql> SELECT * FROM mysql.user WHERE user = 'ua'\G; |
权限范围
全局权限
作用于整个MySQL实例,权限信息保存在mysql.user
,要给用户ua赋一个最高权限的语句如下
1 | GRANT ALL PRIVILEGES ON *.* to 'ua'@'%' WITH GRANT OPTION; |
- 在磁盘上,将
mysql.user
表里的用户'ua'@'%'
这一行中所有表示权限的字段都修改为Y - 在内存里,从数组
acl_users
中找到这个用户对应的对象,将access
值(权限位)修改为二进制的全1 - 这个
GRANT
命令执行完成后,如果有新的客户端使用用户ua登录成功- MySQL会为新连接维护一个线程对象
- 然后从
acl_users
数组里查到用户ua的权限,并将权限值拷贝到这个线程对象中 - 之后在这个连接中执行的语句,所有关于全局权限的判断,都直接使用线程对象内部保存的权限位
GRANT
命令对于全局权限,同时更新了磁盘和内存,命令完成后即时生效- 接下来新创建的连接会使用新的权限
- 对于一个已经存在的连接,它的全局权限不受影响(因为判断时采用的是线程对象内部的权限值)
回收权限
1 | REVOKE ALL PRIVILEGES ON *.* FROM 'ua'@'%'; |
- 在磁盘上,将
mysql.user
表里的用户'ua'@'%'
这一行中所有表示权限的字段都修改为N - 在内存里,从数组
acl_users
中找到这个用户对应的acl_user
对象,将access
的值修改为0
DB权限
让用户'ua'@'%'
拥有库db1的所有权限
1 | GRANT ALL PRIVILEGES ON db1.* to 'ua'@'%' WITH GRANT OPTION; |
- 基于库的权限记录保存在
mysql.db
中,在内存里则保存在数组acl_dbs
中 - 在磁盘上,往
mysql.db
表中插入一行记录,所有权限位的字段设置为Y - 在内存中,增加一个对象到数组
acl_dbs
,该对象的权限位为全1 - 每次需要判断一个用户对一个数据库读写权限的时候,都需要遍历一遍
acl_dbs
数组(多线程共享)- 根据
user
、host
和db
找到匹配的对象,然后根据对象的权限位来判断
- 根据
GRANT
命令对于已经存在的连接的影响,全局权限和基于DB的权限是不一样的- 全局权限:线程私有
- 基于DB的权限:线程共享
1 | mysql> SELECT * FROM mysql.db WHERE user = 'ua'\G; |
操作序列
时刻 | session A | session B | session C |
---|---|---|---|
T1 | CONNECT(root,root); CREATE DATABASE db1; CREATE USER ‘ua‘@’%’ IDENTIFIED BY ‘pa’; GRANT SUPER ON *.* TO ‘ua‘@’%’; GRANT ALL PRIVILEGES ON db1.* TO ‘ua‘@’%’; |
||
T2 | CONNECT(ua,pa) SET GLOBAL sync_binlog=1; (Query OK) CREATE TABLE db1.t(c INT); (Query OK) |
CONNECT(ua,pa) USE db1; |
|
T3 | REVOKE SUPER ON *.* FROM ‘ua‘@’%’; | ||
T4 | SET GLOBAL sync_binlog=1; (Query OK) ALTER TABLE db1.t ENGINE=InnoDB; (Query OK) |
ALTER TABLE t ENGINE=InnoDB; (Query OK) |
|
T5 | REVOKE ALL PRIVILEGES ON db1.* FROM ‘ua‘@’%’; | ||
T6 | SET GLOBAL sync_binlog=1; (Query OK) ALTER TABLE db1.t ENGINE=InnoDB; (ALTER command denied) |
ALTER TABLE t ENGINE=InnoDB;(Query OK) |
SET GLOBAL sync_binlog=1;
这个操作需要SUPER
权限- 虽然用户ua的
SUPER
权限在T3时刻被回收了,但在T4时刻执行SET GLOBAL
的时候,权限验证还是通过了- 这是因为
SUPER
是全局权限,这个信息在线程对象中,而REVOKE
操作影响不了线程对象
- 这是因为
- 在T4时刻去掉用户ua对db1库的所有权限后,session B在T6时刻再操作db1的表时,就会报权限不足
- 这是因为
acl_dbs
是一个全局数组,所有线程判断db权限都会用该数组 - 因此
REVOKE
操作会立马影响到session
- 这是因为
- 特殊逻辑
- 如果当前会话已经在某个db里面,之前use这个db时拿到的库权限就会保存在会话变量中
- session C在T2执行了
USE db1
,拿到这个库的权限,在切换出db1之前,一直对db1有权限
表权限和列权限
1 | CREATE TABLE db1.t1(id INT, a INT); |
- 表权限定义在表
mysql.tables_priv
,列权限定义在表mysql.columns_priv
- 这两类权限组合起来存放在内存的hash结构:
column_priv_hash
- 这两类权限组合起来存放在内存的hash结构:
- 跟DB权限类似,这两个权限在每次
GRANT
的时候都会修改数据表,也会同步修改内存的hash结构 - 因此这两类权限的操作,也会立马影响到已经存在的连接
FLUSH PRIVILEGES
FLUSH PRIVILEGES
命令会清空acl_users
数组(_全局权限_)- 然后从
mysql.user
表中读取数据重新加载,重新构造一个acl_users
数组 - 以数据表中的数据为准,会将全局权限的内存数组重新加载一遍
- 然后从
- 同样的,对于DB权限、表权限和列权限,MySQL也做了同样的处理
- 如果内存的权限数据和磁盘数据表的权限数据相同的话,不需要执行
FLUSH PRIVILEGES
- 如果都是用
GRANT/REVOKE
语句执行的话,内存和数据表的数据应该保持同步更新的 - 正常情况下,在执行
GRANT
命令之后,没有必要跟着执行FLUSH PRIVILEGES
命令
- 如果都是用
使用场景
- 当数据表的权限数据与内存中的权限数据不一致,通过
FLUSH PRIVILEGES
来重建内存数据,达到一致状态 - 这种不一致的状态往往由于不规范的操作导致的,例如直接用DML语句操作系统权限表
不规范操作1
时刻 | client A | client B |
---|---|---|
T1 | CONNECT(root,root) CREATE USER ‘ua‘@’%’ IDENTIFIED BY ‘pa’; |
|
T2 | CONNECT(ua,pa) (Connect OK) DISCONNECT |
|
T3 | DELETE FROM mysql.user WHERE user=’ua’; | |
T4 | CONNECT(ua,pa) (Connect OK) DISCONNECT |
|
T5 | FLUSH PRIVILEGES; | |
T6 | CONNECT(ua,pa) (Access Denied) |
- T3时刻虽然使用了
DELETE
语句删除了用户ua,但在T4时刻,仍然可以用用户ua连接成功- 因为内存中
acl_users
数组中还有这个用户,系统判断时认为用户还正常存在的
- 因为内存中
- T5时刻执行过
FLUSH PRIVILEGES
命令后,内存更新,T6时刻就会报Access Denied
错误 - 直接操作系统权限表是很不规范的操作
不规范操作2
时刻 | client A |
---|---|
T1 | CONNECT(root,root) CREATE USER ‘ua‘@’%’ IDENTIFIED BY ‘pa’; |
T2 | DELETE FROM mysql.user WHERE user=’ua’; |
T3 | GRANT SUPER ON *.* TO ‘ua‘@’%’ WITH GRANT OPTION; (ERROR 1133 (42000): Can’t find any matching row in the user table) |
T4 | CREATE USER ‘ua‘@’%’ IDENTIFIED BY ‘pa’; (ERROR 1396 (HY000): Operation CREATE USER failed for ‘ua‘@’%’) |
- 在T2时刻直接删除了数据表的记录,而内存的数据还存在
- T3时刻给用户ua赋权限失败,因为
mysql.user
表找不到这行记录 - T4时刻也无法重新创建用户ua,因为在内存判断的时候,会认为这个用户还存在
参考资料
《MySQL实战45讲》
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.