MySQL大表分库分表基因法

1.确认需求:一张订单表,三个字段:user_id,order_id,amount;假设一天会产生10亿的数据,现在需要根据user_id 和 order_id查询数据;

2.需求分析:数据库磁盘、cpu必然压力巨大,需要分库+分表;

一、索引表法

1.索引表法:10个库+每个库100张表,平均每张表每天会产生100w的数据,这样每张表每个月就会产生3000w的数据,在这个表中我们可以至保留一个月的数据,其余的数据归档至数据仓库,比如我们需要olap场景,就归档至clickhouse、es等;

2.索引表法:我们需要根据user_id、order_id,这两个字段对订单进行查询,我们先对order_id进行分片键设计;

3.索引表法: hash(order_id) % 10 得到库,hash(order_id) % 100 的到表

4.索引表法:为了能通过user_id查询order_id,需要额外建一张user_id->order_id的索引表。这个索引表也需要根据user_id作为分片键进行相应的分库分表;

5.索引表法:这样我们通过order_id能够直接定位数据库的表进行查询,通过user_id先查order_id再通过order_id再查具体内容,满足需求;

6.反思:上面的做法可以实现需求,但是通过user_id查询订单时,需要多进行一次查询,效率降低了一倍;并且索引表也需要进行分库分表,当然索引也可以考虑其他存储介质,如Hbase,或者增加缓存来提高索引效率;如果需要某个用户订单列表的话,还需要在应用层做数据整合,很麻烦;

二、基因法

1.基因法:我们考虑有没有一种做法,可以把一个用户的所有订单数据都落到同一个库同一个表中,这样不管是通过user_id定位,还是通过order_id都能准确定位到数据,解决了引入索引表的复杂性,并且解决了应用层整合数据的麻烦;

2.基因法:解析一下基因法的思想,我们希望达到的效果是,一个用户的所有数据都落到同一个库中的同一个表,那么我们可以先对user_id进行取余,落库,然后在生成order_id时就不能随便生成了,需要从user_id中提取基因,在生成order_id的时候,把这个基因放到order_id的生成过程中,这样生成出来的order_id通过取余等运算就能得到和user_id一致的结果了,也就是达到了同一个用户的所有数据都落到同一个库的同一个表中的效果;

3.基因法:现在我们把焦点集中在了“基因”这个点上,我们先来看看一个数a对另外一个数b(数b为2^n)进行取余时,其实本质上最后的结果就是a这个数二进制的最后(n)位,举个例子:9%4 = 1(1001 % 100 = 001)/ 10 % 4 = 2 (1010 % 100 = 010)/ 15% 8=7(1111 % 1000 = 111),那么我们在生成订单id 的时候,只要把order_id二进制的最后(n)位的二进制数设置为user_id的最后(n)位,那么我们对user_id/order_id取余都能得到相同的结果了。(原理:比n位高的值,都是b数的倍数,取余时直接归零,所以取余就是取二进制最后n位)

4.基因法:了解原理后,我们只需要重新合理设计分库分表的数量,让其都是 2^n,我们重设 16个库每个库64张表 ;

5.基因法:hash(user_id)% 16 定位库的位置, hash(user_id)% 64 定位表的位置

6.基因法:生成order_id ,对user_id提取一个基因 % 64 也就是二进制最后 6位,把这个最后6位二进制也作为order_id的二进制最后6位,这样就能保证order_id的路由结果与user_id完全一致;

7.基因法:通过基因法,不管是通过order_id查询数据,还是通过user_id查询数据,都能准确定位到具体的表,效率高;

记录一下基因法的代码实现

    public static void main(String[] args) {
        SnowFlakeIdGenerator snowFlakeIdGenerator = new SnowFlakeIdGenerator();

        for (int i = 1; i < 1000; i++) {
            //生成userId和提取基因
            long userId = snowFlakeIdGenerator.generateId();
            String userIdGene = fetchGene(userId, 64);
            
            //利用基因生成orderId
            long rawOrderId = snowFlakeIdGenerator.generateId();
            Long orderId = generateWithGene(rawOrderId, userIdGene);

            System.out.println("userId: " + userId + ",余数:" + userId % 64);
            System.out.println("orderId: " + rawOrderId + ",余数:" + orderId % 64);
        }
    }

    /**
     * 抽取基因
     *
     * @param id    id
     * @param index 16/64 需要取余的数
     * @return {@link String}
     */
    public static String fetchGene(Long id, Integer index) {
        return String.format("%07d", Integer.valueOf(Long.toBinaryString(id % index)));
    }

    /**
     * 根据基因,生成id
     *
     * @param rawId        原始id
     * @param binarySuffix 二进制后缀
     * @return {@link Long}
     */
    public static Long generateWithGene(Long rawId, String binarySuffix) {
        String s = Long.toBinaryString(rawId);
        String substring = s.substring(0, s.length() - binarySuffix.length());
        String newBinaryString = substring + binarySuffix;
        return Long.parseLong(newBinaryString, 2);
    }

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
MySQL中,分库分表是一种常用的数据库架构设计方,用于解决大规模数据存储和查询的性能问题。它将一个大型数据库拆分成多个小型数据库(分库),并将每个小型数据库的表再拆分成多个子表(分表)。这样可以将数据分散存储在多个数据库和表中,提高数据库的并发处理能力和查询性能。 分库分表的实现方式有多种,下面是其中两种常见的方式: 1. 垂直分库分表: 垂直分库分表是按照业务功能将数据库中的表进行划分,每个数据库只包含特定的业务功能所需的表。例如,可以将用户相关的表放在一个数据库中,商品相关的表放在另一个数据库中。这种方式适用于业务功能之间关联性较低的场景。 2. 水平分库分表: 水平分库分表是按照数据行进行划分,将每个表的数据行均匀地分散到多个数据库或表中。例如,可以按照用户ID的哈希值将用户表的数据行分散到不同的数据库或表中。这种方式适用于业务功能之间关联性较高的场景。 实现分库分表需要考虑以下几个方面: - 数据迁移:将现有的数据迁移到新的数据库和表中,保证数据的完整性和一致性。 - 数据路由:根据查询条件将查询请求路由到相应的数据库和表中,确保查询的准确性和效率。 - 分布式事务:处理分布式事务的问题,保证数据的一致性和可靠性。 - 数据备份和恢复:对分散存储的数据进行备份和恢复,确保数据的安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值