摘要:
角色(Role)是 MySQL 8.0 权限系统中最具颠覆性的功能——它将权限从“直接授予用户”升级为“先授予角色,再将角色授予用户”的两层模型,从此 DBA 可以像管理代码库一样管理权限集合。与此同时,动态权限(Dynamic Privileges)打破了传统静态权限(SELECT、INSERT等)的封闭集合,允许存储引擎、插件、组件在运行时注册全新的权限类型。本文从角色与用户的关系模型切入,系统拆解角色的存储实现(虚拟用户)、激活机制(
SET ROLE)、强制角色(mandatory_roles)及默认角色。深入sql/auth/sql_authorization.cc源码,完整还原GRANT ROLE、REVOKE ROLE的事务化执行流程及角色缓存管理。动态权限部分以CONNECTION_ADMIN和TABLE_ENCRYPTION_ADMIN为例,展示插件如何注册新权限。生产实践部分提供角色层级设计、迁移策略及与旧权限模型的共存避坑指南。最后基于 2026 年视角,讨论 RBAC 对传统 user@host 权限模型的替代进度,以及未来 IAM 集成对 MySQL 权限系统的终极影响。
一、核心概念与底层图景
1.1 定义
角色是一个命名的权限集合,可被授予用户或其他角色,激活后用户获得该角色包含的所有权限。
传统权限模型:用户 ← 权限(多对多)
角色模型:用户 ← 角色 ← 权限(用户-角色-权限三层)
动态权限:非内置的、由插件或组件在运行时注册的权限,用于控制对特定功能模块的访问。
设计哲学:
- 权限管理抽象化:将细粒度权限聚合成有业务含义的“角色”,如
read_only、backup_operator。 - 权限集合复用:同一组权限可授予多个用户,修改角色定义即影响所有继承者。
- 最小权限原则落地:为用户分配恰好满足工作职责的角色组合。
- 可扩展性:动态权限允许第三方组件像内置权限一样受 ACL 保护。
1.2 架构全景
graph TB classDef role fill:#fff3e0,stroke:#e65100,stroke-width:2px classDef user fill:#e1f5fe,stroke:#01579b classDef grant fill:#d1c4e9,stroke:#4a148c classDef dynamic fill:#c8e6c9,stroke:#1b5e20 classDef cache fill:#ffccbc,stroke:#bf360c subgraph 持久化存储 [mysql 系统表] USER_TBL[mysql.user] -->|account_locked=Y| ROLE_REC[角色记录] USER_TBL -->|account_locked=N| USER_REC[用户记录] ROLE_EDGES[mysql.role_edges] -->|FROM_USER/FROM_HOST| FROM_ROLE[源角色] ROLE_EDGES -->|TO_USER/TO_HOST| TO_USER[目标用户/角色] DEFAULT_ROLES[mysql.default_roles] -->|DEFAULT| DEFAULT_ROLE[默认激活角色] GLOBAL_GRANTS[mysql.global_grants] -->|动态权限| DYNAMIC_PRIV[非内置权限] end subgraph 内存角色缓存 ACL_USERS[acl_users] -->|is_role| ROLE_FLAG[角色标记] ACL_ROLES[角色映射表] -->|user@host| ROLE_SET[已授予的角色集合] DEFAULT_ROLE_CACHE[默认角色缓存] -->|user@host| DEFAULT_ROLE_SET[登录时激活的角色] end subgraph 角色授权流程 GRANT[GRANT role TO user] -->|写mysql.role_edges| TX[事务提交] TX -->|8.0 增量更新| UPDATE_CACHE[更新ACL_ROLES缓存] UPDATE_CACHE -->|无需FLUSH| EFFECTIVE[立即对新会话生效] end subgraph 角色激活流程 LOGIN[用户登录] -->|读取mysql.default_roles| LOAD_DEFAULT[加载默认角色] LOAD_DEFAULT -->|SET DEFAULT ROLE| ACTIVE[激活角色权限] MANUAL[SET ROLE role] -->|会话级| TEMP_ACTIVE[临时激活] ACTIVE -->|合并权限| SEC_CTX[Security_context.master_access] TEMP_ACTIVE -->|覆盖默认| SEC_CTX end subgraph 动态权限注册 PLUGIN[插件初始化] -->|register_dynamic_privilege| REG[注册权限名] REG -->|存储| GLOBAL_GRANTS REG -->|加入| DYNAMIC_PRIV_SET[全局动态权限集合] GRANT_DYNAMIC[GRANT 新权限] -->|同内置权限| GRANT_FLOW end class USER_TBL,ROLE_REC,USER_REC,ROLE_EDGES,DEFAULT_ROLES,GLOBAL_GRANTS role class ACL_USERS,ACL_ROLES,DEFAULT_ROLE_CACHE cache class GRANT,TX,UPDATE_CACHE,EFFECTIVE grant class LOGIN,LOAD_DEFAULT,MANUAL,ACTIVE,TEMP_ACTIVE,SEC_CTX user class PLUGIN,REG,DYNAMIC_PRIV_SET,GRANT_DYNAMIC dynamic
二、机制原理深度剖析
2.1 角色的存储:为什么角色是“被锁定的用户”?
历史原因:MySQL 8.0 为了实现角色功能,没有设计新的系统表,而是复用了 mysql.user 表。
-- 创建一个角色
CREATE ROLE 'read_only'@'%', 'backup_operator'@'%';
-- 查看 mysql.user,会发现新增两条记录
SELECT user, host, account_locked, plugin
FROM mysql.user
WHERE user IN ('read_only', 'backup_operator');
-- 输出:
-- +-----------------+------+----------------+-------+
-- | user | host | account_locked | plugin |
-- +-----------------+------+----------------+-------+
-- | read_only | % | Y | |
-- | backup_operator | % | Y | |
-- +-----------------+------+----------------+-------+角色记录的特征:
account_locked = 'Y':角色不能用于登录认证。plugin = '':角色无认证插件,无法通过客户端连接。authentication_string = '':无密码哈希。
角色权限的存储:
- 角色拥有的权限与用户权限存储在完全相同的位置——
mysql.global_grants、mysql.db、mysql.tables_priv等表。 - 区别在于:权限记录关联的是角色名,而不是用户名。
-- 授予角色权限
GRANT SELECT, INSERT ON test.* TO 'read_only'@'%';
-- 查看库权限表
SELECT * FROM mysql.db WHERE user = 'read_only';
-- 输出:与普通用户的库权限记录格式完全一致角色成员关系:mysql.role_edges 表
CREATE TABLE `role_edges` (
`FROM_HOST` char(255) NOT NULL DEFAULT '',
`FROM_USER` char(32) NOT NULL DEFAULT '',
`TO_HOST` char(255) NOT NULL DEFAULT '',
`TO_USER` char(32) NOT NULL DEFAULT '',
PRIMARY KEY (`FROM_HOST`,`FROM_USER`,`TO_HOST`,`TO_USER`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;FROM_USER/FROM_HOST:源角色(被授予的角色)。TO_USER/TO_HOST:目标用户或目标角色(用于角色层级)。
设计意图:
- 兼容性:复用现有权限存储结构,无需重构整个权限系统。
- 代价:
mysql.user表膨胀,包含大量非登录账号;权限表(db、tables_priv)存储角色权限记录,与用户权限记录混合。
2.2 角色授权与撤销:事务化操作
8.0 角色授权流程(sql/auth/sql_authorization.cc):
bool grant_role(THD *thd, const char *from_user, const char *from_host,
const char *to_user, const char *to_host) {
/* 1. 检查 FROM 是角色且 TO 是用户/角色 */
if (!is_role(from_user, from_host) ||
(!is_user(to_user, to_host) && !is_role(to_user, to_host))) {
my_error(ER_WRONG_ROLE, MYF(0));
return true;
}
/* 2. 开启事务 */
dd::Transaction_ro trx(thd);
/* 3. 写入 mysql.role_edges 表 */
dd::Table *role_edges = dd::get_table(thd, "mysql", "role_edges");
dd::Row *row = role_edges->create_row();
row->set_string("FROM_HOST", from_host);
row->set_string("FROM_USER", from_user);
row->set_string("TO_HOST", to_host);
row->set_string("TO_USER", to_user);
role_edges->insert_row(row);
/* 4. 提交事务 */
trx.commit();
/* 5. 更新 ACL 缓存(增量)*/
acl_update_role_member(from_user, from_host, to_user, to_host, true);
return false;
}角色撤销流程:
- 从
mysql.role_edges删除对应记录。 - 同时从
mysql.default_roles删除该角色的默认激活标记。 - 更新 ACL 缓存,移除角色的继承关系。
原子性保障:
- 角色授权/撤销是事务性的——若在写
role_edges后、提交前崩溃,整个操作回滚。 - 这是 8.0 将权限表迁移到 InnoDB 带来的关键改进。
2.3 角色激活:从静态授予到动态生效
关键认知:角色授予用户 ≠ 用户拥有角色权限。用户必须先激活角色,才能获得其权限。
激活方式一:默认角色(自动激活)
-- 设置默认角色(用户登录时自动激活)
SET DEFAULT ROLE 'read_only'@'%' TO 'app_user'@'%';
-- 设置所有已授予角色为默认
SET DEFAULT ROLE ALL TO 'app_user'@'%';激活方式二:手动激活(会话级)
-- 登录后激活指定角色
SET ROLE 'read_only'@'%';
-- 激活所有已授予角色
SET ROLE ALL;
-- 除指定角色外全部激活
SET ROLE ALL EXCEPT 'backup_operator'@'%';
-- 撤销所有角色(回到无角色状态)
SET ROLE NONE;全局强制激活(mandatory_roles):
[mysqld]
mandatory_roles = 'audit_reader@%,monitor@%'- 所有用户必须激活这些角色,无法通过
SET ROLE NONE撤销。 - 用于强制审计、监控等跨所有账号的权限。
激活机制源码(sql/auth/sql_authorization.cc):
void set_role_active(THD *thd, const char *role_user, const char *role_host) {
Security_context *sctx = thd->security_context();
/* 1. 查找角色的权限位掩码 */
ACL_USER *acl_role = find_acl_user(role_user, role_host);
if (!acl_role || !acl_role->is_role) return;
/* 2. 将角色的权限合并到当前安全上下文 */
sctx->master_access |= acl_role->access;
/* 3. 递归激活角色的角色(角色层级)*/
/* 4. 记录已激活角色列表,供 CURRENT_ROLE() 查询 */
}为什么设计“授予 + 激活”两层?
- 安全:防止
GRANT后立即生效,导致正在执行误操作。 - 灵活:用户可在同一会话中切换不同权限集合(普通操作 → 管理员操作)。
- 兼容:不影响现有权限模型,角色是上层抽象,底层仍是
user@host权限。
2.4 动态权限:打破内置权限的封闭集合
传统静态权限:SELECT_ACL、INSERT_ACL、UPDATE_ACL……共约 30 个,编译时固定。
8.0 动态权限:运行时由插件/组件注册的全新权限。
内置动态权限示例(8.0.33):
| 权限名称 | 引入版本 | 作用 |
|---|---|---|
CONNECTION_ADMIN | 8.0.14 | 管理连接(替代 SUPER 的部分职责) |
TABLE_ENCRYPTION_ADMIN | 8.0.14 | 管理表加密密钥 |
BINLOG_ADMIN | 8.0.14 | 管理二进制日志 |
REPLICATION_APPLIER | 8.0.18 | 复制应用线程专用 |
GROUP_REPLICATION_ADMIN | 8.0.14 | 管理组复制 |
BACKUP_ADMIN | 8.0.14 | 备份锁管理 |
PERSIST_RO_VARIABLES_ADMIN | 8.0.14 | 持久化只读变量 |
SYSTEM_VARIABLES_ADMIN | 8.0.14 | 管理系统变量 |
ROLE_ADMIN | 8.0.14 | 管理角色(非强制) |
SESSION_VARIABLES_ADMIN | 8.0.14 | 管理会话变量 |
AUDIT_ADMIN | 8.0.14 | 管理审计日志 |
FIREWALL_EXEMPT | 8.0.23 | 绕过防火墙 |
SENSITIVE_VARIABLES_OBSERVER | 8.0.28 | 观察敏感变量 |
动态权限注册 API:
/* include/mysql/plugin.h */
bool register_dynamic_privilege(const char *privilege_name, const char *description);
/* 插件初始化示例 */
mysql_declare_plugin(audit_log) {
MYSQL_AUDIT_PLUGIN, /* 类型 */
&audit_log_descriptor, /* 描述符 */
"audit_log", /* 名称 */
"Oracle Corporation", /* 作者 */
"Audit log", /* 描述 */
PLUGIN_LICENSE_GPL,
audit_log_plugin_init, /* 初始化 */
audit_log_plugin_deinit,
0x0100, /* 版本 */
NULL,
NULL,
"8.0.14", /* MySQL 依赖版本 */
MYSQL_AUDIT_PLUGIN_VERSION,
audit_log_plugin_variables /* 系统变量 */
},
{
MYSQL_DYNAMIC_PRIVILEGE_PLUGIN, /* 动态权限注册插件 */
&audit_log_priv_descriptor,
"audit_log_priv", /* 内部名称 */
"Oracle Corporation",
"Audit log admin privilege",
PLUGIN_LICENSE_GPL,
audit_log_priv_init, /* 注册权限 */
audit_log_priv_deinit,
0x0100,
NULL,
NULL,
"8.0.14",
MYSQL_DYNAMIC_PRIVILEGE_PLUGIN_VERSION,
NULL
}
mysql_declare_plugin_end;
/* 注册回调 */
static int audit_log_priv_init(void *arg) {
register_dynamic_privilege("AUDIT_ADMIN", "Audit log administration");
return 0;
}权限存储:mysql.global_grants 表
CREATE TABLE `global_grants` (
`USER` char(32) NOT NULL DEFAULT '',
`HOST` char(255) NOT NULL DEFAULT '',
`PRIV` char(32) NOT NULL DEFAULT '',
`WITH_GRANT_OPTION` enum('N','Y') NOT NULL DEFAULT 'N',
PRIMARY KEY (`USER`,`HOST`,`PRIV`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;PRIV字段存储动态权限名(字符串),而非位掩码。- 8.0 同时保留
user表的权限位字段用于兼容性,但动态权限只能存储在global_grants。
验证:
-- 授予动态权限
GRANT CONNECTION_ADMIN ON *.* TO 'dba'@'%';
-- 查看存储
SELECT * FROM mysql.global_grants WHERE PRIV = 'CONNECTION_ADMIN';三、内核/源码级实现
3.1 核心数据结构:角色缓存
/* sql/auth/sql_auth_cache.h */
struct ACL_ROLE_MEMBER {
/* 源角色(被授予的角色)*/
LEX_CSTRING from_user;
LEX_CSTRING from_host;
/* 目标用户/角色 */
LEX_CSTRING to_user;
LEX_CSTRING to_host;
/* 是否为默认角色 */
bool is_default;
/* 角色层级深度(用于防止循环继承)*/
uint16_t depth;
};
/* 全局角色成员关系缓存 */
static HASH *acl_role_members = NULL; /* key: to_user@to_host, value: LIST<ACL_ROLE_MEMBER> */
/* 角色权限缓存(复用 ACL_USER)*/
/* 角色也是 ACL_USER,is_role = TRUE */角色激活上下文:
/* sql/auth/sql_auth.h */
class Active_role_context {
private:
/* 当前会话已激活的角色列表 */
List<LEX_CSTRING> m_active_roles;
/* 合并后的权限位掩码 */
ulong m_master_access;
/* 动态权限位图(8.0.14+)*/
Dynamic_privilege_set m_dynamic_privileges;
};3.2 核心流程:用户登录时的角色激活
/* sql/auth/sql_authentication.cc - login_connection */
bool login_connection(THD *thd) {
/* ... 认证通过 ... */
/* 1. 加载默认角色 */
if (activate_all_roles_on_login) {
/* 激活所有已授予的角色 */
set_role_all(thd);
} else {
/* 仅激活 mysql.default_roles 中标记为默认的角色 */
set_role_default(thd);
}
/* 2. 强制添加 mandatory_roles */
set_mandatory_roles(thd);
return false;
}
/* sql/auth/sql_authorization.cc - set_role_all */
bool set_role_all(THD *thd) {
Security_context *sctx = thd->security_context();
/* 1. 获取该用户被授予的所有角色 */
LIST *granted_roles = get_granted_roles(sctx->priv_user(), sctx->priv_host());
/* 2. 递归激活角色及子角色 */
LIST *roles_to_activate = granted_roles;
while (roles_to_activate) {
ACL_USER *role = (ACL_USER*)roles_to_activate->data;
activate_role(thd, role);
roles_to_activate = roles_to_activate->next;
}
return false;
}
/* sql/auth/sql_authorization.cc - activate_role */
void activate_role(THD *thd, ACL_USER *role) {
Security_context *sctx = thd->security_context();
/* 1. 合并全局权限位掩码 */
sctx->master_access |= role->access;
/* 2. 合并动态权限 */
sctx->dynamic_privileges.merge(role->dynamic_privileges);
/* 3. 记录已激活角色 */
sctx->active_roles.push_back(role->user);
/* 4. 递归激活该角色被授予的角色(角色层级)*/
LIST *sub_roles = get_granted_roles(role->user.str, role->host.str);
while (sub_roles) {
activate_role(thd, (ACL_USER*)sub_roles->data);
sub_roles = sub_roles->next;
}
}3.3 核心流程:动态权限验证
/* sql/auth/sql_authorization.cc - has_dynamic_privilege */
bool has_dynamic_privilege(THD *thd, const char *priv_name) {
Security_context *sctx = thd->security_context();
/* 1. 检查是否拥有 SUPER(向后兼容)*/
if (sctx->master_access & SUPER_ACL) {
return true;
}
/* 2. 检查会话缓存的动态权限位图 */
return sctx->dynamic_privileges.contains(priv_name);
}
/* 权限验证点示例:CONNECTION_ADMIN */
bool check_connection_admin(THD *thd) {
return has_dynamic_privilege(thd, "CONNECTION_ADMIN") ||
(sctx->master_access & SUPER_ACL);
}四、生产落地与 SRE 实战
4.1 场景化案例:从直接授权到角色的平滑迁移
现状(5.7 遗留权限模型):
-- 20 个开发工程师,每人单独授权
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'dev1'@'%';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'dev2'@'%';
-- ... 重复 20 次
-- 5 个运维工程师,权限更多
GRANT ALL PRIVILEGES ON *.* TO 'ops1'@'%' WITH GRANT OPTION;
-- ...问题:
- 新人入职需执行 1 次
GRANT,但往往漏授权。 - 离职需执行 1 次
REVOKE,但往往漏回收。 - 权限变更(如增加
DELETE)需修改 20 个用户。
迁移到角色模型(8.0 推荐做法):
-- 1. 定义角色
CREATE ROLE 'app_developer'@'%', 'app_operator'@'%', 'app_auditor'@'%';
-- 2. 为角色授予权限
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_developer'@'%';
GRANT SELECT ON app_db.* TO 'app_auditor'@'%';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE TEMPORARY TABLES
ON app_db.* TO 'app_operator'@'%';
GRANT EXECUTE ON PROCEDURE app_db.cleanup TO 'app_operator'@'%';
-- 3. 角色授予用户
GRANT 'app_developer'@'%' TO 'dev1'@'%', 'dev2'@'%', ...;
GRANT 'app_auditor'@'%' TO 'auditor1'@'%', 'auditor2'@'%';
GRANT 'app_operator'@'%' TO 'ops1'@'%', 'ops2'@'%';
-- 4. 设置默认角色
SET DEFAULT ROLE ALL TO 'dev1'@'%';
-- ... 为每个用户设置默认角色
-- 5. (可选)强制审计角色
SET GLOBAL mandatory_roles = 'app_auditor@%';收益:
- 新增开发人员:
GRANT 'app_developer'@'%' TO 'dev21'@'%'; - 离职:
REVOKE 'app_developer'@'%' FROM 'dev5'@'%'; - 权限变更:只修改角色的权限,影响所有成员。
4.2 参数调优矩阵
| 参数 | 作用域 | 8.0 推荐值 | 内核解释 |
|---|---|---|---|
activate_all_roles_on_login | 全局 | ON | 登录时自动激活所有已授予角色 |
mandatory_roles | 全局 | '' | 强制所有用户激活的角色(逗号分隔) |
show_role_grants_in_show_grants | 全局 | ON | SHOW GRANTS 是否显示角色定义 |
show_grants_information_schema | 全局 | ON | 8.0.30+,性能优化 |
role_admin | 动态 | 视需求 | 可授予用户的动态权限 |
角色授权权限:
-- 默认只有 root 可以创建/授予角色
-- 可授权 ROLE_ADMIN 权限给其他 DBA
GRANT ROLE_ADMIN ON *.* TO 'dba'@'%';4.3 监控与诊断
1. 查看角色定义
-- 列出所有角色(account_locked=Y 且 非系统用户)
SELECT user, host FROM mysql.user
WHERE account_locked = 'Y'
AND user NOT IN ('mysql.session', 'mysql.sys', 'root', 'mysql.infoschema');
-- 查看角色的权限(与用户相同)
SHOW GRANTS FOR 'read_only'@'%';2. 查看角色成员关系
-- 谁拥有该角色?
SELECT FROM_USER, FROM_HOST, TO_USER, TO_HOST
FROM mysql.role_edges
WHERE FROM_USER = 'read_only' AND FROM_HOST = '%';
-- 该用户拥有哪些角色?
SELECT FROM_USER, FROM_HOST
FROM mysql.role_edges
WHERE TO_USER = 'dev1' AND TO_HOST = '%';3. 查看当前会话激活的角色
-- 当前激活的角色
SELECT CURRENT_ROLE();
-- 8.0 查看所有可用角色
SELECT * FROM information_schema.applicable_roles;
SELECT * FROM information_schema.enabled_roles;4. 查看动态权限
-- 列出所有已注册的动态权限
SELECT * FROM information_schema.dynamic_privileges;
-- 查看用户被授予的动态权限
SELECT * FROM mysql.global_grants WHERE PRIV NOT IN (
SELECT PRIV from mysql.global_grants GROUP BY PRIV
) AND PRIV != '' ORDER BY PRIV;4.4 故障排查决策树
mindmap root(角色与动态权限问题) 角色权限不生效 检查角色是否已激活 SELECT CURRENT_ROLE() 执行 SET ROLE ALL 检查 mandatory_roles 全局强制角色 检查 activate_all_roles_on_login 登录时自动激活 角色层级死循环 错误: ER_ROLE_CIRCULAR_GRANT mysql.role_edges 存在循环继承 查找并删除循环边 重构角色设计 SHOW GRANTS 输出过长 角色嵌套多层 SET GLOBAL show_role_grants_in_show_grants = OFF SHOW GRANTS FOR 'user'@'host' 不展开角色定义 动态权限缺失 GRANT 成功但无法执行操作 检查权限名是否拼写正确(大写) 检查是否授予在 *.* 上 检查插件是否已安装 角色无法删除 存在依赖成员 REVOKE 角色 FROM 所有用户/角色 DROP ROLE
4.5 实战案例:角色层级设计不当引发性能问题
环境:
- MySQL 8.0.33,金融核心库,约 500 个用户,30 个角色。
- 角色设计:权限 → 功能角色 → 业务角色 → 部门角色 → 用户。
现象:
- 用户登录耗时从 50ms 增至 300ms。
SHOW PROCESSLIST看到System lock等待。
根本原因:
- 角色嵌套层级深达 5 级。
SET ROLE ALL递归激活角色,每个角色激活需查询mysql.role_edges和mysql.user。- 5 级嵌套产生 5³ = 125 次递归查询(实际测试)。
优化方案:
-- 1. 扁平化角色层级(最多 2 级)
-- 原结构:developer → backend → product → department
-- 新结构:developer → backend_department, product_department
-- 2. 启用缓存
-- 8.0 默认已缓存,检查是否有异常
-- 3. 仅激活必需角色,不设默认角色
SET DEFAULT ROLE NONE TO 'user'@'%';
-- 用户登录后手动 SET ROLE 需要的角色验证:
- 登录延迟降至 80ms(仍高于无角色时的 50ms,但可接受)。
- 角色嵌套深度 ≤ 2。
4.6 角色 vs 传统权限:共存策略
8.0 向后兼容性设计:
- 用户可以同时拥有直接授权的权限和角色的权限。
- 直接授权的权限(
GRANT SELECT ON *.* TO user)存储在mysql.user权限位。 - 角色的权限存储在
mysql.role_edges+ 角色的mysql.user权限位。
最佳实践(2026 年共识):
-- 1. 新账号:完全基于角色,不直接授权
CREATE USER 'app'@'%' IDENTIFIED BY ...;
GRANT 'app_read'@'%', 'app_write'@'%' TO 'app'@'%';
SET DEFAULT ROLE ALL TO 'app'@'%';
-- 2. 旧账号迁移:先授权角色,保留原有权限
GRANT 'app_read'@'%' TO 'legacy_user'@'%';
-- 观察一段时间
-- 确认无误后,撤销原直接权限
REVOKE SELECT ON db.* FROM 'legacy_user'@'%';五、技术演进与 2026 年视角
5.1 历史设计约束与改进
| 版本 | 角色/动态权限变化 | 动因 |
|---|---|---|
| 5.7 | ❌ 无角色,仅直接授权 | 权限管理粗放 |
| 8.0.0 | ✅ 角色功能(虚拟用户实现) | RBAC 需求 |
| 8.0.14 | ✅ 动态权限、部分 SUPER 拆分 | 最小权限原则 |
| 8.0.14 | ✅ mandatory_roles | 强制审计/监控 |
| 8.0.16 | ✅ activate_all_roles_on_login | 简化激活流程 |
| 8.0.19 | ✅ 角色授权可跨主机名 | 灵活性提升 |
| 8.0.30+ | ✅ 性能优化,角色缓存加速 | 大规模用户场景 |
5.2 2026 年仍存在的“遗留设计”
-
角色仍是“虚拟用户”
角色存储在mysql.user表中,与真实用户混在一起。
现状:8.0 未分离,导致mysql.user表膨胀。
社区方案:Percona Server 已支持独立角色存储,社区版未合并。 -
角色与主机名绑定
CREATE ROLE 'role'@'host'——角色仍然带有主机名,这是从user表继承的包袱。
问题:角色本应是全局的,为何要指定主机?
原因:权限表(db、tables_priv)包含user和host联合主键。
现状:8.0 推荐使用@'%'创建角色,实际使用中忽略主机名。 -
动态权限存储效率低
mysql.global_grants每行存储一个权限名(字符串),而非位掩码。
代价:权限验证时需查找哈希表,比位运算慢一个数量级。
现状:8.0 未优化,因为动态权限数量有限(<100)。 -
角色层级不支持循环检测
GRANT role1 TO role2; GRANT role2 TO role1;会报错,但检测是在提交时,而非授权时。
现状:8.0 仍有微小窗口可创建循环依赖(极少见)。 -
SUPER权限仍未完全拆解
8.0 已将部分SUPER职责拆分到动态权限,但仍有遗留功能依赖SUPER。
现状:9.0 计划继续拆分,但完全淘汰SUPER尚需时日。
5.3 未来趋势:RBAC 完全替代直接授权
2026 年现状:
- 新项目:100% 采用角色授权,不直接对用户授权。
- 存量系统:混合模式,逐步迁移。
- 云数据库:已抽象为 IAM 角色,MySQL 角色作为内部映射。
MySQL 9.x/10.x 预测:
- 独立角色存储表:将角色从
mysql.user移出,不再占用用户条目。 - 全局角色:去掉角色中的
host字段,角色成为纯抽象实体。 - 动态权限位图化:高频动态权限可编译到位掩码。
- 角色继承性能优化:预计算扁平化权限集,避免递归激活。
终极形态:
未来 MySQL 权限系统将分为两层:
- 内部层:user@host + 位掩码权限(兼容性保留)。
- 外部层:角色 + 动态权限(推荐使用)。
新代码只操作角色,旧权限模型仅供遗留系统使用。
六、结语:权限管理的现代化,不止于 RBAC
MySQL 8.0 的角色与动态权限,是权限系统二十年来最大的一次架构升级。
它没有推翻原有的 user@host 模型,而是在其上构建了一层更符合现代企业管理模式的抽象。
这次升级的深远意义:
- 从“管人”到“管职能”。
- 从“静态配置”到“动态激活”。
- 从“封闭集”到“开放扩展”。
2026 年的今天:
- 角色已是生产环境标配。
SUPER权限正在被逐项拆解。- 动态权限成为新功能的安全门禁。
但角色不是终点——当企业规模扩张到数千人、数万个数据库实例时,MySQL 角色仍然不够用。
那时,我们需要的是对接企业 LDAP、OAuth2、IAM,让数据库权限成为组织权限管理的延伸。
到那时,MySQL 的角色模型将退居二线——
不是被废弃,而是作为最后一道兼容层,托起整个云原生权限体系。
参考文献
sql/auth/sql_authorization.cc,sql_auth_cache.ccMySQL 8.0.33 源码mysql/plugin.h– 动态权限注册 API- MySQL Internals Manual – Role-based Privilege Model
- Oracle Blogs: “MySQL 8.0: Roles” (2017)
- Oracle Blogs: “MySQL 8.0: Dynamic Privileges” (2018)
- WL#10050: Roles and default roles
- WL#11032: Dynamic privileges
- WL#11386: SUPER privilege split