常用 MySQL 分库分表方案详解

1. 为什么需要分库分表

单机 MySQL 的瓶颈通常出现在三个维度:

  • 存储容量:单表数据量过亿后,索引膨胀、DDL 变慢、备份恢复时间不可控。
  • 并发连接:单实例连接数与 CPU 核数有限,QPS 达到瓶颈后响应时间急剧上升。
  • 写入吞吐:单主库的 binlog 与磁盘 IO 成为写入上限,主从延迟随之放大。

当垂直拆分(按业务拆库、按字段拆表)已经无法缓解时,就需要水平拆分,也就是常说的分库分表。

2. 先做垂直拆分,再做水平拆分

分库分表不是第一选择,合理的演进顺序是:

  1. SQL 与索引优化:慢查询治理、覆盖索引、避免大事务。
  2. 读写分离:主库写、从库读,缓解读压力。
  3. 缓存与异步化:热点数据前置到 Redis,非核心链路走消息队列。
  4. 垂直拆分:按业务域拆库(用户库、订单库、商品库),按冷热字段拆表。
  5. 水平拆分:单表数据量仍然过大时,才按分片键拆分。

3. 水平拆分的四种常见形态

形态 说明 适用场景
只分表 同一库内拆成多张物理表 单库容量够,只是单表过大
只分库 按分片键拆到多个库,表结构不变 单实例连接数或 IO 达到瓶颈
分库分表 既拆库又拆表 数据量与并发同时很高
分库分表 + 读写分离 每个分片再挂从库 读多写少的大流量场景

4. 分片键的选择

分片键决定了数据分布是否均匀,也决定了后续查询能否避免全路由。

  • 用户维度 :user_id,适合用户中心、账户、订单(按买家查询为主)。
  • 订单维度 :order_id,适合订单详情、支付流水。
  • 商户维度 :merchant_id,适合 B 端 SaaS 场景。
  • 时间维度:按月/按年分表,适合日志、账单类冷热分明的数据。

选择原则:

  • 尽量让高频查询条件与分片键一致,避免跨分片扫描。
  • 分片键一旦确定,后期变更成本极高,需要预留扩展方案。
  • 避免使用自增 ID 作为分片键,容易造成热点写入。

5. 常见分片算法

5.1 取模分片

sql 复制代码
-- 假设分 4 个库,每个库 8 张表
-- 库序号 = user_id % 4
-- 表序号 = (user_id / 4) % 8

优点是数据分布均匀,缺点是扩容时需要大量数据迁移。

5.2 范围分片

按 ID 区间或时间区间划分,例如 user_id 在 0~1000万 落在分片 0。

优点是扩容简单、范围查询友好,缺点是容易出现热点分片。

5.3 一致性哈希

将分片节点和数据都映射到哈希环上,扩容时只影响相邻节点的数据。

适合节点数量动态变化的场景,但实现复杂度较高。

5.4 基因法

把分片键的基因位嵌入到业务主键中,使得按主键查询也能直接定位分片。

例如订单号生成时,把 user_id 的低 4 位拼接到订单号末尾,这样按 order_id 查询时也能反解出分片位置。

6. 主流中间件方案对比

方案 类型 特点
ShardingSphere-JDBC 客户端 轻量、无额外部署,与应用同进程
ShardingSphere-Proxy 代理 独立部署,支持多语言客户端
MyCat 代理 老牌方案,社区活跃度下降
Vitess 代理 云原生,适合 Kubernetes 环境
业务层自研 客户端 灵活可控,但开发与运维成本高

对于大多数中小团队,ShardingSphere-JDBC 是性价比最高的起点:无需额外运维组件,接入成本低,功能覆盖分片、读写分离、数据加密等。

7. 基于 ShardingSphere-JDBC 的配置示例

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: ds0,ds1
      ds0:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_0
        username: root
        password: root
      ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/order_db_1
        username: root
        password: root
    rules:
      sharding:
        tables:
          t_order:
            actual-data-nodes: ds$->{0..1}.t_order_$->{0..7}
            database-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: db-inline
            table-strategy:
              standard:
                sharding-column: user_id
                sharding-algorithm-name: table-inline
        sharding-algorithms:
          db-inline:
            type: INLINE
            props:
              algorithm-expression: ds$->{user_id % 2}
          table-inline:
            type: INLINE
            props:
              algorithm-expression: t_order_$->{user_id % 8}

对应的建表语句需要在每个物理库中执行:

sql 复制代码
CREATE TABLE t_order_0 (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    order_no VARCHAR(64) NOT NULL,
    amount DECIMAL(12, 2) NOT NULL,
    status TINYINT NOT NULL DEFAULT 0,
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    KEY idx_user_id (user_id),
    KEY idx_order_no (order_no)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;

8. 分库分表带来的新问题

8.1 跨分片查询

不带分片键的查询会被广播到所有分片,性能急剧下降。常见应对:

  • 建立异构索引表,按另一维度冗余一份数据。
  • 引入 Elasticsearch 承担多条件检索。
  • 对低频查询走离线数仓,不占用在线库资源。

8.2 分布式事务

跨库写入无法用本地事务保证一致性,可选方案:

  • 本地消息表 + 定时补偿:实现简单,最终一致。
  • Seata AT 模式:对业务侵入小,但需要额外部署 TC。
  • TCC / Saga:适合强一致要求较高的核心链路。

8.3 全局唯一 ID

分库分表后自增主键不再全局唯一,常用方案:

  • 雪花算法:时间戳 + 机器 ID + 序列号,趋势递增。
  • 号段模式:数据库批量分配 ID 段,减少数据库压力。
  • Redis INCR:实现简单,但依赖 Redis 可用性。

8.4 分页与排序

LIMIT offset, size 在分片场景下需要各分片取 offset + size 条再归并,深分页性能很差。建议改为游标分页 (基于 id > last_id)。

8.5 扩容与数据迁移

取模分片扩容时,通常采用成倍扩容(2 库扩到 4 库),配合双写 + 数据校验 + 灰度切流,降低迁移风险。

9. 方案选型建议

  • 数据量在千万级、单库仍可承载:优先优化索引与读写分离,暂不分片。
  • 单表过亿、查询基本带分片键:ShardingSphere-JDBC + 取模分片,成本最低。
  • 多语言客户端、需要统一入口:选择 ShardingSphere-Proxy。
  • 云原生、Kubernetes 环境:可评估 Vitess。
  • 业务复杂、分片规则频繁变化:考虑业务层自研路由,但要评估长期维护成本。

10. 总结

分库分表是解决单机 MySQL 容量与并发瓶颈的有效手段,但它把简单问题变成了分布式问题。落地前应确认垂直拆分、读写分离、缓存等手段已经用尽,落地后要同步规划好跨分片查询、分布式事务、全局 ID、数据迁移等配套能力。选型上,ShardingSphere 生态是目前国内最主流、文档最完善的选择,适合作为大多数团队的起点。

相关推荐
kingx32 小时前
Windows Server 2008 应急加固实战:7 项入侵痕迹排查与修复
数据库·windows·提权·应急响应·加固
xiaogai_gai2 小时前
采购业务退款单同步实战:从钉钉审批流到金蝶云星空付款单
java·数据库·钉钉
AlickLbc3 小时前
DM-V8数据库证书过期处理过程记录
数据库
l1t3 小时前
DeepSeek总结的五分钟测试:Ted Dunning 谈 DuckDB 为何让分析更轻松
数据库·sql·duckdb
超级新零售4 小时前
零售 SaaS 与本地部署,技术责任边界究竟怎么分?
数据库·零售
考虑考虑4 小时前
瀚高数据库
数据库·后端·postgresql
dishugj4 小时前
oracle日志切换三条命令区别
数据库
Patrick在香港5 小时前
一个斜杠,把毫秒变成十几秒
android·数据库·python·性能优化·正则表达式·回溯·redos
悟道书生5 小时前
(一)Navicate为什么转存结构和数据只转存表而不包含建库?
数据库·oracle