直接建users、roles、permissions三张表会出问题,因为rbac核心是控制访问链路而非单纯存数据,导致权限动态开关难、角色继承无法表达、菜单与api权限混查性能差,且缺乏扩展性难以支持数据级权限或临时授权。

为什么直接建 users、roles、permissions 三张表会出问题
因为 RBAC 的核心不是“存数据”,而是“控制访问链路”。很多人一上来就设计 users、roles、permissions 三张表加中间关联表,结果发现权限开关难动态、角色继承没法表达、菜单和 API 权限混在一起查得慢。
关键在于:MySQL 本身不校验权限逻辑,所有判断都在应用层做。如果表结构没预留扩展性,后面加「数据级权限」(比如只看自己部门的数据)或「临时授权」就得改表、改 SQL、改业务代码。
-
permissions表里别只存字符串如"user:delete",要拆成resource("user")、action("delete")、scope("own"/"dept"/"all"),否则后期无法按维度过滤 - 别用 JSON 字段存权限规则——MySQL 5.7+ 虽支持 JSON 函数,但
JSON_CONTAINS无法走索引,大表查询直接变慢十倍 - 角色之间要有层级关系?加个
parent_id字段比每次查多层 JOIN 更可控;但注意循环引用检测必须在应用层做,MySQL 没法递归约束
role_permissions 中间表要不要加唯一索引
要,而且必须是联合唯一索引:UNIQUE KEY `uk_role_perm` (`role_id`, `permission_id`)。
不加的后果很实际:同一角色反复导入权限时,可能插入重复记录;应用层做去重又得先 SELECT 再 INSERT,并发下照样脏数据。MySQL 的 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE 都依赖这个索引生效。
- 别只给
role_id加普通索引——单字段索引对联合查询无效,WHERE role_id = ? AND permission_id = ?仍会全表扫 - 如果还要按权限反查角色(比如“谁有这个权限”),再补一个
INDEX idx_perm_role (permission_id, role_id),避免ORDER BY或LIMIT时额外排序 - 别用 UUID 做
role_id或permission_id主键——写入时页分裂严重,RBAC 表虽小,但高频读写下性能差异明显
用户登录后如何高效查出全部可访问路由和按钮
不要在每次请求时拼一堆 LEFT JOIN 查 users → user_roles → roles → role_permissions → permissions。四层 JOIN 在 10w+ 用户系统里,哪怕加了索引,平均响应也超 80ms。
更稳的做法是:登录成功后,用一次聚合查询把当前用户所有有效权限 ID 和资源动作对,拼成结构化缓存(如 Redis Hash),后续接口直接 HGETALL 拿。MySQL 层只负责生成这份快照。
- 聚合 SQL 示例:
SELECT DISTINCT p.resource, p.action, p.scope FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN roles r ON ur.role_id = r.id AND r.status = 'active' JOIN role_permissions rp ON r.id = rp.role_id JOIN permissions p ON rp.permission_id = p.id AND p.status = 'enabled' WHERE u.id = ?
- 注意
r.status和p.status字段必须加索引,否则 JOIN 过程中会漏掉状态过滤 - 如果用了角色继承(比如 admin 继承 editor),这个查询得改成递归 CTE(MySQL 8.0+)或应用层分步查,别指望单条 SQL 解决
ALTER TABLE 加字段时怎么避免锁表影响线上权限变更
MySQL 5.6+ 的 ALGORITHM=INPLACE 并不总生效。比如给大表 roles 加一个 is_system 字段,如果没指定 DEFAULT 值,DDL 仍会触发全表拷贝。
真正安全的操作只有两个前提:字段带默认值 + 使用 ADD COLUMN(不是 MODIFY COLUMN)。否则运维半夜执行 DDL,权限后台页面就打不开。
- 正确写法:
ALTER TABLE roles ADD COLUMN is_system TINYINT DEFAULT 0 AFTER name - 错误写法:
ALTER TABLE roles ADD COLUMN is_system TINYINT—— 缺少DEFAULT,5.7 下会锁表 - 上线前务必在从库执行
SHOW PROCESSLIST看是否有长事务阻塞 DDL;RBAC 表常被权限中间件高频读,容易被忽略
权限模型越往后越容易卡在细节:比如 scope 字段要不要支持正则匹配、角色失效时间怎么和 JWT 过期对齐、第三方登录带来的用户 ID 映射……这些都不是建完表就能跑通的。










