【进制】数据库应用

概要

最近加了一个月的班,很忙,博客已经有一段时间没有写了,心里很不是滋味,今晚决心,再晚,也要写一篇。

我们平时各种系统最常打交道的系统就是数据库了,我们在数据库中,可能就是最直观,最直接的用法,很少用到一些进制相关的知识在里面,其实进制在我们的生活中处处都在渗透,今天我们重点整理一下数据库和进制相关的内容。

分库分表

我们都知道,数据库的优化策略之一就是分库分表,但是这个分库分表需要怎么分才算好呢,这里我也没有打包票说有一个固定的模式,因为这种所谓的“最优”是要结合具体的需求来说明的。

在这里我会列举其中的一些场景来具体说一下。大部分公司的订单号所以没有一个好的架构师或者比较资深的程序员,订单号一般都是最容易忽略的一个关键,一般毕业生或者初学者都会把订单号设置成简单的 int 并且 autoincrease,这样子的做法,简单粗暴有效,定义好了之后就可以撸起袖子干了。

我们一般的订单结构都会面临几个需求

  • 通过 orderId 查询订单,绝大多数都是这类需求(where orderId = :orderId)

  • 某个用户要查找自己的订单信息(where userId = :userId)

搜索订单数据是一个高频的需求,数据量大了之后,很容易产生慢查询,在高吞吐量,又要保证服务高可用,我们有没有一些手段可以优化一下?答案有:分库分表。

简单的分库分表方法

  • (在索引表和详情表都在一起的情况下)通过 OrderId 求余,例如 OrderId%10,那么的出来的结果就是[0-9]十位数,然后我们的库或者表就通过这么一个[0-9 加相应的后缀],这样子我们在查询的时候,就可以通过简单的 OrderId%10 之后,找到对应的库或者表来进行查询,非科学的比喻一下,这里我们就把一个耗时 100s 的查询速度,最后用了 10s 来完成,在查询上我们通过类似于分区的概念,来提升了我们查询 sql 的性能。

或许乐于思考的同学开始发现问题了,这样子做的结果就是如果我要找“我的订单”呢?怎么办?没错,这就是分库或者分表的一个最大的弊端,这个时候,可能你就要通过遍历所有的表来查询“我的订单”了,因为基于 OrderId 的分库分表在 UserId 上一点关系都没有。

那么我反过来思考一下,那我换一种思维,用 UserId 分库分表呢,这个时候确实是可以快速查询到了“我的订单”,但是这个时候,如果是要通过 OrderId 来查数据呢?这个时候就惨了,你可能还是需要遍历全量数据来找到这个数据,运气好的话,可能就在第一次查询的时候刚好就查到了,运气不好的话,可能就是查全量数据了。

基因法

顾名思义,我们接下来要将的就是和“基因”有关,为什么这么说呢,因为我们需要把 UserId 和 OrderId 通过某种方式让他们产生关联。

举个具体的例子:

OrderId = 32 , UserId = 666
(这个时候,我们的分库标志是 UserId 的最后一个十进制位,也就是 6)

我们的 orderId 是不是可以*100,然后+666?

结果:32666,那么如果我的需求的通过 UserId 来的话,那就截取最后一位,是不是就可以快速查询?是的,这个时候可以满足“我的订单”,“对应的 OrderId”,落在了同一个表里面了。

或许!!!你以为这就完了,善于思考的同学这个时候发现问题了,这样子的做法,基本上就是会让我的 OrderId 的字段迅速达到最大值,或许,这个时候,你就要考虑 bigint 了,再不济,有可能碎玉 OrderId 和 UserID 的增加,这个值有可能连 bigint 都存不下了。这简直就是个灾难。

这个时候,或许,我们是不是应该换一种思维,利用二进制呢?我们要考虑到这一点的前提是十进制和二进制的关系,十进制和二进制有什么区别呢,十进制是逢十进一,二进制是逢二进一,或许这个时候你还好没有发现奇妙的地方。

举个例子:一个十进制的 9,转成二进制就是 1001

接着上面的例子。

OrderId = 32 , UserId = 17

OrderId 二进制就是:100000

UserId 二进制就是:10001

我们对 UserId%16,这个时候,UserId 得到的结果又 16 种可能[0-15]

这个时候,我们知道 16 一共是 4bit,我们把这 4bit 拼接到 100000 后面,那就是:10 0000 0001 ,十进制的写法也就是说(32 << 4) | 1 结果等于 513。

(32 << 4) | 1 详解:<< 4 代表向左移动 4 位,往高位移动,| 1 是因为 0001 的十进制是 1,并且我们相加是通过逻辑或完成。

我们试一下:

513 % 16 = 1

17 % 16 = 1

这个时候,我们就完成了二进制的基因法,并且我们可以省下了很多的空间,因为一个 int 是 4 个字节,32 位。最大值就是 2 的 32 次方-1,如果不放心的话用 bitint,基本上是肯定够了。

这个时候,我们的标志位都是 1,那么久可以在同一个表中查询我们想要的数据!

如果在加多一种属性,那么可能就需要采用冗余的方法来了,一般基因法用于 1 对多的场景,常在多的一方嵌入 1 的基因。

单字段存储多个 0-1 关系的属性

…后续补充