mysql位运算符仅支持整数类型,非整数输入静默返回null;常用于权限位判断(如flags & 4)、状态位修改(如flags | 4)及函数索引优化。

MySQL 位运算符能直接操作整数的二进制位
MySQL 的 &、|、^、~、、<code>>> 这些运算符,只对整数类型(如 TINYINT、INT、BIGINT)生效,不会自动转换字符串或 NULL。一旦传入非整数,结果直接是 NULL,且不报错——这是最常踩的静默陷阱。
常见错误现象:SELECT '10' & '5'; 返回 NULL,而不是你预期的 0;SELECT id & 4 FROM users WHERE status = 'active'; 如果 id 是 VARCHAR,整个表达式全为 NULL。
- 务必确认字段类型是整数型,用
DESCRIBE table_name;或SHOW COLUMNS FROM table_name;检查 - 需要从字符串转整数时,显式用
CAST(col AS SIGNED)或CONVERT(col, SIGNED),别依赖隐式转换 -
NULL参与任何位运算都得NULL,提前用IFNULL()或COALESCE()处理
用 & 判断权限/状态位是否开启
这是位运算最典型的使用场景:把多个布尔状态压缩进一个整数字段,比如 flags TINYINT UNSIGNED,用第 0 位表示「已验证」、第 2 位表示「VIP」、第 3 位表示「邮件订阅」。
判断用户是否 VIP(即第 2 位是否为 1),正确写法是:flags & 4(因为 4 = 100₂,对应第 2 位)。只要结果非零,就说明该位被置 1。
- 不要写成
flags = 4,那只能匹配「恰好只有 VIP 位开启」的极端情况 - 不要用
flags & 4 > 0,虽然等价,但多一次比较,MySQL 优化器不一定能完全消除 - 若要查同时满足多个位(如「VIP 且 邮件订阅」),用
flags & 12 = 12(12 = 1100₂ = 4 | 8),而非flags & 4 AND flags & 8,前者单次计算更高效
| 和 ^ 修改状态位时必须配合 UPDATE + 原值
MySQL 没有原地置位(set bit)或清位(clear bit)的专用语法,所有修改都得基于当前值做运算。比如给用户加 VIP 权限(置第 2 位),不能直接 “设为 4”,而要用 flags | 4;取消则用 flags & ~4。
错误做法:UPDATE user SET flags = 4 WHERE id = 123; —— 这会抹掉其他所有位。
- 开启某位(如 VIP):
UPDATE user SET flags = flags | 4 WHERE id = 123; - 关闭某位(如取消邮件订阅,第 3 位是 8):
UPDATE user SET flags = flags & ~8 WHERE id = 123; - 翻转某位(如切换冻结状态,假设第 1 位):
UPDATE user SET flags = flags ^ 2 WHERE id = 123; - 注意
~是按位取反,在有符号整数上会变成负数,建议统一用无符号类型(UNSIGNED)或显式掩码,例如关第 2 位更安全的写法是flags & 251(251 = 11111011₂)
位运算在索引和性能上的实际限制
MySQL 无法对形如 flags & 4 的表达式使用普通 B+Tree 索引——优化器看不到原始列值,只能全表扫描。想加速这类查询,必须建函数索引(MySQL 8.0.13+)或冗余字段。
- 可行方案 1(推荐):建函数索引
CREATE INDEX idx_vip ON user ((flags & 4));,注意括号不能省 - 可行方案 2:增加生成列
is_vip TINYINT AS ((flags & 4) > 0) STORED,再给它建索引 - 别指望
WHERE flags IN (4, 5, 6, 7)能走索引,这本质上仍是范围扫描,且可读性差 - 位运算本身开销极小,瓶颈永远在 I/O 和索引缺失,不是 CPU
真正容易被忽略的是:位字段一旦设计过宽(比如用 BIGINT 存 64 种状态),后续新增状态就得改 schema,而且 ~ 在不同宽度整数下行为不一致——测试时用 TINYINT 没问题,上线换成 INT 后 ~1 从 254 变成 4294967294,逻辑就崩了。









