news 2026/9/17 11:04:15

MySQL分库分表实战:分片方案、中间件选型与数据迁移全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL分库分表实战:分片方案、中间件选型与数据迁移全记录

上个月的一天下午,我正开着会,运维群里突然炸了锅——订单库的CPU使用率直接冲到98%,慢查询日志每分钟刷出几十条,连接数眼看就要被打满。第一反应是又有人写了烂SQL,可查了一圈发现,一堆走了索引的查询也慢得离谱。那一刻我心里清楚:单表数据量已经涨到1.5亿行,MySQL有点扛不住了。数据库分片(Sharding)这件事,终于绕不过去了。

这篇文章想把分表、分库、分片、分区这几个概念一次讲透,同时把我自己做分片时的选型思路、踩坑记录和迁移过程完整分享出来。不搞教科书式的那套,全是实际干过之后的心得。不管你是正在评估要不要分片的架构师,还是已经被老板逼着"赶紧把库拆了"的后端开发,这篇文章应该对你有用。

1. 先说清楚:分表、分库、分片、分区到底是不是一回事

很多人一上来就分不清这四个词。面试的时候我也经常问候选人,结果十有八九是混着说的。这里先花点篇幅把事情说透,因为概念不清,后面的方案一定是一团浆糊。

1.1 分表是"单库内部"的拆分

分表,指的是在同一个数据库实例里,把一张结构相同的大表拆成多张小表。比如订单表 orders 拆成 orders_0、orders_1、orders_2……一直到 orders_15,每张表的结构完全一样,只是数据范围不同。

这样做解决的核心问题是:单表数据量过大导致索引体积膨胀、B+树层级变深、查询效率下降。拆完之后,每张表的数据量只有原来的十六分之一,索引体积小了,同样一条SQL的扫描范围小了一个数量级,性能自然就回来了。

但要注意,分表没有解决数据库实例本身的IO压力。所有表还在同一个实例上,磁盘、内存、CPU是共享的。如果瓶颈在资源层面,只分表是不够的。

1.2 分库是"实例层面"的拆分

分库,是把数据拆分到多个独立的数据库实例上。可以是一台机器上多个实例,也可以是分布在多台机器上。比如把用户库、订单库、商品库拆开部署;也可以把订单数据按规则分到订单库1、订单库2、订单库3。

分库解决的核心问题,是分散单实例的资源瓶颈。读写并发高的时候,一个实例的连接数、IO能力、内存都有限,拆到多个实例后,每个实例的压力就降下来了。这也是为什么很多互联网公司的核心库,都是"分库 + 分表"一起做,因为单表数据量和实例并发压力往往是同时爆发的。

1.3 分片是"全局视角"的统称

分片(Sharding)是一个更大的概念。它描述的是一种数据水平拆分机制:把一份完整的数据集,按照某种规则(分片键 + 分片策略)拆散到多个节点上。节点可以理解为"库"或者"表",核心是数据被分散存储了。

所以你可以把分片理解为"分布式的数据水平切分"。分库、分表都是分片的表现形式。分库是跨实例分片,分表是实例内分片。实际讨论中,大家说的"分库分表",其实就是Sharding在MySQL场景下的落地形态。

1.4 分区是"存储引擎内部"的切分

分区(Partition)跟前面三个容易混淆,因为它在数据库内部实现,应用层无感知。MySQL的 InnoDB 支持表分区,比如 RANGE 分区、LIST 分区、HASH 分区。一张表在逻辑上还是一张表,但底层存储被拆成了多个分区文件,查询时优化器能走分区裁剪(Partition Pruning),只扫描命中的分区。

举个例子,一单日志表按月份做RANGE分区,查上个月的日志时,只会去对应分区的物理文件里找,不会扫整张表。

很多人问:有了分区是不是就不用分表了?答案是不一定。分区确实能解决单表数据量过大的部分问题,但它受限于单个实例的资源。当并发写入量持续走高,实例本身成为瓶颈时,分区也救不了你。另外,MySQL分区功能在实际使用中有不少限制(比如分区键必须包含在主键和唯一键里),用起来并没有想象中那么顺手。

概念拆分粒度应用是否感知主要解决什么问题核心技术点
分表同库内多张表感知(SQL路由)单表数据量过大、索引膨胀表名替换、路由
分库多个实例感知(数据源路由)单实例并发与资源瓶颈多数据源管理、事务边界
分片跨节点水平拆分感知或不感知数据容量与吞吐的整体扩展分片键、一致性哈希、扩容
分区单表内物理文件不感知单表查询性能优化分区裁剪、分区键限制

这里我有一个很直观的类比:分库分表相当于把一个大仓库拆成好几个独立的仓库,每个仓库有自己的货架和出入口;分区则是在一个仓库里隔出不同的货物区,仓库还是那个仓库,门还是那个门。这个区别想清楚了,后面做技术选型时思路就顺了。

2. 什么信号出现时,你才真的需要分片

不是所有表都需要分片。网上流传的"单表超过2000万行就要分表",说实话太绝对了。我在实际项目里遇到过单表8000万行、查询依然很快的情况,也遇到过单表300万行就卡得不行的场景。数据量只是表象,真正的信号是性能和运维指标同时亮红灯。

2.1 我在项目里看到的性能拐点

就拿我维护的订单库来说,数据量从2000万涨到1.5亿的过程中,有几个非常明显的阶段:

第一阶段,单表不到3000万行,主键查询、带订单号的精确查询基本都在毫秒级,普通索引查询也还能接受。这个阶段完全不需要分片,做好索引、优化SQL就够了。

第二阶段,数据量到6000万行左右,出现了一个明显的性能拐点。部分范围查询、统计类SQL开始变慢,尤其是用了非索引字段做筛选的查询,全表扫描的代价已经非常大了。另外,每天晚上做全量备份的时间从半小时拉长到接近两个小时。

第三阶段,数据量超过1亿行,情况急转直下。即使走了索引,因为索引B+树的层级加深,随机IO次数增加,查询耗时也在成倍增长。更麻烦的是,高频写入带来的行锁竞争、undo log膨胀、binlog量暴涨,开始影响整个实例上其他业务表的稳定性。

这个经历告诉我,判断要不要分片,不能只看行数,要结合实例负载、写入并发、查询模式、备份窗口这些因素一起看。

2.2 我做分片决策时看的三类指标

第一类是容量指标。单库磁盘使用率持续超过70%,并且保持增长趋势;单表的数据量已经让日常DDL变得难以忍受——比如给一张一亿行的表加索引,可能要跑几个小时,而且过程中还有锁表风险,这个很要命。

第二类是性能指标。慢查询数量持续增加,尤其是那种"明明走了索引但还是慢"的SQL出现频率升高;数据库连接数频繁打满;主从同步延迟经常超过秒级。这些信号说明单实例的吞吐能力已经逼近天花板。

第三类是业务指标。核心实体的数据增长速度是线性的还是指数的;未来一年预计容量会翻几倍。如果一个订单系统未来一年要承载3倍的数据量,那现在就该设计分片,而不是等数据涨上来再做。

2.3 一个容易被忽略的分片时机

还有一种情况,当前数据量不大,但提前做了分片——那就是业务模型决定了某个实体的访问是"天然隔离"的。比如SaaS系统里不同租户的数据本来就互不往来,按租户ID分片就是顺水推舟的事。这种决策更多是架构层面的预判,不是为了解决眼前的性能问题。

但这里要提醒一句:过早分片的代价也不小。分片带来的复杂度是实打实的,跨节点查询、分布式事务、数据迁移、运维监控,每一项都会消耗团队大量精力。如果业务还没到那个阶段,不妨用下面第7章讲的替代方案先顶着,但要在代码层面预留好分片改造空间,别把路走死。

3. 分片键选不好,后面全是坑:核心策略拆解

如果说分片是个坑,那第一个坑就是分片键的选择。分片键选错了,后面所有操作都别扭:查询绕路、数据倾斜、扩容困难,每一样都是麻烦。这一章我把分片策略和分片键选择放在一起讲,因为两者是绑定关系。

3.1 分片算法:取模、一致性哈希、范围分片

取模是常用方式,也是最容易懂的方式。分片键的值对分片总数取模,得到一个固定的分片编号。比如 orders_id 对16取模,就能均匀地分到16个分片中。优点是简单、数据分布均匀、实现成本低;缺点是扩容时几乎要迁移全部数据。原来16个分片扩到32个分片,取模基数变了,绝大多数数据都要搬家。

一致性哈希是解决扩容问题的思路。把分片节点映射到一个哈希环上,数据按哈希值顺时针找最近的节点。扩容时只需迁移少量数据,但带来的新问题是:如果虚拟节点设计得不好,数据分布可能不够均匀;而且排查数据归属时需要多一步哈希计算,运维和理解成本更高。

范围分片是按分片键的值区间来切分。比如按订单创建时间的月份分片,1月的数据放 shard_1,2月的数据放 shard_2。优点是天然支持范围查询,局部性很好;缺点是容易产生热点。比如大促月的数据量可能是平时的好几倍,某些分片会特别"热",出现数据倾斜。

3.2 分片键选择的业务约束

分片键绝不是拍脑袋定的,它要满足几个条件。

第一,分片键必须覆盖95%以上的核心查询条件。如果订单系统的高频查询是"查某个用户的订单列表",那 user_id 就是合适的分片键;如果高频查询是"查某个订单的详情",那 order_id 更合适。两头都想要怎么办?要么用映射关系,要么冗余数据,后面我会细说。

第二,分片键的值必须稳定。一个订单所属的用户不会变,所以 user_id、order_id 这类字段是好的分片键。但"商家名称"这种可能改名的字段就不能当分片键,否则改名等于数据要重新分布。

第三,分片键不能是随机值。UUID 这类随机分布的值虽然能让数据均匀分散,但会彻底摧毁范围查询能力,在实际业务中很少直接当分片键用。

3.3 我常用的分片策略组合

不同业务场景有不同的组合方式,我整理了一张选型表:

场景推荐分片键分片策略原因
订单中心user_id取模或一致性哈希用户订单列表是核心查询
订单明细/按订单查order_id取模精确查询居多,数据均匀
日志流水时间字段RANGE分片天然适合按时间归档和清理
SaaS多租户tenant_id一致性哈希租户间数据隔离
消息/会话记录会话ID取模单会话内部连续访问,均匀分散

这里补一个很重要的实操细节:如果订单系统的核心查询既有"按用户查列表",又有"按订单号查详情",一个分片键覆盖不了怎么办?

我用的方案是订单号生成时就把用户信息编码进去。具体做法:订单号的前几位包含用户ID的哈希分片信息,这样拿到订单号就能直接定位分片,不用再走映射表。另一种做法是维护一张"订单号到用户ID"的映射表,但这样多了一次查询开销,而且映射表本身也可能成为瓶颈。我更推荐第一种,把路由信息直接编码进业务主键里。

3.4 一致性哈希的虚拟节点问题

如果你选了一致性哈希,一定要注意虚拟节点的配置。没有虚拟节点的话,节点数量少时数据分布可能严重不均匀。我曾在一个项目里看到,5个物理节点的一致性哈希,数据最多的节点比最少的多了接近3倍,就是因为哈希环上的位置过于集中。

解决办法是每个物理节点配置上百个虚拟节点,让它们在哈希环上均匀分布。像 128 个虚拟节点就是一个常用的配置起点。不过虚拟节点太密也会增加管理复杂度,建议先做数据分布模拟实验,别拍脑袋定数量。

4. 分片中间件选型:ShardingSphere还是MyCat

分片方案确定后,面临的第二个大问题是用什么工具落地。完全自己写路由逻辑也不是不行,我早期就干过,但维护成本太高。后来我用过 ShardingSphere,也调研过 MyCat、Vitess,这里把我的选型思路和实际体验分享出来。

4.1 客户端模式与代理模式的本质区别

分片中间件主要有两种形态:客户端模式(Client-Side)和代理模式(Proxy-Side)。

ShardingSphere-JDBC 是典型的客户端模式。它以 jar 包的形式嵌入到应用里,应用直接连接各个分片数据库,SQL由中间件解析、路由、改写、归并。优点是性能损耗小、部署简单,不需要额外维护一个中间件集群;缺点是只支持特定语言(Java生态最好),而且每个应用都要集成一遍。

ShardingSphere-Proxy 是代理模式。它在应用和数据库之间加了一层代理服务,应用连接的是代理层,由代理去连接背后的分片库。优点是语言无关,任何客户端只要有MySQL协议都能连;缺点是多一跳网络开销,代理自身的高可用和性能也会成为新的关注点。

MyCat 也是代理模式,早期在Java圈子里很流行,基于拦截SQL并解析的方式实现路由。但是ShardingSphere生态起来之后,MyCat在社区活跃度和内核能力上的差距逐渐显现,我现在已经不太推荐新项目用了。

4.2 我给出的选型建议

我自己的习惯是:Java应用首选 ShardingSphere-JDBC,性能好、功能全,分布式事务、读写分离、数据加密这些都能支持;如果是异构系统(比如多个语言都要访问分片数据),或者不想改应用代码,那就用 ShardingSphere-Proxy。

有人问为什么不用Vitess?Vitess 本身很优秀,目前主要在Kubernetes化的大规模数据库场景下有明显的优势,比如云原生数据库平台、跨机房部署等。但对于很多中小团队来说,Vitess 的部署和运维门槛偏高,除非你已经深度拥抱Kubernetes,否则不必一开始就上这个级别。

为了让你更直观地对比,我给一张我做过多次评估的表格:

对比项ShardingSphere-JDBCShardingSphere-ProxyMyCat
部署模式客户端内嵌独立代理独立代理
支持语言Java优先任意MySQL客户端任意MySQL客户端
性能损耗
分布式事务XA、Seata集成支持有限支持
运维成本高(代理集群)
社区活跃度非常活跃非常活跃一般
适用场景Java服务直接分片多语言、不改代码老项目遗留

4.3 ShardingSphere-JDBC接入的最小配置

我以订单表按 user_id 取模分片为例,给你写一个最简配置,方便你快速理解整个接入过程。当然这只是一个示例配置,实际使用中还要根据自己的场景调整。

# shardingsphere-jdbc 分片配置示例 dataSources: ds0: url: jdbc:mysql://192.168.1.10:3306/order_db username: root password: 123456 ds1: url: jdbc:mysql://192.168.1.11:3306/order_db username: root password: 123456 rules: - !SHARDING tables: t_order: actualDataNodes: ds${0..1}.t_order_${0..15} tableStrategy: standard: shardingColumn: user_id shardingAlgorithmName: order_table_inline databaseStrategy: standard: shardingColumn: user_id shardingAlgorithmName: order_db_inline shardingAlgorithms: order_db_inline: type: INLINE props: algorithm-expression: ds${user_id % 2} order_table_inline: type: INLINE props: algorithm-expression: t_order_${user_id % 16}

这段配置的意思是:先根据 user_id 对2取模决定数据进哪个库,再对16取模决定进哪张表,总共32个物理分片。实际使用中要格外注意 INLINE 策略不能做跨分片JOIN和子查询,ShardingSphere 的官网文档里写得很清楚,配置前值得花时间好好看看。

4.4 选型时最容易忽略的三个点

第一个是驱动版本兼容性。ShardingSphere-JDBC 对 MySQL 驱动的版本有要求,版本不对会出现一些诡异问题,比如时间类型解析异常,所以接入前先看支持的版本范围,最好在测试环境把驱动版本也一起固定下来。

第二个是SQL兼容性。分片后不是所有SQL都能透明执行。带 LIMIT 深度分页的 SQL 会消耗性能;复杂的多表关联(跨分片JOIN)可能直接不支持;子查询有时候会走全分片路由,实际效果可能比不分片更差。接手项目前,先把核心服务的多条查询SQL在测试环境跑一遍,心里有数。

第三个是连接池配置。客户端模式下,应用要同时保持到多个分片库的连接。如果分片库有32个,每个应用实例的连接池又配了50个连接,那数据库端看到的连接数就是32乘以50再乘以实例数,很容易把数据库的连接限制打爆。我后来把每个分片库的最小空闲连接调低,最大连接数也做了限制,才把这个隐患压住。

5. 分片落地后,那几件让人头疼的事

分片真正上线后,麻烦才刚刚开始。分布式ID、跨节点查询、分布式事务、唯一性约束,每一件都跟单库时代完全不同。这里挑几个让我印象最深的展开讲。

5.1 分布式ID:全局唯一主键怎么设计

单库时代可以用自增主键,分片后每个分片各自自增,一定会有重复,所以必须引入全局唯一ID方案。

我用的主流方案有两种:

第一种是雪花算法(Snowflake)。64位整数,包含时间戳、机器ID、序列号,性能极高,趋势递增。缺点是依赖机器时钟,如果时钟回拨,可能会生成重复ID,所以要在代码里做时钟回拨保护。网上有很多改良版本,比如百度UidGenerator、美团的Leaf,都是基于雪花思路做的,建议直接参考这些项目的实现,别自己从零造轮子。

第二种是号段模式。数据库维护一个号段表,应用批量获取一批ID段(比如每次拿1000个号),在本地生成全局唯一ID。优点是简单、可控、没有时钟回拨问题;缺点是需要额外维护号段表,而且在号段用完之前需要提前申请下一批,否则会出现ID生成阻塞。

我给一个比较稳的方案:核心业务表使用雪花算法生成ID,但把分片路由信息编码进ID里(比如用ID的中间几位表示分片编号),这样拿到ID就能直接计算出数据在哪个分片,非常好用。

5.2 跨节点查询与分页:没那么简单

分片后最大的痛点是查询。如果你的 SQL 没有带分片键条件,中间件就只能把SQL广播到所有分片执行,然后把结果合并。比如你想查"最近一个月下单量排名前100的用户",这条SQL本身没有指定 user_id,那就得在32个分片上都执行一遍,再合并排序。

这个场景我的处理思路是:核心的跨分片查询尽量降级为"单分片查询",实在避免不了就把结果预计算好存起来。像排行榜、统计报表这类数据,用定时任务提前算好写进结果表,查询时只查结果表,彻底绕开跨分片问题。

分页也是重灾区。分片后,LIMIT 10000, 20 这种"深度分页"会把每个分片的前10020条数据都捞出来,然后在中间件内存里合并排序,再取第10000条起的20条。数据量一大,内存直接爆掉。

解决办法有三个方向:

  • 禁止深度分页,改为"下一页"模式,用上一页的最后一条记录ID作为查询条件;
  • 如果必须跳页,可以限定只能查询前N页;
  • 使用 Elasticsearch 这类外部搜索引擎,把分页查询交给搜索引擎做。

5.3 分布式事务:不是所有场景都需要强一致

分片之后,原来在一个库里的本地事务变成跨库分布式事务。经典的例子:下单要扣库存、加订单、减余额,如果订单在分片1,库存和余额在分片2,那一个操作就涉及到两个分片的数据变更,无法用本地事务解决。

分布式事务方案我按场景分三类:

第一类是要求强一致的场景,用 XA 协议或 Seata AT 模式。但XA的性能损耗偏高,高并发场景下要慎重使用。Seata 的AT模式做了不少优化,Spring Cloud Alibaba 生态里接入也方便,适合对一致性要求高的核心链路,但我依然强烈建议尽量把数据放在同一个分片,用本地事务解决。

第二类是追求最终一致性的场景,用本地消息表 + 消息队列。做法是:在本地事务里写业务数据和消息表,提交事务后再异步发送消息给下游模块,下游消费消息后做自己的业务,靠MQ的重试机制保证最终成功。这个方案性能好,是很多场景下性价比最高的选择。

第三类是纯异步补偿,TCC、Saga模式。适合长流程业务,比如聚合支付、跨组织协作。但TCC侵入性强,需要业务方写 Confirm 和 Cancel 逻辑,编码量很大,只推荐在确实必要的核心交易链路用。

我的经验是:能通过合理的分片键设计,把需要强一致的数据放在同一个分片,就别引入分布式事务。比如按用户维度分片时,把用户信息、订单、积分都放在用户ID对应的分片里,那么一个用户的所有操作始终落在同一个分片,事务还是本地事务,省去一堆麻烦。

5.4 唯一性约束的破局

单表能建唯一索引,分片后跨分片唯一约束就比较难做。比如手机号、身份证号、用户昵称这类需要全局唯一的字段。

思路有三条:

第一条,唯一字段本身包含分片信息。比如手机号不是分片键的话,可以为手机号建一张路由表,手机号的哈希对应到特定分片,通过单分片唯一索引保证全局唯一。

第二条,用分布式组件实现唯一性校验。比如用Redis的SETNX做唯一性检查,用ZooKeeper做分布式锁。但前提是这些组件自身的高可用要做好,否则会引入新的单点。

第三条,如果并发量不大,定期在在数据库里做全局唯一性扫描。这个方案最朴素,适合对实时性要求不高场景,比如清理重复昵称。

实际项目中我最常用的是第一条:把"需要全局唯一"的业务字段作为分片键,或者在写入时通过映射关系定位到固定的分片,用数据库自身的唯一索引来保证一致性。不管用哪种方案,测试阶段都要专门准备一套用例去压这个唯一性约束,否则线上最容易出数据问题。

6. 数据迁移与灰度切换的实操记录

做好分片设计、中间件选型,不等于就万事大吉。老系统上分片最难的其实是数据迁移和切换,一不小心就是线上事故。这里我以订单表从单库迁移到32个分片为例,说说我走的完整流程。

6.1 双写:新旧两套并行写入

迁移过程中最怕的是丢数据或数据错乱。所以第一步不是直接切流量,而是先做双写。应用层在业务代码里同时写入旧库(单表)和新分片库,新分片库按照提前设计好的分片规则路由到对应的分片表。

但双写也有陷阱:写入新分片库失败怎么办?如果双写失败直接返回错误,影响主链路;如果异步补偿,又可能造成新旧库数据不一致。我采用的是"同步双写 + 失败重试 + 日志告警"的组合。同步双写失败不阻塞主流程,但会记录日志并告警,随后由补偿任务定时扫描失败记录重新同步。

这里要给一个血泪经验:双写期间一定不要改分片规则。一旦分片规则变了,之前写入的新库数据跟旧库对不上,后续校验怎么做都是乱的。分片规则一旦定下来,上线之前要当作"不可变配置"对待。

6.2 全量 + 增量同步

双写只覆盖"切换时刻之后"的新增数据,历史存量数据需要先同步到新分片库。我用的是全量 + 增量结合的方式。

全量同步比较容易,写脚本按主键范围分批读取旧库数据,再按分片规则写入新库。但要控制每次读取范围,不要一把梭把内存撑爆。我一般按5000行一个批次,边读边写,同时记录批次进度,方便断点续跑。

增量同步可以用 binlog 订阅。旧库的数据库开启 binlog,用 Canal 订阅变更日志,将每一条 insert、update、delete 事件应用到新分片库。这样就保证全量同步期间产生的新增数据不会丢。Canal 应用的延迟要监控,正常情况下应保持在秒级以内,一旦延迟太久,说明堆积严重,需要扩容消费能力。

6.3 数据校验与切流前的检查

同步跑完之后,不能急着切流量,先做数据校验。我的校验办法是抽样 + 对账:

  • 按分片键范围抽样,对比新旧库同一业务ID的核心字段是否一致;
  • 对关键指标做汇总对账,比如总订单数、总金额、状态分布,用SUM和COUNT来粗粒度校验;
  • 随机挑几个高频用户,查看他们在新旧库的订单明细是否完全一致。

校验通过后,先让少量测试流量走新分片库,观察日志和监控,确认无异常。然后再按比例逐步放量:5% -> 20% -> 50% -> 100%。每放大一档,观察至少半小时,如果成交量、错误率、接口响应时间都稳定,再进入下一档。

6.4 回滚预案

即使做得很谨慎,也要做好回滚预案。我在切换期间保留了两条后路:

一是旧库持续保留,并且保持7天的只读快照归挡。如果切换后发现严重问题,可以把流量切回旧库,虽然切换期间双写产生的增量数据可能丢失,但至少主业务能快速恢复。

二是新分片库的中间件配置提前准备好"一键回切"脚本,包括路由配置、数据源配置、连接池参数。有时候切到新库出问题,不是业务问题,而是某个配置参数不合法导致启动异常,有脚本可以快速回退。

实际上我们最后没有触发回滚,但“永远准备好一条可以跑的通的路”这个习惯,让我在切流量的时候心里踏实很多。做技术方案,信心不是来自“肯定不会出错”,而是来自“出错了我也有办法收拾”。

7. 有些场景,其实不用急着分片

写了这么多分片的内容,最后我要说点反直觉的话:很多系统根本不需要分片。市面上对分片有过度推崇的倾向,好像大库不分片就是技术债。但在我的经验里,很多场景存在更轻量的替代方案,先用它们扛一阵,往往比一开始就上分片更划算。

7.1 分区表能解决一部分问题

如果你的瓶颈只是单表数据量过大、查询太慢,而且数据有明显的生命周期(比如日志、流水、订单归档),可以先试试分区表。用时间字段做 RANGE 分区,查询时走分区裁剪,挻多数场景下性能提升明显。

分区表的实现成本很低——它只是一个DDL操作,应用代码不用改。但它确实解决不了跨实例的资源瓶颈问题,所以它适合作为分片的前置手段,能缓一阵是一阵。

7.2 归档表与历史库是个低成本方案

很多大表里面有大量"冷数据"。比如订单表,日常访问的基本是最近3个月到半年的数据,三年前的历史订单几乎没人查。把这些历史数据迁移到归档表或者独立的历史库,主表数据量立刻大幅下降,性能自然恢复。

实际操作上没有想象中复杂。写一个定时任务,每天把超过180天的订单搬到历史表/历史库,主库只留热数据。查询历史订单时路由到历史库。成本低、见效快,而且风险远比分片小。

7.3 读写分离 + 缓存能扛很大一部分流量

如果瓶颈是读多写少,缓存加读写分离的价值,可能比分片还大。用 Redis 缓存热点数据,把读流量从数据库转移到缓存;数据库层面做一主多从,写入走主库,读取走从库,相当于把单实例的读能力成倍放大。

我见过一个业务,峰值QPS冲到6000多,数据库单库扛不住,很多人建议分片。结果我们梳理后发现,写QPS不到100,绝大部分是读。我们用 Redis 缓存热点商品和用户信息,配合一主两从的读写分离,就把问题解决了,压根没动分片。

7.4 不要为了分片而分片

分片有一个很致命的特性:一旦上了分片,很多数据库能力都会受限。JOIN不好用了、子查询被限制、分布式事务变复杂、运维监控难度上升、团队能力要求变高。这些代价都是长期的,不是上线那一刻的切换成本。

我的建议是,在决定分片之前,先回答这几个问题:

  • 有没有认真做过 SQL 优化和索引优化?
  • 有没有统计过数据的冷热分布?历史数据能不能归档?
  • 有没有评估过缓存、读写分离、分区表的收益?
  • 分片后,业务团队能不能接受查询和事务限制?

如果这些答案里有一个"还没有",就先别急着分片。分片是数据架构的大手术,能不做就不做,等确实必要了再动手。

8. 几点实践经验,当聊天了

最后不做什么总结了,就聊几个我反复踩过的细节吧。

第一,分片元数据一定要统一管理。不同的库表对应哪个分片,分片键是什么,分片算法参数是多少,这些要集中维护,不能散落在业务代码里。我曾见过一个项目,同一个订单表在不同服务里分别维护了不同的分片规则,结果跨服务查询时数据路由对不上,排查了一个星期。

第二,分片数尽量定得"大一点但别太大"。比如预估一年后数据量是4000万,单表合理数据量是500万的话,8个分片就够了。但如果预判两年后还可能再翻倍,我当时会直接设计成16个分片。宁可一开始分得多一点,也不要一年后就面临扩容。但也不能盲目的分上千个分片,分片太多会带来连接数、文件描述符、运维复杂度等一堆开销,而且很多分片的数据量太少,根本没有必要。

第三,监控系统要提前适应分片形态。分片后,不能再只看"数据库实例"层面,还得看分片级别:每个分片的数据量是否均匀、慢查询主要集中在哪个分片、连接数在分片间是否均衡。我用的是Prometheus + Grafana 搭建分片监控大屏,把每个分片的关键指标通通拉出来,第一次扩容和排查问题的时候帮了大忙。

第四,所有方案都要有人能接得住。分片不是一个人拍脑袋能做成的,团队里至少要有一个核心开发熟悉分片中间件的原理、至少一个DBA能处理分片库的备份恢复和扩容操作。否则一旦老员工离职,这套系统就成了烫手山芋。

分片这件事,技术上没有银弹。每个人的业务不一样、访问模型不一样,适合的才是最好的。希望我这篇关于分表、分库、分片、分区的记录,能帮你少走一点弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 11:04:10

O2O实时CRM架构:从客户管理到决策中枢的演进

简介:本资源是一份面向互联网中台架构师、O2O业务系统设计者及CRM平台开发者的技术文档,深入解析美团如何通过CRM系统构建核心线下能力。文档系统阐述其公私海线索管理模型、45天期限机制、BD与运营协同分工、移动办公支持(MOMA客户端&#x…

作者头像 李华
网站建设 2026/9/17 11:03:13

低成本闭环步进方案:HK32F030C8T6+TB67H450+MT6816设计详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 10:59:02

传感器稀疏布局优化:用Python实现高信息增益的最少安装点设计

1. 项目概述:这不是一个Python库的使用教程,而是一场关于“在哪里装、装几个、怎么装才最划算”的工程决策实战你手头有一台工业设备需要做状态监测,预算有限,传感器采购清单不能超过8个;你正在设计一套面向老年瘫痪患…

作者头像 李华
网站建设 2026/9/17 10:58:38

STM32F103C8T6+WS2812B蓝牙键盘AD设计:原理图、PCB与固件

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华