分库分表后如何进行分页查询?

典型回答 在我们做了分库分表之后,数据会散落在不同的数据库中,这时候跨多个库的分页查询、以及排序等都非常的麻烦。 如果分的库不多,那么我们还可以通过扫表的方式把多个库中的数据都取出来,然后在内存中进行分页和排序。 比如我要查询limit 100,100 的话,有三个库,那我就分别到这三个库中把0 - 200之间的数据都取回来,然后再在内存中给他们排序,之后,再取出第100-200之间的数据。 这种做法非常的麻烦,而且随着偏移量越大,当要分的页很多的时候,可想而知这种方法根本就不靠谱。 网上有很多文章写了几种做法,还起了几个名字,比如全局视野法、二次查询法、业务折衷法的,在我看来,这几种做法根本就都不靠谱,而且只会带来复杂的理解和维护成本,而且没有办法都一定的前提条件限制。 一般来说,在企业中是怎么做的呢?我们还是拿订单的分库分表举例,当我们用买家ID分表之后: shardingkey查询 一般来说,买家的订单查询是最高频的,而对于买家来说,查询的时候天然就是可以带买家ID的,所以就可以路由到单个库中进行分页以及排序了。 非shardingkey的关键查询 那么,电商网站上不仅有买家,还有卖家,他们的查询也很高频,该怎么做呢? 一般来说,业务上都会采用空间换时间的方案,同步出一张按照卖家维度做分表的表来,同步的过程中一般是使用canal基于bin log 做自动同步。虽然这种情况下可能存在秒级的延迟,但是一般业务上来说都是可以接受的。 也就是说,当一条订单创建出来之后,会在买家表创建一条记录,以买家ID作为分表字段,同时,也会在卖家表创建一条记录出来,用卖家ID进行分表。 并且这张卖家表不会做任何写操作,只提供查询服务,完全可以用一些机器配置没有那么高的数据库实例。 这样,卖家的分页等查询就可以直连卖家表做查询了。 非shardingkey的复杂查询 那除了买家、卖家以外,其他的查询怎么办呢? ...

March 22, 2026 · 1 min · santu

分库分表之后的怎么进行join操作

典型回答 在我们做了分库分表之后,数据会散落在不同的数据库中,这时候在需要进行跨库或跨表的 JOIN 操作时,就会比较麻烦。 如果数据被分表后,分散在不同的数据库上面,那么标准的join是要在单库内执行的,所以这就会带来复杂性。还有就是不同的库可能在不同的服务器上面,那么一次join就会需要和多个数据库交互,那么就会有更多的网络延迟,带来性能问题。 而且,有的时候一次join可能并不是2个库,而可能是多个库,比如订单和用户join,那么我们要查的用户可能分散在很多个库中,那么一次join就会横跨很多库。 那么怎么解决呢?有以下几个办法。其实这个问题,和我们另外一篇有点像,那就是: ✅如果需要跨库join,该如何实现? 应用层 JOIN 在应用代码中单独查询各个表,然后在应用层将结果合并。这意味着所有必要的数据被加载到应用服务器的内存中,然后执行类似于 JOIN 的操作。如: 1 2 3 4 5 6 7 8 9 //先从数据库中查询出要查询的订单列表 List<OrderDO> orders = getOrders(); for(OrderDO orderDO : orders){ OrderDTO orderDTO= new OrderDTO(orderDO); //根据用户ID去users表查询用户名 String userName = getUserNameByUserId(orderDO.getuserId); orderDTO.setUserName(userName); } 这么做的优点是,灵活,可以跨不同的数据库和表实现。不依赖数据库特性,适用于任何数据库系统。 但是他的缺点也很明显,那就是对应用服务器的内存和处理能力要求较高,尤其是数据量大时。而且网络开销可能增加,性能可能受到影响。 使用数据库中间件 在分库分表后,我们也可以使用如MyCat、Shardingsphere等数据库中间件来支持分库分表环境下的 JOIN 操作,比如使用shardingsphere的联邦查询功能(这个功能还在完善中,并不是特别建议在生产环境中用)。这些中间件可以理解为一个数据库代理,对应用透明地处理数据分片和查询路由。 这个方案的优点是对应用透明,应用不需要关心数据如何分片。可以较为高效地执行查询优化和数据汇总。缺点就是引入额外的系统复杂性和维护成本。性能和支持的SQL特性可能受限于中间件的能力。 数据冗余 还有一种方案,那就是调整分库分表策略,尽量减少需要执行 JOIN 操作的场景,比如通过合理的数据冗余和预聚合来避免跨库查询。 这个方案可以显著减少复杂查询,提升系统性能。减少了跨网络的数据传输。缺点是会存在一致性问题,也会增加存储空间。 但是,这个方案确实是公司里面用的比较多的。很多时候对于一些不常修改的字段,做一些数据冗余是非常方便的,比如用户的真实姓名。 搜索引擎 使用Elasticsearch等搜索引擎,也是可以解决跨库JOIN的问题的,尤其是在处理大数据和复杂搜索场景时。 我们可以基于前面的宽表的思想,把orders表和users中我们关心的所有字段做成一个文档,如类似以下形式: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { "userId": "123", "userName": "Hollis", "orders": [ { "orderId": "a1", "orderDate": "2021-01-01", "amount": 100 }, { "orderId": "b2", "orderDate": "2021-02-01", "amount": 150 } ] } 然后再基于canal等工具,把orders表及users表的变更同步到ES中,这样我们就可以基于ES直接做查询了。 ...

March 22, 2026 · 1 min · santu

分库分表的数量为什么一般选择2的幂?

典型回答 一般来说,在很多大厂中,需要做分表的时候,分表的数量都会选择2的幂,比如16、64、128、512、1024等,这么做有什么好处呢? 首先,如果你看过 HashMap 的 hash 算法的话,应该知道,HashMap 的容量其实也是2的幂,这么做的好处在下面的文章中讲过,那就是可以将取模操作改为更加高效的位运算。 ✅HashMap的hash方法是如何实现的? 因为,hash % 8 相当于 hash & (8-1),所以对于分8个表,可以使用哈希值与7进行按位与操作,这不仅简化了计算,还提升了性能。 所以,使用2的幂作为分表数量的第一个好处,就是可以将取模算法优化成位运算算法。 除了这个好处以外,我们通常分表都是伴随着分库的,比如我们分16个库128张表, 这样如果我们的分表数量和分库数量都是2的幂,那么就可以实现均匀分布。128张表就可以均匀的分到16个库中,每个库中有8张表。 所以,使用2的幂作为分表数量的第二个好处,就是可以使得多张分表在多个库中均匀分配。 当我们在做分库分表的时候,必然要考虑的一个问题那就是二次分表的问题,比如我们根据当前的业务发展,计算出可能需要分4张表就够了,但是随着业务增长,可能认为需要更多表才行,这时候如果我们把新的分表数量定位8张表,那么在做数据迁移的时候,就可以只迁移部分数据。 假设我们最开始将订单表分成了4张表,分别是 order_00、order_01、order_02、order_03,这时候假设我们的分表算法是根据 userId取模。即 userId % 4 order_00:userId % 4 == 0 order_01:userId % 4 == 1 order_02:userId % 4 == 2 order_03:userId % 4 == 3 当数据量增长,需要将表扩展到8个时,需要将算法改为userId % 8 : 新的哈希值计算: order_00:userId % 8 == 0 order_01:userId % 8 == 1 order_02:userId % 8 == 2 order_03:userId % 8 == 3 order_04:userId % 8 == 4 order_05:userId % 8 == 5 order_06:userId % 8 == 6 order_07:userId % 8 == 7 ...

March 22, 2026 · 1 min · santu

ShardingJDBC有哪些分片策略,你用的哪个?

典型回答 所谓分片策略,其实就是我们提到的分表算法,就是说一个分表字段之后,基于什么样的策略或者算法来决策出数据应该去哪个库/表中进行读取或者写入。 ✅分表算法都有哪些? 已经有文章单独写过算法了,但是为啥要单独介绍一下所谓的 SharedingJDBC分片策略呢? 算法 、策略好像是一回事儿,其实真要较真的话,可以认为分片策略 = 分片算法+分片键 ShardingJDBC目前提供4种分片算法。包括了: 精确分片算法 对应PreciseShardingAlgorithm,用于处理使用单一键作为分片键的=与IN进行分片的场景。需要配合StandardShardingStrategy使用。 范围分片算法 对应RangeShardingAlgorithm,用于处理使用单一键作为分片键的BETWEEN AND进行分片的场景。需要配合StandardShardingStrategy使用。 复合分片算法 对应ComplexKeysShardingAlgorithm,用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。需要配合ComplexShardingStrategy使用。 Hint分片算法 对应HintShardingAlgorithm,用于处理使用Hint行分片的场景。需要配合HintShardingStrategy使用。 ShardingJDBC目前提供5种分片策略。包括了: 标准分片策略 对应StandardShardingStrategy。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。StandardShardingStrategy只支持单分片键,提供PreciseShardingAlgorithm和RangeShardingAlgorithm两个分片算法。PreciseShardingAlgorithm是必选的,用于处理=和IN的分片。RangeShardingAlgorithm是可选的,用于处理BETWEEN AND分片,如果不配置RangeShardingAlgorithm,SQL中的BETWEEN AND将按照全库路由处理。 复合分片策略 对应ComplexShardingStrategy。复合分片策略。提供对SQL语句中的=, IN和BETWEEN AND的分片操作支持。ComplexShardingStrategy支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。 行表达式分片策略 对应InlineShardingStrategy。使用Groovy的表达式,提供对SQL语句中的=和IN的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的Java代码开发, Hint分片策略 对应HintShardingStrategy。通过Hint而非SQL解析的方式分片的策略。 不分片策略 对应NoneShardingStrategy。不分片的策略。 一般来说,在工作中,行表达式分片策略、复合分片策略用的比较多,还有就是一些特殊情况下 Hint 分片策略的也会用。 行表达式分片策略用的多的原因是因为他比较简单,不需要单独做算法的实现,只需要写 groovy 表达式进行分片即可,如: t_user_$->{u_id % 8} 表示t_user表根据u_id模8,而分成8张表,表名称为t_user_0到t_user_7。 复合分片策略用的也很多,主要是因为我们在项目中同时存在多个分表键的情况比较多的,比如我们介绍过的基因法,就是订单号和用户 ID同时都作为分片建的情况。 ✅分表字段如何选择? Hint 这种一般是在需要指定具体那张表做查询的时候,会通过这种’提示’的方式明确的告知 ShardingJDBC 你要用哪张物理表!

March 22, 2026 · 1 min · santu

分库分表中,如何预估需要分多少个库?多少张表?

典型回答 分成多少,一般是一般是结合订单存量数据,以及增量数据的情况来看要分多少张表。你要知道你存量已经有多少数据量了,以及一年大概增长多少,还要知道业务上希望你的数据保留多久。 我给一个公式(如有雷同,纯属抄袭): <font style="background-color:#FBDE28;">分表数量= (订单存量总数 + 预计年增长量 * 保留年限)/2000万 => 向上取最接近的2的幂</font> 以上公式中的2000万,是Innodb 理论上的单表极限(这里的极限指的是性能不变差的极限)。 ✅从B+树的角度分析为什么单表2000万要考虑分表?? 假设存量数据已经有2000万了,预计每年增长500万,我们需要保留10年,那么就得出: (2000 + 500 * 10) / 2000 = 3.5 => 4 这里为什么计算出的结果要向上取最接近2的幂呢? 因为这样可以更容易基于分表数量计算分库的数量,以及后期数据迁移的时候更加方便。 ✅分库分表的数量为什么一般选择2的幂? 所以,在做分库分表的时候,可以根据这个公式,大致计算一下需要分多少张表,然后再根据并发的情况,大致算一下需要分多少个库。 库的数量和表的数量之间没有必然联系,但是需要有倍数关系,如果你实在是不太好根据并发去预估库的数量,那么我给你个经验值的公式(如有雷同,纯属抄袭): <font style="background-color:#FBDE28;">分库数量 = 分表数量 / 8</font> 比如我们常用的分库分表数量: 128库,1024张表 64库,512张表 16库,128张表 8库,64张表 当然,如果你分表数量本身是小于8的,那要么是2,要么是4,那么我建议你就直接分库数量=分表数量即可。

March 22, 2026 · 1 min · santu

分库分表后怎么设计可以降低数据迁移的难度?

典型回答 分库分表之后,因为最初的预估数据不够准确,导致后续数据增长很快,表不够了,就可能要重新分表。重新分表就需要涉及到数据迁移的,需要对老表的数据迁移到新表中。 关于数据迁移可以看: ✅如何做平滑的数据迁移? 那么,如果未来一定要迁移,有没有办法在最初的时候就想办法降低一下迁移的成本呢? 分库分表的数量选择2的幂? ✅分库分表的数量为什么一般选择2的幂? 当我们在做分库分表的时候,必然要考虑的一个问题那就是二次分表的问题,比如我们根据当前的业务发展,计算出可能需要分4张表就够了,但是随着业务增长,可能认为需要更多表才行,这时候如果我们把新的分表数量定位8张表,那么在做数据迁移的时候,就可以只迁移部分数据。 假设我们最开始将订单表分成了4张表,分别是 order_00、order_01、order_02、order_03,这时候假设我们的分表算法是根据 userId取模。即 userId % 4 order_00:userId % 4 == 0 order_01:userId % 4 == 1 order_02:userId % 4 == 2 order_03:userId % 4 == 3 当数据量增长,需要将表扩展到8个时,需要将算法改为userId % 8 : 新的哈希值计算: order_00:userId % 8 == 0 order_01:userId % 8 == 1 order_02:userId % 8 == 2 order_03:userId % 8 == 3 order_04:userId % 8 == 4 order_05:userId % 8 == 5 order_06:userId % 8 == 6 order_07:userId % 8 == 7 ...

March 22, 2026 · 1 min · santu

在分库分表时,如果遇到了对商品名称的模糊查询,要怎么处理?

典型回答 在分库分表中,尤其是针对海量商品做了水平分表后,商品的数据会分散到很多张不同的分表中,那么,如果前端用户想要查询某个商品怎么办呢? 我看网上有很多各种所谓的奇技淫巧,其实,真正可行的办法就一个,那就是上搜索引擎的, 比如Elasticsearch。 且不说分库分表,就是数据量超过几千万的这种模糊查询的话,数据库你用 like 去查询也都扛不住,因为%hollis%没办法走索引,性能也很差。 ✅什么是最左前缀匹配?为什么要遵守? 所以,这个地方,该上搜索引擎还是要上的; 方案就是把数据同步到 ES 中一份,然后基于 ES 针对商品名称构建倒排索引,这样在分词后就可以做全文索引提升查询效率了。 ✅倒排索引是什么?

March 22, 2026 · 1 min · santu

为啥要全局分布式ID,每张表自增不行吗?

典型回答 其实没啥不行的,只要你能接受以下几个问题就行。(狗头) 1、id会重复,每张表都用自己的自增id,不用全局分布式id,就会导致id的重复,如果id重复了,那么全局查询的实际,就没办用id唯一定位一个数据。尤其是很多需要做数据汇总、统计的场景,会把数据同步到离线表,那么就会导致数据主键冲突,无法同步。 2、无法支持二次分表,每张表自己做自增,在单表中是唯一了,不冲突了,但是如果后面如果要重新分表,那么就会导致表中出现重复id的情况,这是一定有问题的。 所以,一般做过了分库分表之后,都会用一个分布式ID来做主键id、或者业务唯一号,要不然就会出现以上两个问题,而且这两个问题基本上可以认为是无法接受的。 ✅分表后全局ID如何生成?

March 22, 2026 · 1 min · santu

订单号用了基因法之后,二次分表怎么办?

典型回答 ✅分表字段如何选择? 上面的文档中我们介绍过分库分表中,为了让订单号也可以实现快速查询,我们采用基因法来编码订单号,也就是把分表结果放到订单号上面去,这样基于订单号就能直接精准找到具体的单表做查询了。 那么,有个关键问题,那就是原来的表如果是分成了128张,但是后面不够了,要重新分表成256,怎么办?老的订单没办法迁移了,因为订单号肯定是不能变的,而一旦数据迁移,就没办法查询到之前的订单了。那么可行的方案有哪些呢? 网上有很多乱七八糟的方案,我认为可行的就以下两种。 1、把分表字段编码到订单号上 所谓基因法,其实只要订单上有分表相关的基因就行了,但是不要求说一定要是基于分表结果做基因。也可以直接用分表字段做基因。 比如,原来我们是这样的,分成128张表,用buyer_id做分表字段,取模作为分表算法。 数据插入的时候 1、分表结果 = buyer_id % 128 ,如 23 2、分表结果编码到订单号中,如xxxx0023 那么在查询的时候: 1、基于订单号xxxx0023,计算出他的物理表是0023这张表,那么就直接去这张表查询即可。 但是,如果我们换一个方案,数据插入的时候是这样的: 1、buyer_id编码到订单号中,如xxxx32132145 查询的时候: 1、从订单号xxxx32132145中解析出用户id:32132145 2、基于32132145用户id,计算出具体的物理表 3、直接去具体的物理表做查询 如果是这样的话,你想想,我们是不是怎么扩容都可以。只要在扩容后做数据迁移就行了,因为我并没有在订单号上记录具体的分表信息,而是记录的用户id的信息,只要扩容之后,我把分表算法同步改了就行,即之前是对128取模,现在是对256取模了。 优点: 1、完全向下兼容 缺点: 1、用户id可能过长 这个也可以通过固定的保留后几位实现,比如一般就保留后四位就够了 2、需要做数据迁移 2、新老共存 这个方案很简答,其实就是原来的订单按照旧基因法分到老的 128 张表中。扩容后,新订单写入新的256分表中。这样在查询的时候在查询时,根据订单ID判断是老订单还是新订单,然后走不同的表。 比如老订单是20位,新订单则设置个21位。然后通过订单号的长度来判断,如果是20位的,则通过老的算法去路由,如果是21位的,则通过新的算法去路由。 随着时间慢慢推移,老订单就可以统一迁移到历史表里面去了,那么老的128张表就可以慢慢废弃掉了。 优点: 不需要迁移旧数据。 风险较小 缺点: 路由逻辑会更加复杂。 跨分表聚合查询时需要查多个分区。 3、其他方案 我之前看过,网上还给出一些其他的方案,比如一致性hash,中间表等。 都是扯淡的。一致性hash根本不能解决问题,还是那个原因,订单号是不能变的。虽然一致性hash可以让迁移量更小,但是还是要迁移的。 ✅什么是一致性哈希? 另外,有人大聪明说我建一个中间路由表,记录下每个订单再哪张表、、、、想想都好笑。 另外,有人说我搞一个中间层服务,然后让他去做统一处理,首先行肯定是行,但是这个方案太泛了,面试的时候提只会被喷。因为面试官肯定还会问你,这个中间路由层他又是怎么做的呢?你还要回到具体的可落地方案上。 总结 如果你在最开始就考虑可能要二次分表,那么就用第一个方案。 如果最开始没想好,后面才要库容的,那就用第二个方案。

March 22, 2026 · 1 min · santu

留言给博主