查「有没有某项权限」必须用 column & mask > 0,如编辑权限写 permissions & 2 > 0;查「同时满足多个权限」用 (permissions & mask) = mask,如查看+删除写 (permissions & 5) = 5;& 查询无法走索引,高频场景需用生成列或独立布尔字段优化。

直接用 & 判断权限位是否开启,别用 BIT_COUNT() 做核心校验——它只适合统计总数或粗筛。
查「有没有某项权限」必须用 column & mask > 0
比如权限定义为:查看=1、编辑=2、删除=4、导出=8。要查用户是否有编辑权限,就得写 permissions & 2 > 0,不是 permissions & 2 = 2(虽结果等价,但语义弱),更不能写 BIT_COUNT(permissions & 2) > 0(多此一举,还拖慢查询)。
常见错误现象:
- WHERE 条件里写成
permissions & 2不加比较,MySQL 会隐式转为布尔值,但逻辑难读且易出错 - 漏括号,比如写
permissions & 2 = 2,因=优先级高于&,实际执行的是permissions & (2 = 2)→permissions & 1,全错 - 用
BIT_COUNT(permissions) = 3当作“拥有这三项权限”,其实只是“恰好开了三项”,跟具体是哪三项完全无关
查「同时满足多个权限」用掩码组合 + 等值判断
要查“既有查看又有删除权限”的用户,对应掩码是 1 | 4 = 5,条件应为 (permissions & 5) = 5。括号不能省,否则优先级错乱。
使用场景:
- 角色固定组合(如“管理员”=读+写+删+导出 → 掩码 15)
- 前端传入一组权限 ID,后端拼成整数掩码再查
- 避免写一堆
AND (permissions & 1) > 0 AND (permissions & 4) > 0,可读性差且难维护
注意:如果权限集动态增长(比如运营随时加新权限),掩码值可能超出 TINYINT/SMALLINT 范围,需提前选够大的整型(如 INT 或 BIGINT)并预留符号位空间。
& 查询无法走索引,高频场景得靠生成列
哪怕你在 permissions 字段建了 B+Tree 索引,WHERE (permissions & 4) > 0 也基本触发全表扫描。MySQL 优化器不支持对表达式做索引下推。
现实可行方案:
- 对固定高频查询的权限位,加生成列:
ALTER TABLE users ADD COLUMN has_delete TINYINT AS ((permissions & 4) > 0) STORED;
再给has_delete建索引 - 拆成独立布尔字段(如
is_editor),配合应用层同步更新,牺牲一点存储换查询性能 - 接受全表扫描,仅用于后台低频管理操作;高并发接口务必前置缓存或降级为应用层判断
性能提示:生成列必须声明为 STORED 才能建索引,VIRTUAL 列不行。
增删权限要用 | 和 & ~mask,别手写数值
给用户加编辑权限:UPDATE users SET permissions = permissions | 2 WHERE id = 123;
移除删除权限:UPDATE users SET permissions = permissions & ~4 WHERE id = 123;
容易踩的坑:
- 写成
permissions = 2直接覆盖全部权限,不是叠加 - 用
-操作代替& ~mask,比如permissions - 4,在权限有重叠时会误清其他位(如当前值是 7=111b,减 4=100b 得 3=011b,看似OK;但若值是 5=101b,减 4 得 1=001b,正确;可一旦权限定义非 2 的幂次,就彻底失效) -
~4在 MySQL 中是带符号取反,对 TINYINT 可能得负数,建议统一用无符号类型(如INT UNSIGNED)或显式 CAST 避免隐式转换偏差
真正麻烦的从来不是语法,而是权限语义一旦和业务耦合变深——比如“编辑”权限在 A 模块代表改内容,在 B 模块还隐含“可发布”,这时单靠一个 bit 就撑不住了。位运算适合扁平、稳定、低维的状态管理,别硬套复杂权限模型。











