摘要
角色(Role)是 MySQL 8.0 权限系统中最具颠覆性的功能——它将权限从“直接授予用户”升级为“先授予角色,再将角色授予用户”的两层模型,从此 DBA 可以像管理代码库一样管理权限集合。与此同时,动态权限(Dynamic Privileges)打破了传统静态权限(SELECTINSERT 等)的封闭集合,允许存储引擎、插件、组件在运行时注册全新的权限类型。

本文从角色与用户的关系模型切入,系统拆解角色的存储实现(虚拟用户)、激活机制(SET ROLE)、强制角色(mandatory_roles)及默认角色。深入 sql/auth/sql_authorization.cc 源码,完整还原 GRANT ROLEREVOKE ROLE 的事务化执行流程及角色缓存管理。动态权限部分以 CONNECTION_ADMINTABLE_ENCRYPTION_ADMIN 为例,展示插件如何注册新权限。生产实践部分提供角色层级设计、迁移策略及与旧权限模型的共存避坑指南。最后基于 2026 年视角,讨论 RBAC 对传统 user@host 权限模型的替代进度,以及未来 IAM 集成对 MySQL 权限系统的终极影响。


一、核心概念与底层图景

1.1 定义

角色是一个命名的权限集合,可被授予用户或其他角色,激活后用户获得该角色包含的所有权限。

传统权限模型用户 ← 权限(多对多)
角色模型用户 ← 角色 ← 权限(用户-角色-权限三层)

动态权限:非内置的、由插件或组件在运行时注册的权限,用于控制对特定功能模块的访问。

设计哲学

  • 权限管理抽象化:将细粒度权限聚合成有业务含义的“角色”,如 read_onlybackup_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_grantsmysql.dbmysql.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 表膨胀,包含大量非登录账号;权限表(dbtables_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_ACLINSERT_ACLUPDATE_ACL……共约 30 个,编译时固定。

8.0 动态权限:运行时由插件/组件注册的全新权限。

内置动态权限示例(8.0.33):

权限名称引入版本作用
CONNECTION_ADMIN8.0.14管理连接(替代 SUPER 的部分职责)
TABLE_ENCRYPTION_ADMIN8.0.14管理表加密密钥
BINLOG_ADMIN8.0.14管理二进制日志
REPLICATION_APPLIER8.0.18复制应用线程专用
GROUP_REPLICATION_ADMIN8.0.14管理组复制
BACKUP_ADMIN8.0.14备份锁管理
PERSIST_RO_VARIABLES_ADMIN8.0.14持久化只读变量
SYSTEM_VARIABLES_ADMIN8.0.14管理系统变量
ROLE_ADMIN8.0.14管理角色(非强制)
SESSION_VARIABLES_ADMIN8.0.14管理会话变量
AUDIT_ADMIN8.0.14管理审计日志
FIREWALL_EXEMPT8.0.23绕过防火墙
SENSITIVE_VARIABLES_OBSERVER8.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全局ONSHOW GRANTS 是否显示角色定义
show_grants_information_schema全局ON8.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_edgesmysql.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.14mandatory_roles强制审计/监控
8.0.16activate_all_roles_on_login简化激活流程
8.0.19✅ 角色授权可跨主机名灵活性提升
8.0.30+✅ 性能优化,角色缓存加速大规模用户场景

5.2 2026 年仍存在的“遗留设计”

  1. 角色仍是“虚拟用户”
    角色存储在 mysql.user 表中,与真实用户混在一起。
    现状:8.0 未分离,导致 mysql.user 表膨胀。
    社区方案:Percona Server 已支持独立角色存储,社区版未合并。

  2. 角色与主机名绑定
    CREATE ROLE 'role'@'host'——角色仍然带有主机名,这是从 user 表继承的包袱。
    问题:角色本应是全局的,为何要指定主机?
    原因:权限表(dbtables_priv)包含 userhost 联合主键。
    现状:8.0 推荐使用 @'%' 创建角色,实际使用中忽略主机名。

  3. 动态权限存储效率低
    mysql.global_grants 每行存储一个权限名(字符串),而非位掩码。
    代价:权限验证时需查找哈希表,比位运算慢一个数量级。
    现状:8.0 未优化,因为动态权限数量有限(<100)。

  4. 角色层级不支持循环检测
    GRANT role1 TO role2; GRANT role2 TO role1; 会报错,但检测是在提交时,而非授权时。
    现状:8.0 仍有微小窗口可创建循环依赖(极少见)。

  5. 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.cc MySQL 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