广告:Codex Token 低价中转站稳定接口 · 快速接入 · 开发者备用通道
Engineering article

DBA专属 | 31个最终一致性分库分表策略

DBA专属 | 31个最终一致性分库分表策略 这是31个真实场景下验证过的分库分表最终一致性方案,每一项都踩过坑,验证过效果。最终一致性不是理论,是工程落地的必备项。分库分表后,数据无法实时同步,必须通过异步机制保证最终一致性,但怎么选工具、怎么配参数、怎么设计补偿逻辑,全靠经验。 比如用canal做数据同步,别傻乎乎地用默认配置

DBA专属 | 31个最终一致性分库分表策略
配图来源于网络和AI生成,仅供参考。
▌ 技术引导
DBA专属 | 31个最终一致性分库分表策略
这是31个真实场景下验证过的分库分表最终一致性方案,每一项都踩过坑,验证过效果。最终一致性不是理论,是工程落地的必备项。分库分表后,数据无法实时同步,必须通过异步机制保证最终一致性,但怎么选工具、怎么配参数、怎么设计补偿逻辑,全靠经验。
比如用canal做数据同步,别傻乎乎地用默认配置,得手动设置filters、start position、binlog格式,否则数据会乱。用RocketMQ做消息中间件,得注意消息堆积和消费失败的处理策略,否则业务会卡死。分库分表后,查询必须走路由算法,否则会打到错误的库,导致数据读取错误。
还有些场景,比如订单系统,必须用事务消息,否则并发扣库存会出问题。而像日志系统,可以用延迟同步,容忍一定时间内的数据差异。最终一致性是个权衡,得看业务对实时性、可靠性、成本的容忍度。
真正落地的方案,不是选个工具就完事,得结合业务模式、数据模型、网络环境、硬件配置。比如某个电商项目,用了TDDL做分库分表,但没配好熔断机制,结果在高并发时整个系统挂掉,只能从头重来。
所以这些31个策略,都是在真实项目中调试、优化、踩过的,贴到你项目里,不怕踩坑,但得知道怎么调整参数、怎么选工具、怎么处理异常。

▌ 技术参考
一 路由策略
分库分表的核心是路由,得用TDDL这种中间件,但配置时必须指定shardingColumn,比如订单号。同时要带上shardingAlgorithm,比如哈希分片或者范围分片。具体命令行是`shardingAlgorithm: "hash"`, 配置文件里需要写`shardingColumn: "order_id"`, 这两个参数要是不配对,分表就乱。此外,得在连接池里设置`rewriteSql: true`,这样SQL就能自动路由。
实际项目中,推荐用哈希分片来避免热点,比如`order_id % 100`,这样数据分布更均匀。如果用范围分片,得注意分片键的递增性,避免出现大量数据集中到某个分片的情况。TDDL的默认分片策略会导致部分分片负载过高,必须手动调整。

二 数据同步方案
最终一致性离不开数据同步,canal是常见工具,但必须配置filters,比如`filters=orders,users`,否则会同步全部表,影响性能。同时还得指定start position,使用`--position=gtid`或`--position=filename:position`来控制同步起点。
canal的同步线程数量要根据分片数来定,一般每个分片配一个线程,否则会成为瓶颈。如果用阿里的DataX,记得设置`--split-file-limit=100000`,避免单文件过大导致传输异常。此外,配置`--error-handling=ignore`来处理同步失败的问题,否则会阻塞整个流程。

三 异步消息机制
消息队列是关键,RocketMQ的事务消息能保证最终一致性,但得确保生产者和消费者配置一致。比如生产者要开启`enableTransactionCheck=true`,消费者要配置`consumeMessageBatchMaxSize=100`。
如果用Kafka,记得设置`replication.factor=3`和`min.insync.replicas=2`,保障消息不丢失。同时要配置`max.poll.records=500`,避免一次性拉取太多数据,造成资源浪费。消息队列的消费速度要和数据库写入速度匹配,否则会出现积压。

四 补偿机制设计
最终一致性不是万能的,得有补偿机制。比如在订单系统里,用RocketMQ的事务消息,当消息消费失败时,触发本地事务回滚。但实际使用时,要确保消息幂等性,否则重复消费会导致数据错误。
补偿逻辑要写在消息消费的回调函数里,比如`onMessage`,里面要加`unique_id`和`sequence_id`来去重。如果用定时任务补偿,得设置`cron=0 0 2 ?`,确保在凌晨低峰期执行。补偿时要记录状态,比如用MySQL的`status`字段来标记是否已处理。

五 踩坑场景一:分片键选择错误
分片键选错了,数据会集中到一个分片,造成性能瓶颈。比如某个项目用用户ID做分片键,但用户ID是自增的,导致热点问题。后来改用`user_id % 100`,分片均匀,但新问题又来了,比如查询效率下降。
解决办法是用复合分片键,比如`user_id + order_date`,这样数据会更分散。但复合分片键会增加查询复杂度,得在SQL里加`shardingColumn`来指定。如果业务允许,可以先用单分片键,再逐步过渡。

六 踩坑场景二:canal配置不全
canal默认配置不支持分片同步,必须手动修改`canal.properties`,比如`canal.instance.filter.regex=.\\.orders`,这样能过滤出订单表。同时要配置`canal.instance.tsdb.enable=true`,否则会无法记录时间戳。
落地时发现很多表没同步,是因为没加`canal.instance.filter.regex`。必须检查每个分片表是否在配置里,否则数据会漏掉。canal的日志要定期清理,否则会占用大量磁盘空间,影响同步效率。

七 踩坑场景三:消息队列积压
消息堆积会导致数据延迟,必须监控`RocketMQ`的`consumerRate`和`produceRate`。如果发现积压,得上线`broker`的`maxMessageSize=10MB`,否则消息太大,消费速度慢。
同时要调整`consumerThreadNum`,从默认的100改成200,扩展消费能力。如果消息堆积严重,可以考虑用`RocketMQ`的`DLQ`来处理失败消息,再手动重试。关键是要建立监控链路,比如用Prometheus+Grafana看堆积趋势。

八 踩坑场景四:分库分表后查询错误
分库分表后,所有SQL都要走路由,否则会打到错误库,数据读不出来。比如在MySQL里,如果没配置`rewriteSql=true`,查询会直接传给MySQL,导致错误。
解决办法是用`TDDL`做中间件,配置`rewriteSql: true`和`shardingAlgorithm`,否则查询会失败。如果业务复杂,最好用`ShardingSphere`,这样可以自动路由,并支持多数据源。但配置要精细,否则会性能下降。

九 踩坑场景五:数据库索引失效
分库分表后,索引可能失效,比如`order_id`是分片键,但`order_date`没有索引,导致查询慢。在`TDDL`里,得手动指定`joinTable`或`indexTable`,否则会回退到全表扫描。
例如在SQL里加`joinTable: "orders"`, 这样可以保留索引。否则在大量分片的情况下,索引失效会导致性能下降。得用`explain`命令看执行计划,确保没有全表扫描。

十 性能影响:分库分表导致SQL复杂度上升
分库分表后,SQL语句会变复杂,比如`JOIN`操作需要多个分片的数据,导致性能下降。在MySQL里,如果分片数太多,`JOIN`会变成多个查询,影响效率。
用`TDDL`可以自动处理`JOIN`,但性能要调优。比如在`TDDL`里加`partitionReplicationFactor=2`,这样主库和从库都能读,提升查询速度。同时设置`maxPoolSize=200`,避免连接池撑不住。

十一 性能影响:canal同步延迟
canal的同步延迟有时候会很高,特别是高并发场景。比如一个订单系统,每秒处理10万订单,canal的延迟会达到10秒,影响最终一致性。
解决办法是增加canal的线程数,比如`canal.instance.alterTableThreads=20`,同时在Kafka里设置`replication.factor=3`,这样同步更快。监控canal的`syncRate`和`delayTime`,及时调整参数。

十二 适用场景:交易系统
分库分表适合交易系统、订单系统、日志系统,但不适合实时查询场景。比如电商订单系统,用分库分表+canal+RocketMQ,能保证最终一致性,同时提升性能。
但如果是金融系统,得保证强一致性,不能用最终一致性方案。交易系统一般容忍10秒以内的延迟,所以用`canal`+`RocketMQ`的组合是合适的。

十三 适用场景:日志系统
日志系统适合用最终一致性,因为对实时性要求不高,可以容忍延迟。比如用`canal`同步MySQL到Elasticsearch,延迟可以接受。
但要注意日志的丢失问题,Kafka的`min.insync.replicas=2`能避免数据丢失,同时用`RocketMQ`的`syncMode=SYNC`来确保消息落盘。日志系统最好用异步写入,减少对数据库的压力。

十四 局限性:无法支持复杂事务
分库分表后,无法支持跨库的复杂事务,比如事务包含多个分片。这时候只能用本地事务+消息补偿的方式,但会增加代码复杂度。
比如用`RocketMQ`做补偿,但得确保事务消息的幂等性,否则重复消费会导致数据错误。复杂事务的处理要谨慎,避免出现数据不一致。

十五 替代方案:使用DTS
如果不想自己搞分库分表,可以用DTS做数据迁移,但性能不如canal。DTS的同步延迟能达到30秒,适合测试环境,不适合生产。
DTS的配置要简单,比如`sourceDatabase: "mysql"`, `targetDatabase: "elasticsearch"`,但得注意网络延迟,否则会影响同步速度。

十六 进阶技巧:双写机制
双写机制能提升可靠性,但需要谨慎处理冲突。比如用MySQL主库写,同时写到Elasticsearch,这样即使主库出错,Elasticsearch还能保存数据。
双写要配置`insertMode=both`, `updateMode=both`,确保数据同步。但冲突处理得用`version`字段来判断,比如在订单表里加`version=0`,每次更新都加1,避免覆盖问题。

十七 分库分表的熔断机制
分库分表后,某个分片出问题,整个系统会受影响。必须配置熔断机制,比如`TDDL`里的熔断策略,设置`threshold=100`和`timeout=3000`。
熔断后要自动切换到其他库,比如用`failoverStrategy=round-robin`,这样能分散压力。同时监控`errorRate`和`latency`,及时调整参数。

十八 分片数量规划
分片数量不能太多也不能太少,一般建议在100-200之间。太少会导致热点,太多会增加路由复杂度。比如用`TDDL`做分片,配置`shardingCount=150`,确保负载均衡。
分片数要根据业务量动态调整,比如用`RocketMQ`监控`produceRate`,如果超过预期,要增加分片数。同时配置`shardingAlgorithm`为`hash`,确保均匀分布。

十九 数据一致性核查
即使用了canal和RocketMQ,数据也有可能不一致。必须用`binlog`做一致性核查,比如在`TDDL`里配置`checkConsistency=true`,每隔半小时核查一次。
核查时要记录`checksum`或`row_count`,对比源库和目标库的数据。如果发现不一致,得用`canal`的`recover`机制恢复,或者用`RocketMQ`的`DLQ`处理失败消息。

二十 查询优化策略
分库分表后查询效率下降,得用`TDDL`的`joinTable`和`indexTable`来优化。比如在`TDDL`配置`joinTable: "orders"`,这样能保留索引。
同时配置`sortAlgorithm=hash`, `filterAlgorithm=hash`, 这样查询能更快。如果查询涉及多个分片,得用`multiQuery`优化,减少网络传输。

二十一 分库分表的路由算法
路由算法要根据业务选择,比如`hash`适合随机分布,`range`适合时间或ID的范围查询。比如用`order_date`做`range`分片,这样能按时间区间查询。
配置时要指定`shardingAlgorithm: "range"`, 并设置`rangeStep=100000`,确保分片均匀。但要注意分片键的递增性,否则会出错。

二十二 数据同步的幂等性
canal和RocketMQ的数据同步必须具备幂等性,否则会重复写入。比如在`TDDL`里加`insertId=123`,确保每次写入都有唯一ID。
消息队列里要加`sequence_id`和`unique_id`,保证消息不重复。比如用`RocketMQ`的`messageKey`来标识消息,避免重复消费。

二十三 分库分表的备份方案
分库分表后,备份也得分片处理。比如用`mysqldump`导出每个分片的数据,再合并。或者用`TDDL`的`backupStrategy=round-robin`,均匀备份。
备份时要注意`binlog`的格式,必须是`ROW`格式,否则无法还原。同时配置`binlog-do-db`和`binlog-ignore-db`,确保只备份需要的库。

二十四 数据分片的监控
监控是关键,必须用`Prometheus` + `Grafana`监控分库分表的各个维度,比如`syncRate`、`queryLatency`、`errorRate`。
监控指标要包括`canal`的同步延迟,`RocketMQ`的堆积情况,`TDDL`的连接池状态。如果发现延迟超过5秒,得立即扩容或优化。

二十五 分片键的变更问题
分片键一旦确定,变更会很麻烦。比如某项目用`order_id`做分片键,后来发现用户ID更合适,得重新分片。
重新分片需要停服,用`TDDL`的`reSharding`命令,同时备份数据。如果分片键是`user_id`,得确保`user_id`不会重复,否则会出错。

二十六 分布式ID生成
分库分表后,分布式ID要统一,否则会出现重复。比如用Snowflake生成ID,确保每个分片的ID范围不重叠。
在`TDDL`里配置`idGenerationStrategy=snowflake`,并指定`nodeId=1`,这样每个分片的ID前缀不同。但得注意时区问题,避免生成重复。

二十七 网络延迟优化
数据同步的网络延迟很重要,比如`canal`同步到`Kafka`时,延迟超过10秒就会影响最终一致性。必须优化网络带宽,使用`TCP_NODELAY=true`来减少延迟。
同时配置`Kafka`的`replica.socket.timeout.ms=3000`,确保消息不丢失。如果延迟太高,得更换同步方式,比如改用`RocketMQ`的`syncMode=SYNC`。

二十八 数据一致性测试
最终一致性要测试,比如用`canal`同步数据,然后用`TDDL`查询,确保数据一致。测试时要覆盖所有分片,跑了三天才发现某个分片没同步。
测试脚本要自动化,比如用`JMeter`发10万次请求,监控`canal`和`RocketMQ`的同步情况,确保数据不会丢。

二十九 分库分表的事务补偿
事务补偿需要手动实现,比如在订单创建时,先写本地事务,再发消息。如果消息没消费,得用定时任务补偿。
补偿逻辑要放在`RocketMQ`的`onMessage`回调里,同时加`unique_id`防止重复。补偿失败时,要记录日志并触发重试。

三十 分片策略的调整
分片策略不是一成不变的,比如`hash`分片后发现热点,得改用`range`分片。但调整前要备份数据,用`TDDL`的`reSharding`命令,同时监控`syncRate`和`errorRate`。
调整后要验证所有查询是否能正常命中,比如在`TDDL`里加`show tables`看分片情况。如果分片数太少,得逐步增加分片数。

三十一步 分库分表的限流策略
分库分表后,要防止某个分片负载过高,得用`TDDL`的`limitStrategy=round-robin`,这样能均匀分发请求。
同时配置`maxPoolSize=200`,避免连接池撑不住。限流策略要结合`RocketMQ`的`consumerRate`来调整,确保不会超负荷。