分区表为什么不够了?

  • 仍在单机:所有分区仍然在同一个MySQL实例、同一块磁盘/磁盘阵列上。这意味着:
    • IO瓶颈:所有分区的数据读写最终会争用同一套物理硬件(CPU、内存、磁盘IO、网络带宽)。当数据量和并发达到单机上限时,性能会达到天花板。
    • 连接数瓶颈:所有连接都连接到同一个MySQL实例,受max_connections限制。
    • 可用性风险:单点故障。该实例宕机,整个表(所有分区)都不可用。

结论:分区表是单机数据库性能优化的高级手段,但无法突破单机硬件资源的根本限制。

垂直分库分表

垂直分库
例如微服务按业务功能将表拆分到不同数据库,例如:

  • 订单库order, order_item
  • 用户库user, user_profile
  • 商品库product, category

垂直分表

将一张宽表按列拆分为多张表:

  • 主表:存放频繁访问的字段(user_id, nickname, age
  • 扩展表:存放冷门或大字段(bio, avatar, settings

优点:减少 IO 阻塞,提高缓存命中率。

水平分库分表

==这是最常用的分库分表手段==

常见分片算法

image-20260523233634329

一致性哈希

就像一个有刻度的圆盘,数据和服务器都放在上面。数据顺时针找最近的服务器。增加或减少服务器时,只影响相邻一小段的数据,而不是全部重来。** 它是分布式系统(如缓存、数据库分片)==平滑迁移扩容==的基石

路由

(以ShardingSphere-JDBC为例)

假设我们配置了:

  • 分库键user_id,分库算法:user_id % 4 → 4个库(ds0~`ds3`)
  • 分表键user_id,分表算法:user_id % 2 → 每库2张表(t_user_0t_user_1
  • 实际物理表:ds0.t_user_0ds0.t_user_1ds1.t_user_0ds1.t_user_1 ……

等值路由:

1
SELECT * FROM t_user WHERE user_id = 123;

路由步骤

  1. 解析SQL,提取 user_id = 123
  2. 分库计算:123 % 4 = 3 → 目标库 ds3
  3. 分表计算:123 % 2 = 1 → 目标表 t_user_1
  4. 最终路由到:ds3.t_user_1

范围路由:

1
SELECT * FROM t_user WHERE user_id BETWEEN 100 AND 200;
  • 分片算法无法精确计算到单一值,中间件会计算所有可能的分片
    • 对于 user_id 在 100~200 范围内,%4 的结果可能是 0,1,2,3 全有。
    • 因此路由到所有库的所有表(广播),然后在应用层合并结果。

不带分片键:

1
SELECT * FROM t_user WHERE name = '张三';
  • 没有 user_id,无法确定分片。
  • 默认行为:广播到所有分片ds0.t_user_0~`ds3.t_user_1`),然后合并结果。
  • 必须避免广播,可以建==索引表==(name → `user_id),或者基因法具体怎么实现:
    • 核心思想:将分片键信息“编码”进业务主键。
      • 定长:假设分16张表,取 buyer_id 后4位bit 作为“基因”。
      • 移位:将“基因”嵌入 order_idorder_id << 4 + 基因)。
      • 解析:查询 order_id 时,解析出ID末尾的“基因”,直接路由到目标分片。

问题

🤔 “非分片键如何查询?比如按买家ID分片,卖家怎么查?”

  • 基因法(最优):将 buyer_id 的基因(如后几位)融入 order_id,解析ID即可定位。
  • 索引表:建立映射关系表,二次查询后获取数据。

🤔 “跨节点JOIN查询怎么解决?”

  • 字段冗余:在表中直接存储常关联的字段,避免查询关联表。
  • 全局表:配置表等小表在每个库都存一份,实现本地JOIN。

🤔 “分库分表后,如何进行分页查询(尤其是深度分页)?”

  • 不允许跨页跳转:前端每次传入上一页末尾游标,规避 OFFSET

🤔 “分库分表后,如何保证全局主键ID的唯一性?”

  • 数据库分段号:批量获取ID段,性能好。
  • 雪花算法(Snowflake):64位长整型,趋势递增、不依赖数据库,是业界最主流方案

🤔 “分库后,怎么保证数据一致性?”

  • 最佳实践:规避最佳实践:规避最佳实践:规避。从设计上避免跨分片事务(如用 order_id 同时分库分表)。
  • 柔性事务(TCC/Saga):高性能场景下的最终一致性方案。

分布式ID

1. UUID (Universally Unique Identifier)

  • 实现方式:JDK 内置 UUID.randomUUID().toString() 生成类似 550e8400-e29b-41d4-a716-446655440000 的36位字符串。
  • 作为数据库主键的致命缺陷:在 MySQL 中,其无序性会导致 B+ 树索引频繁的页分裂(Page Split),严重影响写入和查询性能。

2. 数据库自增

  • 多实例部署:通过设置 auto_increment_increment(步长)和 auto_increment_offset(起始值)规避冲突。例如两台机器,步长设为 2,一台从 1 开始,另一台从 2 开始。
  • 致命缺陷:扩展性极差!假设要从2台扩容到3台,必须重新规划所有实例的步长和偏移量,甚至需要数据迁移,操作复杂且风险高。

3. 雪花算法

  • ID结构:64位整数 [1位符号位 | 41位时间戳(毫秒级,可用69年)| 10位机器ID | 12位序列号(单节点每毫秒最多4096个ID)]。

  • 时钟回拨问题及解决方案:雪花算法强依赖系统时间,一旦发生时钟回拨(如NTP同步),可能生成重复ID。

    NTP(网络时间协议)同步:为了保持服务器时间准确,系统会定期与NTP服务器同步。如果本地时间过快,NTP服务会将时间小幅度回调(通常是毫秒或微秒级)进行校准。

    应对方案如下:

    image-20260524130453219