不能直接在join on中写flags & mask = mask做权限筛选,因优化器无法将其作为连接键处理,导致全表扫描和嵌套循环;应改用生成列+索引或exists/维度表映射。

不能直接在 JOIN ON 里写 flags & mask = mask 做权限筛选——这不是语法错误,但会让查询退化成嵌套循环+全表扫描,哪怕 flags 字段有索引也完全用不上。
为什么 ON a.flags & b.mask = b.mask 会慢得离谱
数据库优化器无法把位运算表达式当作连接键(join key)来处理。它既不能建哈希表,也不能排序合并,更没法下推索引范围扫描。实际执行时,引擎只能先做笛卡尔积或嵌套循环,再逐行计算该布尔表达式过滤——本质上等价于 CROSS JOIN ... WHERE。
- 执行计划里常出现
Using where; Not exists或rows_examined高得异常 - 哪怕只有 10 万行用户数据,关联 5 个权限标签表,响应可能从 20ms 拉到 3s+
- MySQL/PostgreSQL/SQL Server 表现一致:语法合法,语义失效,性能崩坏
真正能走索引的 JOIN 写法:用生成列 + 等值匹配
把位判断提前固化为可索引的布尔列,让 JOIN 回归标准等值路径。
- 加生成列:
ALTER TABLE users ADD COLUMN has_delete TINYINT AS ((flags & 4) = 4) STORED - 建索引:
CREATE INDEX idx_has_delete ON users(has_delete) - JOIN 时直接用:
INNER JOIN permissions p ON u.has_delete = p.enabled(前提是p.enabled是常量或枚举值) - 若需动态匹配多个权限组合,改用
EXISTS替代JOIN,语义更清晰、优化器更友好
替代方案:用辅助维度表 + 显式映射,而非硬算
把位掩码含义物化成一张小表,用主键关联代替运行时计算。
- 建表:
CREATE TABLE permission_bits (id TINYINT PRIMARY KEY, name VARCHAR(32), mask TINYINT UNSIGNED NOT NULL, UNIQUE KEY uk_mask (mask)) - 填充:
INSERT INTO permission_bits VALUES (1,'read',1), (2,'write',2), (3,'delete',4) - 查询“有删除权限的用户”:
SELECT u.* FROM users u INNER JOIN permission_bits p ON u.flags & p.mask = p.mask WHERE p.mask = 4 - 注意:这仍含位运算,但右表极小(通常 eq_ref 或
const访问,比大表间位运算 JOIN 稳定得多
容易被忽略的细节:字段类型、NULL 和括号优先级
这三个点不出错,位运算才能按预期工作;出错则静默失效——查不到数据,也不报错。
-
flags必须是TINYINT UNSIGNED NOT NULL DEFAULT 0:用SIGNED会导致负数干扰,允许NULL会让WHERE (flags & 4) != 0漏掉整行 - 所有位运算条件必须加括号:
(flags & 4) = 4,不是flags & 4 = 4(MySQL 解析为flags & 1) - 别在
WHERE里混用LIMIT和位运算JOIN:一旦ON条件不走索引,LIMIT会在最后才生效,中间仍扫全量











