数据库中利用二进制实现多个布尔属性的增删改查
需求背景
业务场景中经常需要一些布尔型的属性来标记数据状态, 如果每个属性值都新增一个数据库字段来保存会造成空间上的浪费, 在工作中进行建模设计时难免会想到用二进制来节省空间, 以整形32位为例, 去除最高位的符号位总共可以用来保存31个布尔属性值。
实现细节
为方便讲解, 这里统一用8位二进制数字进行举例说明, 0表示false、1表示true, 从低到高依次对应ABCDEFGH这八个布尔属性值。
- 每个属性值对应固定某个二进制位, 声明一个常量哈希表声明指定, 例如AB属性分别对应的就是'00000001'和'00000010';
- 根据对应的二进制位数值bitVal更新属性值:
- 通过 oldVal & bitVal 将属性值设为true, 如 setA(true) 对应 oldVal = oldVal & '00000001';
- 通过 oldVal & ~bitVal 将属性值设为false, 如 setA(false) 对应 oldVal = oldVal & '11111110';
- 因为业务场景中需要同时查询多个属性部分为true且部分为false的数据, 实现时需要借助另一个变量值来标记具体哪些二进制位参与了数据筛选:
- 假设需要查询AC为true且BF为false的数据, 即用于筛选的对象变量依次调用 setA(true)、setC(true)、setB(false)、setF(false);
- 属性变量queryVal记录指定为true或false后的值, 即 \(queryVal = '00000101'\);
- 属性变量bitCompared记录哪些属性对应二进制位要参与筛选, 即 \(bitCompared = '00100111'\);
- 筛选的SQL子句为 t.val ^ #{queryVal} & #{bitCompared} = 0;
- 满足期望的数据与queryVal值的异或结果会将ABCF对应四个二进制位置零, 再和bitCompared进行与操作后就将其它不需要关注的属性二进制位也置零了;
- 筛选子句也可以用 t.val & #{bitCompared} = #{queryVal} 实现, 主要是看对位运算的理解运用。
总结
- 优点是节省数据库单表字段数量节省空间;
- 缺点是查询时子句有位运算无法再利用索引, 需要看业务场景中有没有诸如时间区间等其它主索引字段辅助减少筛选数据量, 否则影响查询效率;
- 这算是比较常见的二进制知识在工作中的应用, 属于能想到就简单巧妙但是想不到就真挠头的典型位运算技巧, 简单记录一下。
本文表述基于作者主观理解,如有错漏或歧义之处,欢迎评论指出沟通交流