欢迎光临
我们一直在努力

MYSQL-分库分表的大致实施流程

1.评估可行性

首先想一想为什么选择进行分库分表?是因为觉得现在数据量这么大,SQL查询太慢了,得优化一下,而且慢SQL,索引优化,表结构优化,数据库参数调优,缓存扛热点读和削峰填谷这些都已经使用过了。

在开始分库分表之前一定要提前分析好!分库分表应该是“最后的大招”,因为带来的复杂度是永久性的。

看几个点:

  • 看单表数据量有没有超过2000万行
  • 看单库QPS有没有超过5000
  • 看数据增长趋势,算一下半年会不会撑不住
  • 如果当前没有瓶颈、未来也看不到瓶颈,就不要分库分表了,优先上面的尝试别的操作。

    • 分表解决的是单表数据量过大导致的查询慢的问题
    • 分库解决的是单机资源瓶颈,把压力分摊到多台服务器

    2.设计方案

    ①定拆分方向

    • 垂直分表:按列拆分,宽表变窄,解决是“表太宽”。把一张宽表的列拆开,常用字段放一张表,不常用的大字段单独拎出去。比如用户表拆成user_base存用户名、手机号,user_detail存个人简介、头像这些占空间的字段
    • 垂直分库:按照业务边界进行拆分,把一整个库拆分成订单库order_db、用户库user_db、商品库goods_db,通常跟着微服务走,一个服务模块对应一个库。
    • 水平分表:同一张表的数据按行拆分到一个库内,解决“单表行数太多”,比如按用户ID取模,user_0、user_1、user_2这样分。每张表结构一模一样,只是数据不同。
    • 水平分库:按行拆到多个数据库实例,解决“单库写入/连接数等单机资源瓶颈”。表结构复制到多个数据库实例,数据按规则分散存储。比如电商订单按用户ID哈希到order_db_0、order_db_1、order_db_2三个库

    垂直,是从业务的视角来分析的,解决的是“边界混乱”,跟领域划分、微服务拆分绑定。

    水平,是从数据视角来分析的,解决的是“量太大”,跟QPS、行数、容量绑定。

    实际演进顺序通常是:

    垂直分库(随微服务按照业务分库)->垂直分表(优化表结构,把不常用的大字段单拎出去)->水平分表(表太大)->水平分库分表(单库也扛不住)。

    ②定分片键与分片策略和数量

    这个阶段要确定分片键、分片策略和分片数量。

    分片键,给每条数据一个“门牌规则”,决定“用哪个字段做路由依据”。这是因为分库分表之后,写入和查询都要先回答“这条数据在哪个库哪张表”。

    每张被水平拆分的逻辑表,只能有一条路由规则(通常落在分片键字段上),因为每行是数据的物理住址必须唯一。

    通常分片键要选择查询频率高、数据分布均匀的字段,比如电商系统就选user_id/order_id。

    分片策略,门牌号的换算公式,决定“怎么把键值换算成表编号”。

    主要有以下策略:

    • 哈希取模,hash(user_id) % 64->分布最均匀。代价是扩容的时候需要搬数据
    • 范围分片,按照时间(比如2026年的订单放这一批表里面),最为方便,好归档,好扩容。代价是热点集中(最新月份的表最忙,老表闲着)
    • 查表法,维护一张“哪个值在哪”的映射表,灵活。但是每次路由要多一次查询。

    分片数量,是给未来留的位置,建议一步到位。

    最好是根据未来的3~5年的预估数据量进行反推。

    ③定全局唯一ID

    单表的时候,主键主要是靠数据库自增。

    那么分库分表之后,如果每个库都从1开始自增,出现主键撞车的情况,解决起来比较麻烦,所以需要一个独立于数据库的、全局统一的发号器:

    • UUID,应用自己随机生成一个36位字符串。
      • 优点:简单,一行代码搞定
      • 致命伤:无序。新插入的主键随机落点,导致B+树频繁分裂页(写性能差);36字符太长,做索引又大又慢
      • 所以它可以用做唯一标识,但不适合做MySQL主键
    • 雪花算法,实际最主流。生成的是一个64位long
      [1位符号 | 41位时间戳 | 10位机器ID | 12位序列号]

      • 同一毫秒、同一机器能发4096个号,理论峰值400万+/秒
      • 优点:不依赖数据库,本地内存生成,性能极高。趋势递增,对B+树友好
      • 最大的坑:时钟回拨,机器NTP校时回退后可能发出重复号。需要有回拨检测
      • 10位机器ID意味着最多1024个节点,需要一套分配机制
    • 号段模式(美团Leaf-segment),数据库里面放一张发号表,每次从库里面批量取一段(比如一次领1000个号),应用在内存里面慢慢发,发完了再领下一段。
      • 优点:递增、简单可靠、不依赖ZK
      • 代价:每次领号有一次DB访问(但批量1000,摊薄后可忽略);双buffer预加载可消除毛刺
      • 缺点:严格连续,重启会浪费一段号,这个无所谓,号段会暴露业务量。
    • Redis自增,直接INCR一把梭
      • 优点:实现最快
      • 缺点:持久化策略尴尬,Redis挂了发号就停
      • 一般只用于并发不高的小场景

    ④中间件选型

    我们写的SQL不改变,还是 INSERT INTO order … 这种样子,是中间件帮我们做了一些事情。

    中间件主要是负责:

  • 解析:读懂SQL,并发现里面的分片键 user_id = 9527
  • 路由:hash(9527) % 512 = 207,算出目标库
  • 改写:把SQL改成 INSERT INTO db_x.order_207 …
  • 归并:如果是跨表查询,把64张表的结果拿回来合成一份按时序返回
  • 主要有以下形态:

    • ShardingSphere-JDBC(客户端模式),性能最好但入侵代码,适合Java应用,追求性能。
      • 缺点:只支持java,每个微服务都要引入配置,连接数会放大
    • ShardingSphere-Proxy(代理模式)。独立部署一个“假MySQL”,应用拿它当普通数据库连,它背后再连真实的16个库。适合多语言,集中管控。
      • 语言无关,对应用完全透明
      • 改分片规则只改代理的配置,应用零感知
      • 代价:每次查询多跳一次网络
    • MyCat。独立部署,适用于存量系统,现在社区基本停更,新项目不建议选

    还有其他的一些方案【Vitess,自研等】,这里不再展开。

    ⑤非分片键查询方案

     分片键的设计是为了让最频繁的操作都走高速,日常操作尽量都走分片键,但是总会有一些低频的查询支线。

    • 基因法
      • 解决:按照订单号、支付单号这类“自己生成的单号”查单
      • 原理:把路由信息(user_id的基因)提前埋进单号里面,让单号自己“报路”
    •  两步走,用映射换算
      • 解决:查询条件和user_id一对一的场景(手机号、身份证、邮箱)
      • 也就是说先换算成分片键,再拿着分片键进行查询。
      • 注意:映射本身要在下单/注册时同步维护。如果映射表没有命中,回源到用户表查一次再回填。
    • 异构索引表
      • 解决一个新维度对应一堆订单的高频查询(商家查“我的订单”、骑手查“我送的单”)
      • 用一个窄表索引型,只存储 [merchant_id | order_id | user_id] 。这样的话查找详细的再拿 user_id 会主表取完整详情就可以。
      • 怎么写进去呢?
        • 像这种双写主流的做法是异步:

          下单 -> 写主表(user_id 分片)->发一条MQ消息->消费者写异构表

          或者使用Canal订阅主库binlog,监听到订单插入,自动搬运到异构表

    • ES异构
      • 解决:多维组合查询(运营后台:时间 × 省份 × 类目等等)

        主表 ->binlog -> Cancal/Kafka ->ES

    • 广播兜底查询:针对日常几乎不会查询的就可以搬出来广播了,直接全表扫描

    3.数据库迁移

    这是最容易出事的环节。用Canal监听老库binlog做增量同步,用存量脚本只做存量迁移。

    迁移完必须做数据校验,行数对不对、关键字段值对不对、业务逻辑跑一遍结果对不对。

    4.灰度切换

    先切读流量到新库,观察一周没问题再切写流量。

    切换期间保持双写,万一新库有问题能快速回滚到老库。

    5.一些问题

    ①迁移过程中怎么保证数据一致性?

    关键是增量同步和数据校验。增量用Canal监听binlog,保证老库的每一条变更都能同步到新库。

    存量迁移期间可能会有数据被改,所以迁移完要跑一遍全量校验,比对行数、关键字段的MD5、抽样查询业务数据。

    校验发现不一致的记录打标记,单独补偿修复。

    切流量之前再跑一遍校验,确认没有问题才敢切。

    ②索引和分片键有什么区别?

    索引  分片键
    回答的问题 这行数据在表内的哪个位置 这行数据在哪张表(哪个库)
    本质 有指路的作用,不移动数据 直接决定了数据的物理位置
    没命中的后果 全表扫描 需要采取一些措施,最差的结果广播所有表

    ③灰度切换期间老库和新库同时写,怎么处理主键冲突?

    双写期间主键生成必须用分布式ID,不能用数据库自增。

    Snowflake或者Leaf这类方案都行,保证全局唯一。

    如果老系统用的是自增主键,迁移前得先把主键生成逻辑改掉,跑一段时间确认没问题再开始迁移。这个改造最好提前几周做,别跟迁移混在一起,出了问题不好定位。

    赞(0)
    未经允许不得转载:171主机测评 » MYSQL-分库分表的大致实施流程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址