封面来源:由博主个人绘制,如需使用请联系博主。

本文参考:MySQL数据库教程天花板,mysql安装到mysql高级,强!硬!

1. 用户管理

MySQL 用户可以分为普通用户和 root 用户。root 用户是超级管理员,拥有所有权限,包括创建用户、删除用户和修改用户的密码等管理权限,普通用户只拥有被授予的各种权限。

MySQL 提供了许多语句来管理用户账号,这些语句可以用用来管理包括登录和退出 MySQL 服务器、创建用户、删除用户、密码管理和权限管理等内容。

MySQL 数据库的安全性需要通过账户管理来保证。

1.1 登录MySQL服务器

启动 MySQL 服务后,可以通过 mysql 命令来登录 MySQL 服务器,命令如下:

1
mysql –h hostname|hostIP –P port –u username –p DatabaseName –e "SQL语句"

使用的参数:

  • -h:其后紧跟主机名或者主机 IP,hostname 表示主机,hostIP 表示主机 IP;

  • -P:其后紧跟 MySQL 服务的端口,通过该参数连接到指定的端口。MySQL 服务的默认端口是 3306,不使用该参数时自动连接到 3306 端口,port 表示连接的端口号;

  • -u:其后紧跟用户名,username 为用户名;

  • -p:提示输入密码;

  • DatabaseName:指明登录后使用哪个数据库。如果没有该参数,直接登录到 MySQL 数据库中,然后可以使用 USE 命令选择数据库;

  • -e:后面可以直接追加 SQL 语句。登录 MySQL 服务器后立即执行该 SQL 语句,然后退出 MySQL 服务器。

1
mysql -uroot -p -hlocalhost -P3306 mysql -e "select host,user from user"
+-----------+------------------+
| host      | user             |
+-----------+------------------+
| localhost | mysql.infoschema |
| localhost | mysql.session    |
| localhost | mysql.sys        |
| localhost | root             |
+-----------+------------------+

1.2 创建用户

在 MySQL 数据库中,官方推荐使用 CREATE USER 语句来创建新用户。MySQL8 版本移除了 PASSWORD 加密方法,因此不在推荐使用 INSERT 语句修改 user 表来增加用户。

使用 CREATE USER 语句创建新用户时,必须拥有 CREATE USER 权限。每添加一个用户,CREATE USER 语句就会在 user 表中增加一条记录,但是新创建的用户没有任何权限。

如果创建的用户已经存在,则会报错。

CREATE USER 语句的基本语法形式如下:

1
CREATE USER 用户名 [IDENTIFIED BY '密码'][,用户名 [IDENTIFIED BY '密码']];
  • 用户名参数表示新建用户的账户,由用户(user)和主机名(host)构成;
  • [ ] 表示可选,也就是说可以指定用户登录时需要密码验证,也可以不指定密码验证,使得用户可以直接登录。当然,不指定密码的方式不安全,不推荐使用。如果指定密码值,则需要使用 IDENTIFIED BY 指定明文密码值。
  • CREATE USER 语句可以同时创建多个用户。
1
2
CREATE USER zhang3 IDENTIFIED BY '123123';  # 默认 host 是 %
CREATE USER 'mofan'@'localhost' IDENTIFIED BY '123456';

user 表中的账户是唯一的,这是说 user 表中的 userhost 列是组合唯一的,因此可以存在相同的 userhost 不同。

1.3 修改用户

这里的修改用户特指修改用户名,而不是修改用户的权限。

1
2
3
UPDATE mysql.user SET USER='zhang3' WHERE USER='li4';

FLUSH PRIVILEGES;

1.4 删除用户

可以使用 DROP USER 语句来删除用户,也可以直接在 mysql.user 表中删除用户。

推荐:使用 DROP 方式删除

使用 DROP USER 语句来删除用户时,必须拥有 DROP USER 权限。DROP USER 语句的基本语法形式如下:

1
DROP USER user[,user]...;

比如:

1
2
DROP USER li4;  # 默认删除 host 为 % 的用户
DROP USER 'mofan'@'localhost';

使用 DELETE 方式删除

还可以使用 DELETE 语句直接将用户信息从 mysql.user 表中删除,但必须拥有对 mysql.user 表的 DELETE 权限。基本语法如下:

1
2
3
4
# host 和 user 字段是 user 表的联合主键,需要两个字段的值才能确定一条记录
DELETE FROM mysql.user WHERE host=’hostname’ AND user=’username’;
# 执行完 DELETE 命令后要使用 FLUSH 命令来使用户生效
FLUSH PRIVILEGES;

比如:

1
2
DELETE FROM mysql.user WHERE host='localhost' AND user='Emily';
FLUSH PRIVILEGES;

不推荐使用 DELETE 方式对用户进行删除,系统会有残留信息。而 DROP USER 命令会删除用户以及对应的权限,执行命令后会发现 mysql.user 表和 mysql.db 表的相应记录都消失了。

1.5 设置当前用户密码

适用于 root 用户修改自己的密码,以及普通用户登录后修改自己的密码。

root 用户拥有最高的权限,因此必须保证 root 用户的密码安全。root 用户可以通过多种方式来修改密码,使用 ALTER USER 修改用户密码是 MySQL 官方推荐的方式。此外,也可以通过 SET 语句修改密码。由于 MySQL 8 中已经移除了 PASSWORD() 函数,因此不再使用 UPDATE 语句直接操作用户表修改密码。

旧写法,在 MySQL 5.7 中测试有效:

1
2
# 修改当前用户的密码
SET PASSWORD = PASSWORD('123456');

推荐的写法

  • 使用 ALTER USER 命令修改当前用户密码:
1
ALTER USER USER() IDENTIFIED BY 'new_password';
  • 使用 SET 语句修改当前用户密码。使用 root 用户登录 MySQL 后,可以使用 SET 语句来修改密码:
1
2
# 该语句会自动将密码加密后再赋给当前用户
SET PASSWORD='new_password';

1.6 修改其它用户密码

root 用户不仅可以修改自己的密码,还可以修改其他普通用户的密码。root 用户登录 MySQL 服务器后,可以通过 ALTER 语句和 SET 语句来修改普通用户的密码。由于 PASSWORD() 函数已经移除,因此不再使用 UPDATE 直接操作用户表。

  • 使用 ALTER 语句来修改普通用户的密码:
1
2
3
4
5
# user 表示账户,由用户名和主机名构成
ALTER USER user [IDENTIFIED BY '新密码']
[,user[IDENTIFIED BY '新密码']]...;
# 比如:
ALTER USER 'mofan'@'localhost' IDENTIFIED BY 'mofan212';
  • 使用 SET 命令来修改普通用户的密码。使用 root 用户登录到 MySQL 服务器后,可以使用 SET 语句修改普通用户的密码:
1
SET PASSWORD FOR 'username'@'hostname'='new_password';
  • 使用 UPDATE 语句修改普通用户的密码(不推荐):
1
2
3
UPDATE mysql.user 
SET authentication_string=PASSWORD("123456")
WHERE user = "username" AND host = "hostname";

1.7 MySQL8 的密码管理(了解)

密码过期策略

在 MySQL 中,数据库管理员可以手动设置账号密码过期,也可以建立一个自动密码过期策略。

过期策略可以是全局的,也可以为每个账号单独设置过期策略。

手动设置密码立马过期:

1
ALTER USER user PASSWORD EXPIRE;

比如:

1
ALTER USER 'mofan'@'localhost' PASSWORD EXPIRE;

该语句将 mofan 的密码设置为过期,mofan 用户仍然可以登录进数据库,但是无法进行查询,只用重新设置了新密码才能正常使用。

手动全局设置指定时间过期方式:

如果密码使用的时间大于允许的时间,服务器会自动将其设置为过期,无需手动设置。

MySQL 使用 default_password_lifetime 系统变量建立全局过期密码策略。

  • 它的默认值是 0,表示禁用自动密码过期。
  • 它允许的值是正整数 N,表示密码的生存期,密码必须每隔 N 天进行修改。

可以使用 SQL 语句更改该变量的值并持久化:

1
2
# 建立全局策略,设置密码每隔180天过期
SET PERSIST default_password_lifetime = 180;

还可以直接在配置文件 my.cnf 中配置:

1
2
[mysqld]
default_password_lifetime=180 # 建立全局策略,设置密码每隔180天过期

手动单独设置指定时间过期方式:

每个账号既可延用全局密码过期策略,也可单独设置策略。在 CREATE USERALTER USER 语句上加入 PASSWORD EXPIRE 选项即可实现单独设置策略。比如:

1
2
3
4
5
6
7
8
9
10
11
#设置mofan账号密码每90天过期:
CREATE USER 'mofan'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;
ALTER USER 'mofan'@'localhost' PASSWORD EXPIRE INTERVAL 90 DAY;

#设置密码永不过期:
CREATE USER 'mofan'@'localhost' PASSWORD EXPIRE NEVER;
ALTER USER 'mofan'@'localhost' PASSWORD EXPIRE NEVER;

#延用全局密码过期策略:
CREATE USER 'mofan'@'localhost' PASSWORD EXPIRE DEFAULT;
ALTER USER 'mofan'@'localhost' PASSWORD EXPIRE DEFAULT;

密码重用策略

MySQL 限制使用已用过的密码。重用限制策略基于 密码更改的数量使用的时间。重用策略可以是全局的,也可以给每个账号单独设置。

账号的历史密码包含过去该账号所使用的密码,MySQL 基于以下规则来限制密码重用:

  • 如果账号密码限制基于 密码更改的数量,那么新密码不能从最近限制的密码数量中选择。例如密码更改的最小值为 3,那么新密码不能与最近 3 个密码中的任何一个相同。
  • 如果账号密码限制基于 时间,那么新密码不能从规定的时间内选择。例如,如果密码重用周期为 60 天,那么新密码不能从最近 60 天内使用的密码中选择。

MySQL 使用 password_historypassword_reuse_interval 系统变量设置密码重用策略:

  • password_history:密码重用的数量;
  • password_reuse_interval:密码重用的周期。

手动全局设置密码重用方式:

可以通过 SQL 语句进行设置:

1
2
3
4
# 设置不能选择最近使用过的6个密码
SET PERSIST password_history = 6;
# 设置不能选择最近一年内的密码
SET PERSIST password_reuse_interval = 365;

也可以在 my.ini 配置文件中设置:

1
2
3
[mysqld]
password_history=6
password_reuse_interval=365

手动单独设置密码重用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#不能使用最近5个密码:
CREATE USER 'mofan'@'localhost' PASSWORD HISTORY 5;
ALTER USER 'mofan'@'localhost' PASSWORD HISTORY 5;

#不能使用最近365天内的密码:
CREATE USER 'mofan'@'localhost' PASSWORD REUSE INTERVAL 365 DAY;
ALTER USER 'mofan'@'localhost' PASSWORD REUSE INTERVAL 365 DAY;

#既不能使用最近5个密码,也不能使用365天内的密码
CREATE USER 'mofan'@'localhost'
PASSWORD HISTORY 5
PASSWORD REUSE INTERVAL 365 DAY;

ALTER USER 'mofan'@'localhost'
PASSWORD HISTORY 5
PASSWORD REUSE INTERVAL 365 DAY;

2. 权限管理

2.1 权限列表

官方文档:MySQL :: MySQL 8.0 Reference Manual :: 6.2.2 Privileges Provided by MySQL

MySQL 到底都有哪些权限呢?

1
show privileges;
权限 user 表中对应的列 权限范围
ALL [PRIVILEGES\] Synonym for “all privileges” Server administration
ALTER Alter_priv Tables
ALTER ROUTINE Alter_routine_priv Stored routines
CREATE Create_priv Databases, tables, or indexes
CREATE ROLE Create_role_priv Server administration
CREATE ROUTINE Create_routine_priv Stored routines
CREATE TABLESPACE Create_tablespace_priv Server administration
CREATE TEMPORARY TABLES Create_tmp_table_priv Tables
CREATE USER Create_user_priv Server administration
CREATE VIEW Create_view_priv Views
DELETE Delete_priv Tables
DROP Drop_priv Databases, tables, or views
DROP ROLE Drop_role_priv Server administration
EVENT Event_priv Databases
EXECUTE Execute_priv Stored routines
FILE File_priv File access on server host
GRANT OPTION Grant_priv Databases, tables, or stored routines
INDEX Index_priv Tables
INSERT Insert_priv Tables or columns
LOCK TABLES Lock_tables_priv Databases
PROCESS Process_priv Server administration
PROXY See proxies_priv table Server administration
REFERENCES References_priv Databases or tables
RELOAD Reload_priv Server administration
REPLICATION CLIENT Repl_client_priv Server administration
REPLICATION SLAVE Repl_slave_priv Server administration
SELECT Select_priv Tables or columns
SHOW DATABASES Show_db_priv Server administration
SHOW VIEW Show_view_priv Views
SHUTDOWN Shutdown_priv Server administration
SUPER Super_priv Server administration
TRIGGER Trigger_priv Tables
UPDATE Update_priv Tables or columns
USAGE Synonym for “no privileges” Server administration
  • CREATEDROP 权限,可以创建新的数据库和表,或删除(移掉)已有的数据库和表。如果将 MySQL 数据库中的 DROP 权限授予某用户,该用户就可以删除 MySQL 访问权限保存的数据库。
  • SELECTINSERTUPDATEDELETE 权限允许在一个数据库现有的表上实施操作。
  • SELECT 权限只有在它们真正从一个表中检索行时才被用到。
  • INDEX 权限允许创建或删除索引,INDEX 适用于已有的表。如果具有某个表的 CREATE 权限,那么可以在CREATE TABLE 语句中包括索引定义。
  • ALTER 权限可以使用 ALTER TABLE 来更改表的结构和重新命名表。
  • CREATE ROUTINE 权限用来创建保存的程序(函数和程序),ALTER ROUTINE 权限用来更改和删除保存的程序,EXECUTE 权限用来执行保存的程序。
  • GRANT 权限允许授权给其他用户,可用于数据库、表和保存的程序。
  • FILE 权限使用户可以使用 LOAD DATA INFILESELECT...INTO OUTFILE 语句读或写服务器上的文件,任何被授予 FILE 权限的用户都能读或写 MySQL 服务器上的任何文件(说明用户可以读任何数据库目录下的文件,因为服务器可以访问这些文件)。

MySQL 的权限如何分布:

权限分布 可能设置的权限
表权限 SELECT、INSERT、UPDATE、DELETE、CREATE、DROP、GRANT、REFERENCES、INDEX、ALTER
列权限 SELECT、INSERT、UPDATE、REFERENCES
过程权限 EXECUTE、ALTER ROUTINE、GRANT

2.2 授予权限的原则

权限控制主要是出于安全因素,因此需要遵循以下几个经验原则:

  1. 只授予能满足需要的最小权限,防止用户干坏事。比如用户只是需要查询,那就只给 SELECT 权限,不要给用户赋予 UPDATE、INSERT 或者 DELETE 权限;
  2. 创建用户的时候限制用户的登录主机,一般是限制成指定 IP 或者内网 IP 段。
  3. 为每个用户设置满足密码复杂度的密码。
  4. 定期清理不需要的用户,回收权限或者删除用户。

2.3 授予权限

给用户授权的方式有 2 种,分别是通过把 角色赋予用户给用户授权直接给用户授权。用户是数据库的使用者,可以通过给用户授予访问数据库中资源的权限,来控制使用者对数据库的访问,消除安全隐患。

授权命令:

1
GRANT 权限1, 权限2, ...权限n ON 数据库名称.表名称 TO 用户名@用户地址 [IDENTIFIED BY '密码口令'];

在授权时如果发现没有指定的用户,会直接创建一个用户。

比如:

  • 给 li4 用户用本地命令行方式,授予 test_table 这个库下的所有表的插删改查的权限
1
GRANT SELECT, INSERT, DELETE, UPDATE ON test_table.* TO li4@localhost;
  • 授予通过网络方式登录的 joe 用户 ,对所有库所有表的全部权限,密码设为 123。注意这里唯独不包括 GRANT 的权限:
1
GRANT ALL PRIVILEGES ON *.* TO joe@'%' IDENTIFIED BY '123';

ALL PRIVILEGES 表示所有权限,也可以指定 SELECTUPDATE 等权限。ON 用来指定权限针对哪些库和表。. 前面的 * 用来指定数据库名,后面的 * 用来指定表名。TO 表示将权限授予哪个用户。li4@localhost 表示 li4 用户,@ 后面跟限制的主机,可以是 IP、IP 段、域名以及 %% 表示任何地方。在不同的版本中 % 的含义不同,有些版本中 % 可能不包括本地,此时再加上一个 localhost 的用户就行。

如果需要授予包括 GRANT 的权限,添加参数 WITH GRANT OPTION 选项即可,表示该用户可以将自己拥有的权限授予给别人。经常有人在创建操作用户时不指定 WITH GRANT OPTION 选项导致后来该用户不能使用 GRANT 命令创建用户或者给其它用户授权。

权限叠加:可以使用 GRANT 重复给用户添加权限。比如先给用户添加 SELECT 权限,然后又给用户添加 INSERT 权限,那么该用户就同时拥有了 SELECTINSERT 权限。

在开发应用的时候,经常会遇到一种需求,就是要根据用户的不同,对数据进行横向和纵向的分组。

  • 所谓横向的分组,就是指用户可以接触到的数据的范围,比如可以看到哪些表的数据;

  • 所谓纵向的分组,就是指用户对接触到的数据能访问到什么程度,比如能看、能改,甚至是删除。

2.4 查看权限

查看当前用户权限:

1
2
3
4
5
SHOW GRANTS;
# 或
SHOW GRANTS FOR CURRENT_USER;
# 或
SHOW GRANTS FOR CURRENT_USER();

查看某用户的全局权限:

1
SHOW GRANTS FOR 'user'@'主机地址';

2.5 收回权限

收回权限就是取消已经授予用户的某些权限。收回用户不必要的权限可以在一定程度上保证系统的安全性。MySQL 中使用 REVOKE 语句取消用户的某些权限。使用 REVOKE 收回权限之后,用户账户的记录将从 db、host、tables_priv 和 columns_priv 表中删除,但是用户账户记录仍然在 user 表中保存(删除 user 表中的账户记录使用 DROP USER 语句)。

注意: 在将用户账户从 user 表删除之前,应该收回相应用户的所有权限。

基本语法如下:

1
REVOKE 权限1, 权限2, ...权限n ON 数据库名称.表名称 FROM 用户名@用户地址;

比如:

1
2
3
4
5
# 收回全库全表的所有权限
REVOKE ALL PRIVILEGES ON *.* FROM joe@'%';

# 收回 mysql 库下的所有表的插删改查权限
REVOKE SELECT, INSERT, UPDATE, DELETE ON mysql.* FROM joe@localhost;

权限收回后,需要用户重新登录才会生效。

在直接收回所有权限时,可能会提示:

ERROR 1227 (42000): Access denied; you need (at Least one of) the SYSTEM_USER privilege(s) for this operation

此时给 root 用户授予 SYSTEM_USER 权限后再操作即可:

1
grant SYSTEM_USER on *.* to 'root '@'%' ;

一些程序员喜欢使用 root 超级用户来访问数据库,完全把权限控制放在应用层面实现,但建议尽量使用数据库自己的角色和用户机制来控制访问权限,不要轻易用 root 账号。因为 root 账号密码放在代码里面不安全,一旦泄露,数据库就会完全失去保护。

3. 权限表

MySQL 服务器通过权限表来控制用户对数据库的访问,权限表存放在数据库 mysql 中。MySQL 数据库系统会根据这些权限表的内容为每个用户赋予相应的权限。这些权限表中最重要的是 user 表、db 表。 除此之外,还有 tables_priv 表、columns_ priv 表和 proc_ priv 表等。在 MySQL 启动时,服务器将这些数据库表中权限信息的内容读入内存。

表名 描述
user 用户账号及权限信息
global_grants 动态全局授权
db 数据库层级的权限
tables_priv 表层级的权限
columns_priv 列层级的权限
procs_priv 存储的过程和函数的权限
proxics_priv 代理用户的权限
default_roles 账号连接并认证后默认授予的角色
role_edges 角色子图的便边界
password_history 密码更改信息

3.1 user 表

user 表是 MySQL 中最重要的一个权限表,记录用户账号和权限信息,有 49 个字段。如下图:

user表的表结构

这些字段可以分成4类,分别是范围列(或用户列)、权限列、安全列和资源控制列。

范围列(或用户列)

host:连接类型。

  • % 表示所有远程通过 TCP方式的连接

  • IP 地址,如(192.168.1.2、127.0.0.1)通过制定 IP 地址进行的 TCP 方式的连接

  • 机器名,通过制定网络中的机器名进行的TCP方式的连接

  • ::1,IPv6 的本地 IP 地址,等同于 IPv4 的 127.0.0.1

  • localhost 本地方式通过命令行方式的连接 ,比如 mysql -u xxx -p xxx 方式的连接。

user:用户名,同一用户通过不同方式链接的权限是不一样的。

password:密码。

  • 所有密码串通过 password(明文字符串) 函数生成密文字符串。MySQL 8.0 在用户管理方面增加了角色管理,默认的密码加密方式也做了调整,由之前的 SHA1 改为了 SHA2 ,不可逆 。同时加上 MySQL 5.7 的禁用用户和用户过期的功能,MySQL 在用户管理方面的功能和安全性都较之前版本大大的增强了。
  • MySQL 5.7 及之后版本的密码保存到 authentication_string 字段中不再使用 password 字段。

权限列

Grant_priv 字段表示是否拥有 GRANT 权限。

Shutdown_priv 字段表示是否拥有停止 MySQL 服务的权限

Super_priv 字段表示是否拥有超级权限。

Execute_priv 字段表示是否拥有 EXECUTE 权限。拥有 EXECUTE 权限,可以执行存储过程和函数。

Select_privInsert_priv 等为该用户所拥有的权限。

资源控制列

资源控制列的字段用来限制用户使用的资源,包含 4 个字段,分别为:

  1. max_questions,用户每小时允许执行的查询操作次数;
  2. max_updates,用户每小时允许执行的更新操作次数;
  3. max_connections,用户每小时允许执行的连接操作次数;
  4. max_user_connections,用户允许同时建立的连接次数。
1
2
3
4
5
6
7
# 查看字段
DESC mysql.user;
# 查看用户, 以列的方式显示数据
SELECT * FROM mysql.user \G;
# 查询特定字段
SELECT host, user, authentication_string, select_priv, insert_priv, drop_priv
FROM mysql.user;

3.2 db 表

db 表是 MySQL 数据中非常重要的权限表。db 表中存储了用户对某个数据库的操作权限,决定用户能从哪个主机存取哪个数据库。

user 表中的权限是针对所有数据库的,如果 user 表中的 Select_ priv 字段取值为 Y,那么该用户可以查询所有数据库中的表。如果希望用户只对某个数据库有操作权限,那么需要将 user 表中对应的权限设置为N,然后在 db 表中设置对应数据库的操作权限。

使用 DESCRIBE 查看 db 表的基本结构:

1
DESCRIBE mysql.db;
+-----------------------+---------------+------+-----+---------+-------+
| Field                 | Type          | Null | Key | Default | Extra |
+-----------------------+---------------+------+-----+---------+-------+
| Host                  | char(255)     | NO   | PRI |         |       |
| Db                    | char(64)      | NO   | PRI |         |       |
| User                  | char(32)      | NO   | PRI |         |       |
| Select_priv           | enum('N','Y') | NO   |     | N       |       |
| Insert_priv           | enum('N','Y') | NO   |     | N       |       |
| Update_priv           | enum('N','Y') | NO   |     | N       |       |
| Delete_priv           | enum('N','Y') | NO   |     | N       |       |
| Create_priv           | enum('N','Y') | NO   |     | N       |       |
| Drop_priv             | enum('N','Y') | NO   |     | N       |       |
| Grant_priv            | enum('N','Y') | NO   |     | N       |       |
| References_priv       | enum('N','Y') | NO   |     | N       |       |
| Index_priv            | enum('N','Y') | NO   |     | N       |       |
| Alter_priv            | enum('N','Y') | NO   |     | N       |       |
| Create_tmp_table_priv | enum('N','Y') | NO   |     | N       |       |
| Lock_tables_priv      | enum('N','Y') | NO   |     | N       |       |
| Create_view_priv      | enum('N','Y') | NO   |     | N       |       |
| Show_view_priv        | enum('N','Y') | NO   |     | N       |       |
| Create_routine_priv   | enum('N','Y') | NO   |     | N       |       |
| Alter_routine_priv    | enum('N','Y') | NO   |     | N       |       |
| Execute_priv          | enum('N','Y') | NO   |     | N       |       |
| Event_priv            | enum('N','Y') | NO   |     | N       |       |
| Trigger_priv          | enum('N','Y') | NO   |     | N       |       |
+-----------------------+---------------+------+-----+---------+-------+

用户列

db 表用户列有 3 个字段,分别是 HostUserDb。这 3 个字段分别表示主机名、用户名和数据库名,表示从某个主机连接某个用户对某个数据库的操作权限,这 3 个字段的组合构成了 db 表的主键。

权限列

Create_routine_privAlter_routine_priv 这两个字段决定用户是否具有创建和修改存储过程的权限。

3.3 tables_priv 表和 columns_priv 表

tables_priv 表用来对表设置操作权限,columns_priv 表用来对表的某一列设置权限。

tables_priv 表有8个字段,分别是 Host、Db、User、Table_name、Grantor、Timestamp、Table_priv 和 Column_priv,各个字段说明如下:

  • Host 、Db 、User 和 Table_name 四个字段分别表示主机名、数据库名、用户名和表名。

  • Grantor 表示修改该记录的用户。

  • Timestamp 表示修改该记录的时间。

  • Table_priv 表示对象的操作权限。包括 Select、Insert、Update、Delete、Create、Drop、Grant、
    References、Index 和 Alter。

  • Column_priv 字段表示对表中的列的操作权限,包括 Select、Insert、Update 和 References。

columns_ priv 表只有 7 个字段,分别是 Host、Db、User、Table_name、Column_name、Timestamp、Column_ priv。 其中,Column_ name 用来指定对哪些数据列具有操作权限。

3.4 procs_priv 表

procs_priv 表可以对存储过程和存储函数设置操作权限,表结构如图:

procs_priv表的表结构

procs_priv 表包含 8 个字段,分别是 Host、Db、User、Routine_ name、Routine_type、Grantor、 Proc_priv 和 Timestamp,各个字段的说明如下:

  • Host、 Db 和 User 字段分别表示主机名、数据库名和用户名。
  • Routine_name 表示存储过程或函数的过程。
  • Routine_type 表示存储过程或函数的类型。Routine_type 字段有两个值,分别是 FUNTION 和 PROCEDURE,前者表示存储函数,后者表示存储过程。
  • Grantor 是插入或修改该记录的用户。
  • Proc_priv 表示拥有的权限,包括 Execute、Alter Routine 和 Grant 三种。
  • Timestamp 表示记录更新时间。

4. 访问控制(了解)

正常情况下,并不希望每个用户都可以执行所有的数据库操作。当 MySQL 允许一个用户执行各种操作时, 它将首先核实该用户向 MySQL 服务器发送的连接请求,然后确认用户的操作请求是否被允许。这个过程称为 MySQL 中的访问控制过程。MySQL 的访问控制分为两个阶段:连接核实阶段和请求核实阶段。

4.1 连接核实阶段

当用户试图连接 MySQL 服务器时,服务器基于用户的身份以及用户是否能提供正确的密码验证身份来确定接受或拒绝连接。即客户端用户会在连接请求中提供用户名、主机地址、用户密码,MySQL 服务器接收到用户请求后,会使用 user 表中的 Host、User 和 authentication_string 这 3 个字段匹配客户端提供信息。

服务器只有在 user 表记录的 Host 和 User 字段匹配客户端主机名和用户名,并且提供正确的密码时才接受连接。如果连接核实没有通过,服务器就完全拒绝访问;否则服务器接受连接,然后进入阶段 2 等待用户请求。

4.2 请求核实阶段

一旦建立了连接,服务器就进入了访问控制的阶段 2,也就是请求核实阶段。对此连接上进来的每个请求,服务器会检查该请求要执行什么操作、是否有足够的权限来执行它,这正是需要授权表中的权限列发挥作用的地方。这些权限可以来自 user、db、table_priv 和 column_priv 表。

确认权限时,MySQL 首先检查 user 表,如果指定的权限没有在 user 表中被授予,那么 MySQL 就会继续检查 db 表,db 表是下一安全层级,其中的权限限定于数据库层级,在该层级的 SELECT 权限允许用户查看指定数据库的所有表中的数据;如果在该层级没有找到限定的权限,则 MySQL 继续检查 tables_priv 表以及 columns_priv 表,如果所有权限表都检查完毕,但还是没有找到允许的权限操作,MySQL 将返回错误信息,显示用户请求的操作不能执行,操作失败。

MySQL请求核实阶段过程图

MySQL 通过向下层级的顺序(从 user 表到 columns_priv 表)检查权限表,但并不是所有的权限都要执行该过程。例如,一个用户登录到 MySQL 服务器之后只执行对 MySQL 的管理操作,此时只涉及管理权限,因此 MySQL 只检查 user 表。另外,如果请求的权限操作不被允许,MySQL 也不会继续检查下一层级的表。

5. 角色管理

5.1 角色的理解

角色是在 MySQL 8.0 中引入的新功能。在 MySQL 中,角色是权限的集合,可以为角色添加或移除权限。用户可以被赋予角色,同时也被授予角色包含的权限。对角色进行操作需要较高的权限,并且像用户账户一样,角色可以拥有授予和撤消的权限。

引入角色的目的是方便管理拥有相同权限的用户。恰当的权限设定,可以确保数据的安全性,这是至关重要的。

角色的理解

5.2 创建角色

创建角色使用 CREATE ROLE 语句,语法如下:

1
CREATE ROLE 'role_name'[@'host_name'] [,'role_name'[@'host_name']]...

角色名称的命名规则和用户名类似。如果 host_name 省略,默认为 %role_name 不可省略,不可为空。也可以一次性创建多个用户,彼此以 , 隔开即可。

比如创建一个经理的角色:

1
CREATE ROLE 'manager';

没有写主机名,默认是通配符 %,意思是该用户可以从任何一台主机上登录数据库。

也可以写上主机名,比如:

1
CREATE ROLE 'manager'@'localhost';

这表示该用户只能从数据库服务器运行的这台计算机登录。

5.3 给角色赋予权限

创建角色之后,默认这个角色是没有任何权限的,因此需要给角色授权。基本语法如下:

1
GRANT privileges ON table_name TO 'role_name'[@'host_name'];

上述语句中 privileges 代表权限的名称,多个权限以逗号隔开,可使用 SHOW 语句查询权限名称。

比如查看权限列表信息:

1
SHOW privileges \G;

比如给经理角色授予商品信息表、盘点表和应付账款表的只读权限:

1
2
3
GRANT SELECT ON demo.settlement TO 'manager';
GRANT SELECT ON demo.goodsmaster TO 'manager';
GRANT SELECT ON demo.invcount TO 'manager';

5.4 查看角色的权限

赋予角色权限之后,可以通过 SHOW GRANTS 语句,来查看权限是否创建成功:

1
SHOW GRANTS FOR 'manager';
+-------------------------------------------------------+
| Grants for manager@%                                  |
+-------------------------------------------------------+
| GRANT USAGE ON *.* TO `manager`@`%`                   |
| GRANT SELECT ON `demo`.`goodsmaster` TO `manager`@`%` |
| GRANT SELECT ON `demo`.`invcount` TO `manager`@`%`    |
| GRANT SELECT ON `demo`.`settlement` TO `manager`@`%`  |
+-------------------------------------------------------+

只要创建了一个角色,系统就会自动分配一个 USAGE 权限,意思是连接登录数据库的权限。最后三行代表了为角色 manager 授予的权限,也就是对商品信息表、盘点表和应付账款表的只读权限。

5.5 回收角色的权限

角色授权后,可以对角色的权限进行维护,对权限进行添加或撤销。添加权限使用 GRANT 语句,与角色授权相同。回收角色或角色权限使用 REVOKE 语句。

修改了角色的权限,会影响拥有该角色的账户的权限。回收角色权限的语法如下:

1
REVOKE privileges ON tablename FROM 'rolename';

比如:

1
2
3
4
# 回收 school_write 角色的权限
REVOKE INSERT, UPDATE, DELETE ON school.* FROM 'school_write';
# 再查看其对应的权限
SHOW GRANTS FOR 'school_write';

5.6 删除角色

当需要对业务重新整合的时候,可能就需要对之前创建的角色进行清理,删除一些不会再使用的角色。语法如下:

1
DROP ROLE role [,role2]...

如果删除了角色,那么用户也就失去了通过这个角色所获得的所有权限。

比如:

1
DROP ROLE 'school_read';

5.7 给用户赋予角色

角色创建并授权后,要赋予用户并处于 激活状态 才能发挥作用。给用户添加角色可使用 GRANT 语句,语法如下:

1
GRANT role [,role2,...] TO user [,user2,...];

上述语句中,role 代表角色,user 代表用户。可将多个角色同时赋予多个用户,用逗号隔开即可。

比如为 mofan 添加 school_read 角色:

1
2
3
4
# 添加 school_read 角色
GRANT 'school_read' TO 'mofan'@'localhost';
# 使用 SHOW 查看是否添加成功
SHOW GRANTS FOR 'mofan'@'localhost';

使用 mofan 用户登录,然后查询当前回话的角色,如果角色未激活,结果将显示 NONE

1
SELECT CURRENT_ROLE();
+----------------+
| current_role() |
+----------------+
| NONE           |
+----------------+

如果此时使用赋予了角色的用户去登录、操作,会发现这个账号没有任何权限。这是因为在 MySQL 中创建角色之后,默认都是没有被激活,也就是不能用, 必须手动激活, 之后用户才能拥有角色对应的权限。

5.8 激活角色

方式一:使用 SET DEFAULT ROLE 命令激活角色

1
SET DEFAULT 'school_read' ALL TO 'mofan'@'localhost';

也可以同时为多个用户默认激活所有已拥有的角色:

1
2
3
4
5
SET DEFAULT 'school_read' ALL TO 
'dev1'@'localhost',
'read_user1'@'localhost',
'read_user2'@'localhost',
'rw_user1'@'localhost';

用户需要退出并重新登录才能看到被赋予的角色。

方式2:将 activate_all_roles_on_login 设置为 ON

未设置前:

1
SHOW VARIABLES LIKE 'activate_all_roles_on_login';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| activate_all_roles_on_login | OFF   |
+-----------------------------+-------+

进行设置:

1
SET GLOBAL activate_all_roles_on_login = ON;

该语句的意思是对所有角色永久激活。运行这条语句之后,用户才真正拥有了赋予角色的所有权限。

5.9 撤销用户的角色

基本语法如下:

1
REVOKE role FROM user;

比如:

1
2
3
4
# 撤销 mofan 的 school_read 角色
REVOKE 'school_read' FROM 'mofan'@'localhost';
# 查看 mofan 用户的角色信息
SHOW GRANTS FOR 'mofan'@'localhost';

5.10 设置强制角色

强制角色是给每个创建账户的默认角色,不需要手动设置。强制角色无法被 REVOKEDROP

可以在服务启动前修改 my.conf 文件:

1
2
[mysqld]
mandatory_roles='role1,role2@localhost'

也可以在运行时设置:

1
2
3
4
# 系统重启后仍然有效
SET PERSIST mandatory_roles = 'role1,role2@localhost';
# 系统重启后失效
SET GLOBAL mandatory_roles = 'role1,role2@localhost';

6. 配置文件的使用

6.1 配置文件格式

与在命令行中指定启动选项不同的是,配置文件中的启动选项被划分为若干组,每个组有一个组名,用中括号 [] 括起来,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[server]
(具体的启动选项...)

[mysqld]
(具体的启动选项...)

[mysqld_safe]
(具体的启动选项...)

[client]
(具体的启动选项...)

[mysql]
(具体的启动选项...)

[mysqladmin]
(具体的启动选项...)

像这个配置文件里就定义了许多个组,组名分别是 servermysqldmysqld_safeclientmysqlmysqladmin。每个组下边可以定义若干个启动选项,以 [server] 组为例来看一下填写启动选项的形式:

1
2
3
4
5
6
[server]
# 该选项不需要选项值
option1
# 该选项需要选项值
option2=value2
...

在配置文件中指定启动选项的语法类似于命令行语法,但是配置文件中指定的启动选项不允许加 -- 前缀,并且每行只指定一 个选项,而且 = 周围可以有空白字符(命令行中选项名、=、选项值之间不允许有空白字符)。另外,在配置文件中可以使用 # 来添加注释,从 # 出现直到行尾的内容都属于注释内容,读取配置文件时会忽略这些注释内容。

6.2 启动命令与选项组

配置文件中不同的选项组是给不同的启动命令使用的。不过有两个选项组比较特别:

  • [server] 组下边的启动选项将作用于 所有的服务器 程序。
  • [client] 组下边的启动选项将作用于 所有的客户端 程序。
启动命令 类别 能读取的组
mysqld 启动服务器 [mysqld][server]
mysqld_safe 启动服务器 [mysqld][server][mysqld_safe]
mysql.server 启动服务器 [mysqld][server][mysql.server]
mysql 启动客户端 [mysql][client]
mysqladmin 启动客户端 [mysqladmin][client]
mysqldump 启动客户端 [mysqldump][client]

比如在 /etc/mysql/my.cnf 这个配置文件中添加一些内容:

1
2
3
[server]
skip-networking
default-storage-engine=MyISAM

然后直接用 mysqld 启动服务器程序:

1
mysqld

虽然在命令行没有添加启动选项,但在程序启动时,会默认到上边提到的配置文件路径下查找配置文件,其中就包括 /etc/my.cnf。又由于 mysqld 命令可以读取 [server] 选项组的内容,所以 skip-networkingdefault-storage-engine=MyISAM 这两个选项是生效的,如果把这些启动选项放在 [client] 组里再用 mysqld 启动服务器程序,是不会生效的。

6.3 特定 MySQL 版本的专用选项组

可以在选项组的名称后加上特定的 MySQL 版本号,比如对于 [mysqld] 选项组来说,可以定义一个 [mysqld-5.7] 的选项组,它的含义和 [mysqld] 一样,只不过只有版本号为 5.7mysqld 程序才能使用这个选项组中的选项。

6.4 同一个配置文件中多个组的优先级

同一个命令可以访问配置文件中的多个组,比如 mysqld 可以访问 [mysqld][server] 组,如果在同一个配置文件中,比如 ~/.my.cnf 文件中的组里出现了同样的配置项:

1
2
3
4
5
[server]
default-storage-engine=InnoDB

[mysqld]
default-storage-engine=MyISAM

此时将以最后一个出现的组中的启动选项为准,上方例子中就以 [mysqld] 组中的配置项为准。

6.5 命令行和配置文件中选项的区别

在命令行上指定的绝大部分启动选项都可以放到配置文件中,但是有一些选项是专门为命令行设计的,比方说 defaults-extra-filedefaults-file 这样的选项本身就是为了指定配置文件路径的,放在配置文件中使用没有任何意义。

如果同一个启动选项既出现在命令行中,又出现在配置文件中,那么以命令行中的启动选项为准。比如在配置文件中写了:

1
2
[server]
default-storage-engine=InnoDB

而启动命令是:

1
mysq1.server start --default-storage-engine=MyISAM

那么 default-storage-engine 最终的值是 MyISAM

7. 系统变量

MySQL 的系统变量已在 【MySQL的变量、流程控制、游标与触发器】一文中讲解,此处仅做复习。

7.1 系统变量简介

MySQL 服务器程序运行过程中会用到许多影响程序行为的变量,它们被称为 MySQL 系统变量。 比如:

  • max_connections:允许同时连入的客户端数量

  • default_storage_engine:表的默认存储引擎

  • query_cache_size:查询缓存的大小

MySQL 服务器程序的系统变量有很多,在此不再一一列举。

7.2 查看系统变量

查看 MySQL 服务器程序支持的系统变量以及它们的当前值:

1
2
3
4
5
6
# 查看所有全局变量
SHOW GLOBAL VARIABLES;
# 查看所有会话变量
SHOW SESSION VARIABLES;
# 或者直接
SHOW VARIABLES;
1
2
3
4
# 查看满足条件的部分系统变量
SHOW GLOBAL VARIABLES LIKE "%标识符%";
# 查看满足条件的部分会话变量
SHOW SESSION VARIABLES LIKE "%标识符%";

由于系统变量的数量实在太多,所以通常会使用 LIKE 来过滤需要查看的系统变量值,比如:

1
2
SHOW VARIABLES LIKE 'default_storage_engine';
SHOW VARIABLES LIKE 'max_connections';

LIKE 也可以跟通配符来进行模糊查询,比如:

1
2
# 查询出所有以 default 开头的系统变量值
SHOW VARIABLES LIKE 'default%';

7.3 设置系统变量

通过启动选项设置

大部分的系统变量都可以通过启动服务器时传送启动选项的方式来进行设置。

主要有以下两种方式:

  • 通过命令行添加启动选项

比方在启动服务器程序时使用:

1
mysqld --default-storage-engine=MyISAM --max-connections=10
  • 通过配置文件添加启动选项

可以这样填写配置文件:

1
2
3
[server]
default-storage-engine=MyISAM
max-connections=10

当使用上边两种方式中的任意一种启动服务器程序后,再来查看系统变量的值:

1
SHOW VARIABLES LIKE 'default_storage_engine';

可以看到 default-storage-enginemax-connections 这两个系统变量的值已经被修改。有一点需要注意,对于启动选项来说,如果启动选项名由多个单词组成,各个单词之间用短划线 - 或者下划线 _ 连接起来都可以,但它对应的系统变量的单词之间必须使用下划线 _ 连接起来。

服务器程序运行过程中设置

对于大部分系统变量来说,它们的值可以在服务器程序运行过程中进行动态修改而无需停止并重启服务器,但系统变量有作用范围之分。

设置不同作用范围的系统变量

多个客户端程序可以同时连接到一个服务器程序。对于同一个系统变量,有时想让不同的客户端有不同的值。比方说使客户端 A 对应的默认存储引擎为 InnoDB,所以可以把系统变量 default_storage_engine 的值设置为 InnoDB;而又想让客户端 B 对应的默认存储引擎为 MyISAM,所以可以把系统变量 default_storage_engine 的值设置为 MyISAM。这样两个客户端拥有不同的默认存储引擎,使用时互不影响,十分方便。但是这样各个客户端都私有一份系统变量可能产生下列两个问题:

  • 有一些系统变量并不是针对单个客户端的,比如允许同时连接到服务器的客户端数量 max_ connections,查询缓存的大小 query_cache_size,这些公有的系统变量让某个客户端私有显然不合适。

  • 一个新连接到服务器的客户端对应的系统变量的值该怎么设置?

为了解决这两个问题,MySQL 提出了系统变量作用范围的概念,具体来说作用范围分为这两种:

  • GLOBAL:全局变量,影响服务器的整体操作。

  • SESSION:会话变量,影响某个客户端连接的操作。 (注:SESSION 有个别名叫 LOCAL)

在服务器启动时,会将每个全局变量初始化为其默认值(可以通过命令行或选项文件中指定的选项更改这些默认
值),然后服务器还为每个连接的客户端维护一组会话变量,客户端的会话变量在连接时使用相应全局变量的当
前值初始化。

default_storage_engine 举例,在服务器启动时会初始化一个名为 default_storage_engine,作用范围为 GLOBAL 的系统变量,之后每当有一个客户端连接到该服务器时, 服务器都会单独为该客户端分配一个名为 default_storage_engine,作用范围为 SESSION 的系统变量,该作用范围为 SESSION 的系统变量值按照当前作用范围为 GLOBAL 的同名系统变量值进行初始化。

很显然,通过启动选项设置的系统变量的作用范围都是 GLOBAL 的,也就是对所有客户端都有效,因为在系统启动时还没有客户端程序连接进来。了解了系统变量的 GLOBAL 和 SESSION 作用范围后,再看一下在服务器程序运行期间通过客户端程序设置系统变量的语法:

1
SET [GLOABL|SESSION] 系统变量名 = 值;

或者:

1
SET [@@(GLOBAL|SESSION).]var_name = XXX;

比如,服务器运行过程中把作用范围为 GLOBAL 的系统变量 default_storage_engine 的值修改为 MyISAM,也就是想让之后新连接到服务器的客户端都用 MyISAM 作为默认存储弓|擎,那可以选择下边两条语句中的任意一条来进行设置:

1
2
3
4
# 方式一
SET GLOBAL default_storage_engine = MyISAM;
# 方式二
SET @@GLOBAL.default_storage_engine = MyISAM;

如果只想对本客户端生效,也可以选择下边三条语句中的任意一条进行设置:

1
2
3
4
5
6
# 方式一
SET SESSION default_storage_engine = MyISAM;
# 方式二
SET @@SESSION.default_storage_engine = MyISAM;
# 方式三
SET default_storage_engine = MyISAM;

从上边的方式三可以看出,如果在设置系统变量的语句中省略了作用范围,默认作用范围就是 SESSION,也就是说 SET 系统变量名 = 值SET SESSION 系统变量名 = 值 是等价的。

查看不同作用范围的系统变量

既然系统变量有作用范围之分,那 SHOW VARIABLES 语句查看的是什么作用范围的系统变量呢?

默认查看的是 SESSION 作用范围的系统变量。

也可以在查看系统变量的语句上加上要查看的系统变量的作用范围:

1
SHOW [GLOBAL|SESSION] VARIABLES [LIKE 匹配的模式];

如果某个客户端改变了某个系统变量在 GLOBAL 作用范围的值,并不会影响该系统变量在当前已经连接的客户端作用范围为 SESSION 的值,只会影响后续连入的客户端在作用范围为 SESSION 的值。

注意事项

并不是所有系统变量都具有 GLOBAL 和 SESSION 的作用范围。

  • 有一些系统变量只具有 GLOBAL 作用范围,比如表示服务器程序支持同时最多有多少
    个客户端程序进行连接的 max_connections

  • 有一些系统变量只具有 SESSION 作用范围,比如表示在对某个包含 AUTO_INCREMENT 列的表进
    行插入时,该列初始的值 insert_id

  • 有一些系统变量的值既具有 GLOBAL 作用范围,也具有 SESSION 作用范围,比如 default_storage_engine,当然大部分的系统变量都是这样。

  • 还有些系统变量是只读的,并不能设置值。比如表示当前 MySQL 版本的 version,不能设置它的值,只能使用 SHOW VARIABLES 语句查看。