mysql 8.0原生角色不适用于业务权限控制,必须自建rbac五表模型;permissions表需perm_code字段和type类型码;user_roles/role_permissions设联合主键及role_id索引;权限应登录时加载至redis并用通配del及时失效。

MySQL 8.0中RBAC表设计必须避开原生角色功能的陷阱
直接复用MySQL 8.0的CREATE ROLE和GRANT ... TO role做业务权限控制,是典型误用。原生角色只管数据库对象级权限(如SELECT ON db.table),无法表达“订单管理-导出”“用户列表-批量禁用”这类应用层操作权限,也不支持动态数据权限(如“仅查看本部门订单”)。业务系统需要的是自己建表实现的RBAC模型,不是DBA用的权限体系。
- MySQL原生角色不存于业务数据库,无法被应用代码查询或缓存,每次鉴权都得连
INFORMATION_SCHEMA查,性能差且不可控 -
role_edges等系统表只有root能读,业务账号无法JOIN获取用户有效权限 - 应用部署、灰度、多租户场景下,权限配置需版本化、可审计、可回滚——原生角色不支持DDL日志追踪
五张核心表的字段取舍与索引关键点
标准RBAC需users、roles、permissions、user_roles、role_permissions五张表,但字段不能照搬教程。重点在三个地方:
-
permissions表必须有perm_code(如order:export)而非仅name,这是代码中hasPermission("order:export")的匹配依据;type字段建议用tinyint(1=菜单,2=按钮,3=API),比varchar更省内存且排序快 -
user_roles和role_permissions必须设联合主键(user_id, role_id和role_id, permission_id),并额外加INDEX(role_id)——因为查某角色所有权限的频率远高于查某用户所有角色 -
users表别加status字段来软禁用,直接删记录或用is_deleted+ 唯一约束防重名;软删除会导致user_roles关联数据悬空,JOIN时容易漏权限
连接池环境下权限加载的坑:别在每次请求里查表
Spring Boot或Node.js连接池中,若每个HTTP请求都执行SELECT p.perm_code FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN role_permissions rp ON ur.role_id = rp.role_id JOIN permissions p ON rp.permission_id = p.id WHERE u.username = ?,数据库会成为瓶颈。正确做法是:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用户登录成功后,一次性查出所有
perm_code,存入Redis,key为user:perms:{user_id},过期时间设为30分钟(配合JWT过期策略) - 权限变更时,用
PUBLISH通知所有服务节点清空对应key,避免轮询或定时刷新 - 绝对不要在MyBatis的
@Select里写四表JOIN——即使加了索引,QPS上500后响应延迟就明显抖动
权限变更后如何安全生效而不重启服务
线上改一个角色的权限,要求5秒内全量生效,不是靠FLUSH PRIVILEGES(它对自建RBAC表无效)。关键在三层同步:
- 数据库层:用
INSERT INTO role_permissions后,立刻SELECT ROW_COUNT()确认写入成功,失败则抛异常中断事务 - 缓存层:用Redis的
DEL user:perms:* user:roles:{user_id}通配清除,而不是单删——因为用户可能有多个角色,新权限可能来自未缓存的角色 - 应用层:前端按钮显隐靠后端接口返回的
permissions数组控制,禁止前端硬编码v-if="hasPermission('order:delete')"却没从后端拉取最新值
最易被忽略的是:权限表字段加了NOT NULL约束,但插入时传了NULL,MySQL会静默转成默认值(如空字符串),导致perm_code变成"",而代码里hasPermission("")永远返回true——这种bug上线后极难定位。










