权限管理
导学
创建用户后,必须精确控制他们能做什么。MySQL 的权限系统粒度从全局到列级,理解权限层级和 GRANT 语法是数据库安全的核心。
定义
权限(Privilege):允许用户执行的特定操作。MySQL 权限按作用范围分为全局级、数据库级、表级、列级和存储过程级。
核心权限类型
| 权限 | 作用范围 | 说明 |
|---|---|---|
ALL PRIVILEGES | 所有 | 所有权限(除 GRANT OPTION) |
SELECT | 表/列 | 查询数据 |
INSERT | 表/列 | 插入数据 |
UPDATE | 表/列 | 更新数据 |
DELETE | 表 | 删除数据 |
CREATE | 数据库/表 | 创建对象 |
DROP | 数据库/表 | 删除对象 |
ALTER | 表 | 修改表结构 |
INDEX | 表 | 创建/删除索引 |
EXECUTE | 存储过程 | 执行存储过程/函数 |
GRANT OPTION | 所有 | 允许将权限授予他人 |
SUPER | 全局 | 管理操作(改全局变量、杀线程等) |
REPLICATION SLAVE | 全局 | 复制从库权限 |
GRANT 语法
GRANT 权限列表 ON 作用范围 TO '用户'@'主机' [WITH GRANT OPTION];
| 作用范围 | 含义 | 示例 |
|---|---|---|
*.* | 所有数据库的所有对象 | GRANT SELECT ON *.* |
db_name.* | 指定数据库的所有对象 | GRANT ALL ON app_db.* |
db_name.table_name | 指定表 | GRANT SELECT ON app_db.employees |
db_name.table_name(col) | 指定列 | GRANT UPDATE(dept) ON app_db.employees |
SQL 示例
前置准备:建立测试环境
-- 创建测试数据库
CREATE DATABASE IF NOT EXISTS app_db CHARACTER SET utf8mb4;
USE app_db;
-- 创建员工表
CREATE TABLE employees (
emp_id INT AUTO_INCREMENT PRIMARY KEY,
emp_name VARCHAR(20),
dept VARCHAR(20),
score DECIMAL(5, 2)
);
-- 插入测试数据
INSERT INTO employees (emp_name, dept, score) VALUES
('大翔', '技术部', 100),
('白歌', '技术部', NULL);
当前数据状态(employees 表):
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
场景一:授予全局级权限
第一步:创建测试用户
CREATE USER 'global_reader'@'localhost'
IDENTIFIED BY 'GlobalReader123!';
第二步:授权前验证
以 'global_reader'@'localhost' 连接数据库:
USE app_db;
SELECT * FROM employees;
结果:
ERROR 1142 (42000): SELECT command denied to user 'global_reader'@'localhost' for table 'employees'
第三步:授予全局 SELECT 权限
GRANT SELECT ON *.* TO 'global_reader'@'localhost';
第四步:查看授权结果
SHOW GRANTS FOR 'global_reader'@'localhost';
操作后的权限状态:
| Grants for global_reader@localhost |
|---|
| GRANT SELECT ON . TO 'global_reader'@'localhost' |
第五步:授权后验证
以 'global_reader'@'localhost' 连接数据库:
USE app_db;
SELECT * FROM employees;
验证结果:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
-- 尝试 INSERT(应该失败,未授予)
INSERT INTO employees (emp_name) VALUES ('测试');
结果:
ERROR 1142 (42000): INSERT command denied to user 'global_reader'@'localhost' for table 'employees'
结果解读:
SELECT ON *.*授予了该用户对所有数据库、所有表的查询权限。- 全局级权限存储在
mysql.user表中,影响范围最广,应谨慎授予。 - 该用户只能查询,不能执行
INSERT、UPDATE、DELETE等修改操作。
第六步:回收权限
REVOKE SELECT ON *.* FROM 'global_reader'@'localhost';
SHOW GRANTS FOR 'global_reader'@'localhost';
回收后的权限状态:
| Grants for global_reader@localhost |
|---|
| GRANT USAGE ON . TO 'global_reader'@'localhost' |
第七步:清理
DROP USER 'global_reader'@'localhost';
场景二:授予数据库级权限
第一步:创建测试用户
CREATE USER 'db_admin'@'localhost'
IDENTIFIED BY 'DbAdmin123!';
第二步:授权前验证
以 'db_admin'@'localhost' 连接数据库:
USE app_db;
SHOW TABLES;
结果:
ERROR 1044 (42000): Access denied for user 'db_admin'@'localhost' to database 'app_db'
第三步:授予数据库级所有权限
GRANT ALL PRIVILEGES ON app_db.* TO 'db_admin'@'localhost';
第四步:查看授权结果
SHOW GRANTS FOR 'db_admin'@'localhost';
操作后的权限状态:
| Grants for db_admin@localhost |
|---|
| GRANT USAGE ON . TO 'db_admin'@'localhost' |
GRANT ALL PRIVILEGES ON app_db.* TO 'db_admin'@'localhost' |
第五步:授权后验证
以 'db_admin'@'localhost' 连接数据库:
USE app_db;
-- 查询(成功)
SELECT * FROM scores;
验证结果:
| score_id | emp_id | score |
|---|---|---|
| 1 | 1 | 90 |
| 2 | 2 | 85 |
-- 插入(成功)
INSERT INTO scores (emp_id, score) VALUES (1, 88);
-- 更新(成功)
UPDATE scores SET score = 88 WHERE score_id = 2;
-- 删除(成功)
DELETE FROM scores WHERE score_id = 2;
-- 创建新表(成功)
CREATE TABLE logs (
log_id INT PRIMARY KEY AUTO_INCREMENT,
emp_id INT,
action VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 尝试访问其他数据库(失败)
USE mysql;
SELECT * FROM mysql.user;
结果:
ERROR 1142 (42000): SELECT command denied to user 'db_admin'@'localhost' for table 'user'
操作后的数据状态(scores 表):
| score_id | emp_id | score |
|---|---|---|
| 1 | 1 | 90 |
| 2 | 2 | 88 |
操作后的数据状态(logs 表已创建):
SHOW TABLES;
| Tables_in_app_db |
|---|
| employees |
| logs |
| scores |
结果解读:
ALL PRIVILEGES ON app_db.*授予了对app_db数据库中所有对象的全部操作权限(除GRANT OPTION)。- 该用户可以在
app_db库内自由查询、修改、删除、创建表,但无法访问mysql系统库或其他数据库。 - 数据库级权限存储在
mysql.db表中。
第六步:回收部分权限
-- 回收 DELETE 权限
REVOKE DELETE ON app_db.* FROM 'db_admin'@'localhost';
SHOW GRANTS FOR 'db_admin'@'localhost';
回收后的权限状态:
| Grants for db_admin@localhost |
|---|
| GRANT USAGE ON . TO 'db_admin'@'localhost' |
GRANT SELECT, INSERT, UPDATE, CREATE, DROP, ... ON app_db.* ... |
注意:
REVOKE DELETE后,权限列表中不再包含DELETE,但仍保留SELECT、INSERT、UPDATE、CREATE、DROP等。
第七步:验证回收后的权限
以 'db_admin'@'localhost' 连接数据库:
USE app_db;
-- 尝试 DELETE(失败)
DELETE FROM scores WHERE score_id = 1;
结果:
ERROR 1142 (42000): DELETE command denied to user 'db_admin'@'localhost' for table 'scores'
-- SELECT 仍然可以执行(成功)
SELECT * FROM scores;
结果:
| score_id | emp_id | score |
|---|---|---|
| 1 | 1 | 90 |
| 2 | 2 | 88 |
第八步:清理
REVOKE ALL PRIVILEGES ON app_db.* FROM 'db_admin'@'localhost';
DROP USER 'db_admin'@'localhost';
场景三:授予表级权限
第一步:创建测试用户
CREATE USER 'table_user'@'localhost'
IDENTIFIED BY 'TableUser123!';
第二步:授予精确到表的权限
-- 只能读 employees 表
GRANT SELECT ON app_db.employees TO 'table_user'@'localhost';
-- 可以读写 scores 表,但不能删表
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.scores TO 'table_user'@'localhost';
第三步:查看授权结果
SHOW GRANTS FOR 'table_user'@'localhost';
操作后的权限状态:
| Grants for table_user@localhost |
|---|
| GRANT USAGE ON . TO 'table_user'@'localhost' |
GRANT SELECT ON app_db.employees TO 'table_user'@'localhost' |
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.scores TO 'table_user'@'localhost' |
第四步:验证权限边界
以 'table_user'@'localhost' 连接数据库:
USE app_db;
-- employees 表:只能 SELECT(成功)
SELECT * FROM employees;
验证结果:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
-- employees 表:尝试 INSERT(失败)
INSERT INTO employees (emp_name) VALUES ('测试');
结果:
ERROR 1142 (42000): INSERT command denied to user 'table_user'@'localhost' for table 'employees'
-- scores 表:可以 INSERT(成功)
INSERT INTO scores (emp_id, score) VALUES (1, 88);
-- scores 表:可以 DELETE(成功)
DELETE FROM scores WHERE score_id = 2;
-- 尝试访问 logs 表(如果存在,会失败)
SELECT * FROM logs;
结果:
ERROR 1142 (42000): SELECT command denied to user 'table_user'@'localhost' for table 'logs'
操作后的数据状态(scores 表):
| score_id | emp_id | score |
|---|---|---|
| 1 | 1 | 90 |
| 3 | 1 | 88 |
结果解读:
table_user对employees表只有SELECT权限,只能查询不能修改。table_user对scores表有完整的读写删权限,但不能修改表结构(无ALTER、DROP)。table_user无法访问logs表或其他任何表。- 表级权限存储在
mysql.tables_priv表中。
第五步:清理
REVOKE ALL PRIVILEGES ON app_db.* FROM 'table_user'@'localhost';
DROP USER 'table_user'@'localhost';
场景四:授予列级权限(极少使用)
第一步:创建测试用户
CREATE USER 'column_user'@'localhost'
IDENTIFIED BY 'ColumnUser123!';
第二步:授予列级权限
-- 只能更新 employees 表的 dept 列
GRANT UPDATE(dept) ON app_db.employees TO 'column_user'@'localhost';
-- 可以查询 employees 表所有列
GRANT SELECT ON app_db.employees TO 'column_user'@'localhost';
第三步:查看授权结果
SHOW GRANTS FOR 'column_user'@'localhost';
操作后的权限状态:
| Grants for column_user@localhost |
|---|
| GRANT USAGE ON . TO 'column_user'@'localhost' |
GRANT SELECT ON app_db.employees TO 'column_user'@'localhost' |
GRANT UPDATE (dept) ON app_db.employees TO 'column_user'@'localhost' |
第四步:验证列级权限
以 'column_user'@'localhost' 连接数据库:
USE app_db;
-- SELECT 所有列(成功)
SELECT * FROM employees;
验证结果:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
-- 更新 dept 列(成功)
UPDATE employees SET dept = '产品部' WHERE emp_id = 1;
-- 更新 score 列(失败,未授权)
UPDATE employees SET score = 95 WHERE emp_id = 1;
结果:
ERROR 1143 (42000): UPDATE command denied to user 'column_user'@'localhost' for column 'score' in table 'employees'
-- 更新 emp_name 列(失败,未授权)
UPDATE employees SET emp_name = '大翔翔' WHERE emp_id = 1;
结果:
ERROR 1143 (42000): UPDATE command denied to user 'column_user'@'localhost' for column 'emp_name' in table 'employees'
操作后的数据状态(employees 表):
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
结果解读:
column_user成功更新了emp_id = 1的dept列。- 尝试更新
score和emp_name列时被拒绝,错误信息明确指出了被拒绝的列名。 - 列级权限存储在
mysql.columns_priv表中。 - 警告:列级权限会使权限表膨胀,增加认证开销。生产环境中建议通过视图控制列访问,而非列级权限。
第五步:清理
REVOKE ALL PRIVILEGES ON app_db.* FROM 'column_user'@'localhost';
DROP USER 'column_user'@'localhost';
场景五:GRANT OPTION(允许转授权限)
第一步:创建两个测试用户
CREATE USER 'senior_user'@'localhost'
IDENTIFIED BY 'SeniorUser123!';
CREATE USER 'junior_user'@'localhost'
IDENTIFIED BY 'JuniorUser123!';
第二步:授予 senior_user 权限及转授权
GRANT SELECT, INSERT ON app_db.* TO 'senior_user'@'localhost'
WITH GRANT OPTION;
第三步:查看 senior_user 权限
SHOW GRANTS FOR 'senior_user'@'localhost';
操作后的权限状态:
| Grants for senior_user@localhost |
|---|
| GRANT USAGE ON . TO 'senior_user'@'localhost' |
GRANT SELECT, INSERT ON app_db.* TO 'senior_user'@'localhost' WITH GRANT OPTION |
第四步:senior_user 将权限转授给 junior_user
以 'senior_user'@'localhost' 连接数据库:
GRANT SELECT ON app_db.* TO 'junior_user'@'localhost';
第五步:查看 junior_user 权限
SHOW GRANTS FOR 'junior_user'@'localhost';
转授后的权限状态:
| Grants for junior_user@localhost |
|---|
| GRANT USAGE ON . TO 'junior_user'@'localhost' |
GRANT SELECT ON app_db.* TO 'junior_user'@'localhost' |
第六步:验证 junior_user 权限
以 'junior_user'@'localhost' 连接数据库:
USE app_db;
SELECT * FROM employees;
验证结果:
| emp_id | emp_name | dept | score |
|---|---|---|---|
| 1 | 大翔 | 技术部 | 100 |
| 2 | 白歌 | 技术部 | NULL |
-- 尝试 INSERT(失败)
INSERT INTO employees (emp_name) VALUES ('测试');
结果:
ERROR 1142 (42000): INSERT command denied to user 'junior_user'@'localhost' for table 'employees'
结果解读:
WITH GRANT OPTION使senior_user拥有将已获权限授予其他用户的能力。senior_user只能转授自己拥有的权限(SELECT、INSERT),不能转授不存在的权限。junior_user只能从senior_user处获得SELECT权限,无法获得INSERT(除非senior_user也转授了INSERT)。GRANT OPTION是高级权限,应谨慎授予,避免权限扩散失控。
第七步:清理
REVOKE ALL PRIVILEGES ON app_db.* FROM 'senior_user'@'localhost';
REVOKE ALL PRIVILEGES ON app_db.* FROM 'junior_user'@'localhost';
DROP USER 'senior_user'@'localhost';
DROP USER 'junior_user'@'localhost';
场景六:存储过程级权限
第一步:创建存储过程
DELIMITER //
CREATE PROCEDURE app_db.GetEmployeeCount()
BEGIN
SELECT COUNT(*) AS total_employees FROM app_db.employees;
END //
CREATE PROCEDURE app_db.GetHighScore(IN p_dept VARCHAR(20))
BEGIN
SELECT emp_name, dept, e.score
FROM app_db.employees e
JOIN app_db.scores s ON e.emp_id = s.emp_id
WHERE e.dept = p_dept
ORDER BY s.score DESC
LIMIT 1;
END //
DELIMITER ;
第二步:创建测试用户
CREATE USER 'proc_user'@'localhost'
IDENTIFIED BY 'ProcUser123!';
第三步:授予存储过程执行权限
GRANT EXECUTE ON PROCEDURE app_db.GetEmployeeCount TO 'proc_user'@'localhost';
GRANT EXECUTE ON PROCEDURE app_db.GetHighScore TO 'proc_user'@'localhost';
第四步:查看授权结果
SHOW GRANTS FOR 'proc_user'@'localhost';
操作后的权限状态:
| Grants for proc_user@localhost |
|---|
| GRANT USAGE ON . TO 'proc_user'@'localhost' |
GRANT EXECUTE ON PROCEDURE app_db.GetEmployeeCount TO 'proc_user'@'localhost' |
GRANT EXECUTE ON PROCEDURE app_db.GetHighScore TO 'proc_user'@'localhost' |
第五步:验证存储过程权限
以 'proc_user'@'localhost' 连接数据库:
-- 执行授权的存储过程(成功)
CALL app_db.GetEmployeeCount();
验证结果:
| total_employees |
|---|
| 2 |
-- 执行另一个授权的存储过程(成功)
CALL app_db.GetHighScore('技术部');
验证结果:
| emp_name | dept | score |
|---|---|---|
| 大翔 | 技术部 | 100 |
-- 尝试直接查询底层表(失败)
SELECT * FROM app_db.employees;
结果:
ERROR 1142 (42000): SELECT command denied to user 'proc_user'@'localhost' for table 'employees'
结果解读:
proc_user没有employees表的直接SELECT权限,但通过EXECUTE权限可以调用存储过程。- 存储过程在创建者的权限上下文中执行(默认的
SQL SECURITY DEFINER),因此proc_user可以间接获取数据。 - 这种机制非常适合"只允许通过特定接口访问数据"的场景,避免用户直接操作表。
第六步:清理
REVOKE EXECUTE ON PROCEDURE app_db.GetEmployeeCount FROM 'proc_user'@'localhost';
REVOKE EXECUTE ON PROCEDURE app_db.GetHighScore FROM 'proc_user'@'localhost';
DROP USER 'proc_user'@'localhost';
DROP PROCEDURE app_db.GetEmployeeCount;
DROP PROCEDURE app_db.GetHighScore;
场景七:创建只读用户
第一步:创建测试用户
CREATE USER 'readonly'@'localhost'
IDENTIFIED BY 'ReadOnly123!';
第二步:授予只读权限
GRANT SELECT ON app_db.* TO 'readonly'@'localhost';
第三步:验证只读权限
以 'readonly'@'localhost' 连接数据库:
USE app_db;
-- SELECT 成功
SELECT * FROM employees;
-- INSERT 失败
INSERT INTO employees (emp_name) VALUES ('测试');
结果:
ERROR 1142 (42000): INSERT command denied to user 'readonly'@'localhost' for table 'employees'
-- UPDATE 失败
UPDATE employees SET dept = '测试部' WHERE emp_id = 1;
结果:
ERROR 1142 (42000): UPDATE command denied to user 'readonly'@'localhost' for table 'employees'
-- DELETE 失败
DELETE FROM employees WHERE emp_id = 1;
结果:
ERROR 1142 (42000): DELETE command denied to user 'readonly'@'localhost' for table 'employees'
结果解读:
- 只读用户是生产环境中最常见的权限配置之一,适用于报表查询、数据分析等场景。
- 如果只需访问特定表,应精确到表级:
GRANT SELECT ON app_db.employees TO 'readonly'@'localhost'。 - 只读用户仍然可以看到表结构和数据,敏感数据应通过视图进一步控制。
第四步:清理
REVOKE ALL PRIVILEGES ON app_db.* FROM 'readonly'@'localhost';
DROP USER 'readonly'@'localhost';
权限生效时机
GRANT ... -- 修改权限表
FLUSH PRIVILEGES; -- 手动刷新(通常不需要,GRANT 自动刷新)
MySQL 5.7 中,GRANT、REVOKE、CREATE USER 等语句会自动触发权限刷新,一般不需要手动执行 FLUSH PRIVILEGES。
验证权限生效时机:
-- 创建新用户
CREATE USER 'test_timing'@'localhost' IDENTIFIED BY 'Test123!';
-- 立即授权
GRANT SELECT ON app_db.* TO 'test_timing'@'localhost';
-- 立即验证(无需 FLUSH PRIVILEGES)
-- 以 test_timing 连接,执行 SELECT,立即生效
结果解读:
GRANT执行后会自动刷新权限缓存,新连接立即生效。- 已建立的连接通常也能立即感知权限变化,但某些客户端可能缓存了连接,需要重新连接才能生效。
- 只有在直接修改系统权限表(如
UPDATE mysql.user)后,才需要手动执行FLUSH PRIVILEGES。
常见误区
| 误区 | 正解 |
|---|---|
GRANT ALL 包括所有权限 | 不包括 GRANT OPTION,需额外授予。 |
REVOKE 会删除用户 | 不会,只回收权限。用户还在,只是没权限了。 |
| 权限修改后立即对所有连接生效 | 已建立的连接可能需要重新连接才能感知新权限。 |
面试考点
Q:GRANT ALL 和 GRANT ALL PRIVILEGES 的区别?
两者等价。
ALL PRIVILEGES是标准写法,ALL是简写。都不包含GRANT OPTION。
Q:如何创建一个只读用户?
GRANT SELECT ON database.* TO 'readonly'@'host';。如果只需访问特定表,精确到表级。
Q:FLUSH PRIVILEGES 什么时候需要?
直接修改权限表(如
UPDATE mysql.user)后需要手动刷新。正常使用GRANT/REVOKE/CREATE USER时自动刷新,不需要手动执行。
小结
- 权限按层级分为全局、数据库、表、列、存储过程级
GRANT授予权限,REVOKE回收权限- 列级权限开销大,建议用视图替代
WITH GRANT OPTION允许用户将权限转授他人,需谨慎使用- 遵循最小权限原则,只授予完成任务所需的最小权限集合
- 完整流程:创建用户 → 精确授权 → 多维度验证 → 按需回收 → 定期审计
下一章引子:MySQL 5.7 引入了角色(Role),让权限管理从"一对一"升级为"一对多"。