Oracle利用位运算实现一个字段表达多种状态

背景

由于业务原因,系统的数据需要发送到多个用户定义的Kafka的不同Topic中,同时需要确认发送成功后修改发送的状态,由于数据量很大,不可能新建表记录每个用户的发送状态,只能考虑同一个字段表达多个用户发送状态的解决方案。

位运算

最终考虑的解决方案是使用二进制表达不同用户的发送状态,每个用户占其中一位,使用0和1表达未发送和已发送的状态。

如0101,表示第一个和第三个用户已发送,第二个和第四个用户未发送。

  • 查询指定用户未发送的可以使用与运算
  • 修改用户已发送可以使用或运算
  • 将已发送状态改为未发送可以使用异或运算

按位与(&)

按位与的运算规则

操作数1 0 0 1 1
操作数2 0 1 0 1
按位与结果 0 0 0 1

规则总结:只有两个操作数对应位同为1时,结果为1,其余全为0. (或者是只要有一个操作数为0,结果就为0)。

从以上规则可知,如果要查询第三个用户未发送的记录,操作数应该是0100,满足 [任意数字] & 0100 = 0 条件的就是第三个用户未发送的记录了。

按位或(|)

按位或的运算规则

操作数1 0 0 1 1
操作数2 0 1 0 1
按位或 0 1 1 1

规则总结:只有两个操作数对应位同为0时,结果为0,其余全为1.(或者是只要有一个操作数为1,结果就为1)。

从以上规则可知,修改第三个用户的记录为已发送,操作数应该是0100,直接设置状态为 [任意数字] | 0100

按位异或(^)

按位异或的运算规则

操作数1 0 0 1 1
操作数2 0 1 0 1
按位异或 0 1 1 0

规则总结:异:1。

从以上规则可知,将第三个用户的已发送的记录修改为未发送,操作数应该是0100,直接设置状态为 [任意数字] ^ 0100

Oracle的操作

对于与运算,Oracle原生支持。

select BitAnd(6, 4) from dual;

或运算和异或运算需要自定义存储过程

-- 或运算
Create or replace function BitOr(a in int, b in int) return int
    is
begin
    return a + b - bitand(a, b);
end;

-- 异或运算
Create or replace function BitXor(a in int, b in int) return int
    is
begin
    return a + b - 2 * bitand(a, b);
end;

Oracle也不支持十进制直接转二进制,对于debug来说很不友好,同样需要自定义存储过程

CREATE OR REPLACE FUNCTION F_NUM_TO_BIN(p_num NUMBER) RETURN VARCHAR2
    IS
    r_binstr VARCHAR2(32767);
    l_num    NUMBER := p_num;
BEGIN
    WHILE l_num != 0
        LOOP
            r_binstr := TO_CHAR(MOD(l_num, 2)) || r_binstr;
            l_num := TRUNC(l_num / 2);
        END LOOP;

    RETURN r_binstr;
END F_NUM_TO_BIN;

Demo演示

为了演示效果,创建一张简单的表:

create table DPM_TEST_BINARY
(
    ID     NUMBER(22),
    STATUS NUMBER
)

插入几条数据

INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (1, 32768);
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (2, 42258);
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (3, 65535);
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (4, 65535);
INSERT INTO DDSJ.DPM_TEST_BINARY (ID, STATUS) VALUES (5, 0);

这里对第五个用户进行操作,二进制是:0000000000010000,转为十进制的值是:16

查询未发送的记录

Select ID, STATUS, F_NUM_TO_BIN(STATUS)
from DPM_TEST_BINARY
where BitAnd(STATUS, 16) = 0;

结果查询除了ID为1和5的数据,符合我们的预期。

将未发送修改为已发送

update DPM_TEST_BINARY set STATUS=BITOR(STATUS, 16) where BitAnd(STATUS, 16) = 0;
commit;

再次查询,发现第五位修改为1,其他位都没有变化,符合预期。

将已发送修改为未发送

update DPM_TEST_BINARY set STATUS=BITXOR(STATUS, 16);
commit;

除了第五位修改为0,其他位都没有变化,符合预期。

综上,完成了一个字段表达多种状态的目标。

参考

逻辑运算符"&"配合二进制实现一个字段表示多个判断条件的状态

Java位运算原理及使用讲解

Oracle里二进制与十进制的相互转换

oracle位与或等比特运算

Oracle利用位运算实现一个字段表达多种状态

上一篇:常见sqlite3 API的简单说明


下一篇:一文快速入门分库分表中间件 Sharding-JDBC (必修课)