在这里插入图片描述
你正在设计一张“用户表”,产品经理一口气提了 N 个开关需求:

  • 是否验证过邮箱?
  • 是否验证过手机?
  • 是否绑定了微信?
  • 是否是 VIP?
  • 是否被封禁?
  • 是否开启了推送通知?
  • … (此处省略 20 个 flag)

噩梦设计:
你在表中加了 20 多个 TINYINT 字段:email_verified, phone_verified, wx_bound
每次新增一个状态,你都要 ALTER TABLE,还要改 Java 代码里的 Entity,改前端的接口。表变得越来越宽,维护极其痛苦。
极客设计:
只加 一个 BIGINT 字段,名叫 status_mask。利用二进制位,一行代码搞定所有状态的存储和查询。


1. 核心原理:二进制的魔法

计算机底层只有 0 和 1。我们可以把一个整数看作一串二进制开关
一个 INT (32位) 可以存 32 种状态,一个 BIGINT (64位) 可以存 64 种状态。

状态定义 (2 的 N 次幂):

我们给每个状态分配一个独一无二的位值

  • 1 (Binary: ...0001): 邮箱已验证
  • 2 (Binary: ...0010): 手机已验证
  • 4 (Binary: ...0100): 绑定微信
  • 8 (Binary: ...1000): 是 VIP
  • 16 (Binary: ...10000): 账号被封禁

组合状态:
如果一个用户既验证了邮箱 (1),又是 VIP (8),他的 status_mask 值就是 (Binary: ...1001)。


2. SQL 实战:增删改查的位运算技巧

在 MySQL 中,我们直接使用 & (与), | (或), ^ (异或), ~ (取反) 符号。

A. 开启某个状态 (OR 运算 |)

逻辑:不管原来是什么,把对应位置为 1。

-- 用户验证了手机 (值为 2)
-- 原值如果是 9 (1001),变成 9 | 2 = 11 (1011)
UPDATE users 
SET status_mask = status_mask | 2 
WHERE id = 1001;

B. 关闭某个状态 (AND NOT 运算 & ~)

逻辑:不管原来是什么,把对应位置为 0。

-- 用户 VIP 过期了,取消 VIP (值为 8)
-- status_mask & (~8)
UPDATE users 
SET status_mask = status_mask & ~8 
WHERE id = 1001;

C. 查询是否具备某个状态 (AND 运算 &)

这是最高频的操作。

-- 查询所有 "是 VIP" 的用户
-- 如果结果 > 0 (或等于 8),说明该位是 1
SELECT * FROM users 
WHERE (status_mask & 8) > 0;

-- 查询 "既验证了邮箱(1) 又绑定了微信(4)" 的用户
-- 1 + 4 = 5
SELECT * FROM users 
WHERE (status_mask & 5) = 5;


3. 三大实战场景

场景一:用户偏好设置 (User Preferences)

用户在设置页有几十个开关:

  • 接收邮件通知 (1)
  • 接收短信通知 (2)
  • 接收 App 推送 (4)
  • 允许被搜索发现 (8)

优势: 产品经理今天要加一个“接收营销电话”,你只需要在代码里定义一个新的常量 CONST_MARKETING = 32,数据库完全不用动

场景二:权限控制系统 (RBAC)

这是 Linux 文件权限的经典复刻(r=4, w=2, x=1)。
在简单的权限系统中,我们可以给每个原子权限定义一个位:

  • CREATE (1)
  • READ (2)
  • UPDATE (4)
  • DELETE (8)

查询: 查所有拥有“删除权限”的管理员。
SELECT * FROM admins WHERE (permissions & 8) > 0;

场景三:商品/内容的多维标签

商品可能有各种布尔属性:

  • 是否热销 (1)
  • 是否新品 (2)
  • 是否包邮 (4)
  • 是否参与双11 (8)

如果用位存储,前端传参只需要传一个整数,后端判断逻辑非常统一。


4. 避坑指南与局限性

虽然位运算很爽,但它有明显的短板,使用前必须评估:

  1. 索引失效 (Index Inefficiency):
    WHERE (status & 8) > 0 这种写法,通常无法利用 B+ 树索引
  • 后果: 如果用户表有几千万行,查 VIP 用户会导致全表扫描。
  • 解法:
  • MySQL 8.0 可以创建 函数索引 (Functional Index): CREATE INDEX idx_is_vip ON users ((status_mask & 8));
  • 或者只用于数据量小、或必定会带上 user_id 主键查询的场景。
  1. 可读性差:
    数据库里存个 13,没人知道它是啥意思(1+4+8)。
  • 解法: 必须在代码文档或注释中严格维护位值映射表。
  1. 有限性:
    BIGINT 最多 64 位。如果你的状态超过 64 个,就得开第二个字段 status_mask_2

5. 总结

位运算 (Bitwise) 是用“计算换空间”和“维护灵活性”的经典案例。

  • 什么时候用? 状态多、并发修改少、不需要对某个特定状态进行高频范围查询(除非加函数索引)的场景。
  • 拒绝: 每次新增一个 Boolean 状态就去改表结构。
  • 拥抱: status_mask | FLAG 的优雅。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐