前言
随着政企信创系统业务量快速增长,单库单表逐渐触达性能瓶颈,分库分表成为很多核心系统的必然选择。但国产数据库(人大金仓、达梦等)搭配 ShardingSphere/MyCat 等中间件的组合,远比 MySQL 生态更容易踩坑:SQL 解析失效、方言不兼容、事务回滚失败、主键冲突、扩容困难…… 很多项目上线后故障频发,反而不如单库稳定。
本文基于10+ 政务 / 国企核心系统分库分表落地经验,从架构选型、SQL 兼容、主键事务、运维扩容、国产化特性五个维度,整理了 24 个 90% 项目都会犯的经典错误,每个坑都附现象、根因与生产级解决方案,帮你避开绝大多数弯路。
一、适用范围
- 底层数据库:人大金仓 KingbaseES V9、达梦 DM8/DM9
- 分片中间件:ShardingSphere-JDBC 5.x、MyCat 2.x
- 业务场景:政务审批、国企订单、民生服务等 TB 级数据量系统
- 适用人群:信创架构师、后端开发、DBA、项目技术负责人
📌 一、架构选型与分片设计类坑(前期错了后期改不了)
这类错误属于 “先天缺陷”,上线后再改成本极高,甚至需要推倒重来。
坑 1:盲目上分片,单库能扛硬拆分
现象:几十万数据量就上分库分表,复杂度翻倍,性能反而不如单库,运维成本飙升。根因:把分库分表当银弹,忽略了国产数据库单库支撑能力 —— 金仓 / 达梦单库 5000 万行、1TB 数据完全可以通过索引、分区表优化满足性能。解决方案:
- 优先用分区表(按时间 / 区域)解决单表性能问题,分区表对业务零侵入
- 单库 TPS 持续超过 2000、单表数据量超 5000 万且持续高速增长,再考虑分库分表
坑 2:分片键选错,数据分布严重不均
现象:某几个分库 CPU、磁盘占用是其他库的 5~10 倍,热点库成为新瓶颈,分了等于没分。典型错误:政务系统按创建时间分片,当月库压力爆表,历史库空闲;按单位编码分片,大单位数据量是小单位的几十倍。解决方案:
- 分片键首选「高频查询维度 + 分布均匀」的字段,如用户 ID、统一社会信用代码哈希分片
- 时间分片仅适合冷数据归档,不适合核心业务表
- 预估数据分布偏差超过 3:1 时,采用一致性哈希 + 虚拟节点方案
坑 3:分片数量拍脑袋定,扩容困难
现象:一开始分 4 个库,半年就不够用,扩容需要全量数据重分布,停机十几个小时。根因:用简单取模分片,分片数变更后所有数据的路由规则全部改变。解决方案:
- 初始分片数按未来 3 年数据量规划,一次到位,尽量不扩容
- 必须支持扩容的场景,采用 2 的 N 次方分片,或使用一致性哈希算法
- 政务系统优先用「按行政区域分片」,扩容只新增区域,不影响存量数据
坑 4:中间件选型跟风,不考虑国产化适配
现象:照搬 MySQL 技术栈,选了某开源分片中间件,结果对金仓 / 达梦兼容极差,SQL 解析全错,事务频繁失败。根因:多数开源分片中间件原生只支持 MySQL、PostgreSQL、Oracle,对国产数据库适配不完善,很多功能是 “看起来能用,实际全是坑”。解决方案:
- 优先验证 ShardingSphere,它对国产数据库适配相对完善,金仓可指定
kingbase8方言,达梦可用 Oracle 方言兼容 - 核心系统优先选择国产原厂配套的分片方案,降低适配风险
- 选型前必须做 POE 验证,覆盖所有核心 SQL 与事务场景
📝 二、SQL 兼容与语法类坑(最高发,占比 60%)
国产数据库兼容模式 + 分片中间件 SQL 解析,相当于两层语法过滤,是问题最多的重灾区。
坑 5:方言不配置,SQL 解析全失效
现象:简单查询都报错,分页、关联查询完全不能用,日志全是 SQL 解析异常。根因:ShardingSphere 通过 JDBC URL 前缀识别数据库类型,金仓jdbc:kingbase8:、达梦jdbc:dm:无法被默认识别,直接回退到 SQL92 标准方言,大量语法解析失败。解决方案:
- 人大金仓手动指定数据库类型:
props: database-type: kingbase8 limit-format: LIMIT %d OFFSET %d - 达梦配置为 Oracle 兼容模式,驱动类使用
dm.jdbc.driver.DmDriver - 复杂场景自定义方言扩展,适配国产数据库特有语法
坑 6:分页查询数据重复 / 丢失
现象:列表翻页时,有的数据重复出现,有的数据直接消失,总数也对不上。根因:
- 分片中间件的 limit 分页逻辑与国产数据库分页语法叠加,双重分页导致数据截断
- 金仓 Oracle 兼容模式下
rownum分页与 ShardingSphere 分页逻辑冲突 - 排序字段不唯一,多库归并时排序不稳定解决方案:
- 统一使用 MyBatis-Plus 分页插件,由 ShardingSphere 统一处理分页,业务层不手写 rownum
- 排序字段必须包含唯一主键,保证归并排序稳定性
- 金仓关闭 Oracle 模式的 rownum 分页,统一使用 LIMIT/OFFSET 语法
坑 7:ORDER BY 排序结果与单库不一致
现象:同一条 SQL,单库执行和分片执行排序结果不一样,NULL 值位置、大小写排序都有差异。根因:各分片独立排序后,中间件再做内存归并,不同数据库的排序规则(如 NULLS FIRST/LAST、大小写敏感)与中间件归并规则不一致。解决方案:
- 显式指定 NULL 值排序规则:
ORDER BY name ASC NULLS LAST - 达梦关闭大小写敏感,或排序字段统一使用大小写转换函数
- 避免复杂的多字段混合排序,尽量用数值型字段排序
坑 8:聚合查询结果错误
现象:COUNT(*)、SUM()、GROUP BY查询结果和单库统计对不上,尤其是去重统计、多字段分组。根因:
- 分片中间件无法正确下推聚合函数,多库结果合并逻辑错误
DISTINCT、GROUP BY跨分片去重不彻底- 国产数据库特有聚合函数中间件不识别,直接透传导致报错解决方案:
- 核心统计 SQL 提前做下推验证,复杂统计建议走数仓或离线报表
- 全局去重统计避免直接用
COUNT(DISTINCT),改用应用层聚合 - 禁止使用数据库特有聚合函数,全部改用标准 SQL
坑 9:跨库 Join 笛卡尔积灾难
现象:两张分片表关联查询,性能暴跌几十倍,数据库 CPU 直接打满。根因:两张表分片键不一致,无法在单库完成关联,中间件只能把两张表全量数据拉到内存做笛卡尔积关联,数据量大时直接 OOM。解决方案:
- 强关联表使用相同分片键,保证同分片数据在一个库,实现 “绑定表” 关联
- 小表 + 大表场景,小表做广播表,全量同步到所有分片
- 禁止跨库多表复杂关联,业务层拆分为单表查询后在内存组装
坑 10:使用数据库特有函数,路由失败
现象:业务 SQL 里用了金仓SYS_*函数、达梦DM_*内置函数,分片后要么报错,要么全库路由性能极差。根因:分片中间件无法识别国产数据库特有函数,无法判断是否包含分片键,只能全库执行。解决方案:
- 业务 SQL 全部使用标准 SQL 函数,禁止直接调用数据库特有函数
- 必须使用的场景,通过自定义函数下推规则配置,让中间件正确识别路由
- 复杂逻辑下沉到应用层处理,减少数据库依赖
坑 11:DDL 变更不同步
现象:加字段、加索引只改了部分分库,业务跑起来随机报错,排查半天发现是漏改了某个分表。根因:人工逐个分库执行 DDL,数量多了极易遗漏,也没有校验机制。解决方案:
- 使用 ShardingSphere 的分布式 DDL 能力,自动同步所有分片
- 自研 DDL 执行工具,批量执行并校验所有分库表结构一致性
- 变更前做表结构对比,所有分库结构完全一致再上线
🔑 三、主键与分片键类坑
主键全局唯一是分库分表的基础,很多项目在这里栽了大跟头。
坑 12:原生自增 / 序列导致主键冲突
现象:上线后陆续出现主键重复报错,数据插入失败。根因:每个分库的自增主键、序列都是独立计数的,默认从 1 开始,必然产生重复 ID。达梦序列默认开启缓存,事务回滚时序列值不回退,进一步加剧乱序风险。解决方案:
- 全局统一使用雪花算法(Snowflake)生成分布式 ID,完全不依赖数据库自增
- 必须用数据库序列的场景,每个分库设置不同起始值 + 步长,如 4 个库步长为 4,起始值分别 1/2/3/4
- 金仓可使用 ShardingSphere 内置的
KINGBASE8_SEQUENCE主键生成器,统一管理序列
坑 13:雪花算法时钟回拨,ID 重复
现象:服务器时间同步异常,时钟回拨,导致生成重复主键,数据插入失败。根因:雪花算法强依赖机器时钟,NTP 时间同步可能导致时间回退。解决方案:
- 引入时钟回拨检测,回拨时间短则等待,超过阈值则报错或切换备用工作节点
- 使用号段模式(Leaf)生成 ID,数据库号段分配,完全不依赖时钟
- 所有数据库与应用服务器统一 NTP 时钟源,关闭自动时间同步
坑 14:分片键允许更新,数据找不到
现象:修改了分片键字段值后,这条数据查不出来了,更新也失败。根因:分片键变更后,数据所在的分片变了,但物理记录还在原分库,路由规则找不到对应位置。解决方案:
- 分片键禁止更新,业务设计时就明确为不可变字段
- 必须更新的场景,采用「删除旧记录 + 插入新记录」的方式,保证数据路由到正确分片
- 关键操作增加数据校验,避免数据静默丢失
坑 15:全局唯一约束失效
现象:用户名、手机号等需要全局唯一的字段,出现重复数据。根因:分库分表后,数据库唯一索引只能保证单库唯一,无法跨分片校验唯一性。解决方案:
- 全局唯一字段单独建一张广播表,存储唯一值做校验
- 引入分布式锁,插入前先做唯一性校验
- 对一致性要求极高的字段,考虑不分片或单独设计路由规则
⚖️ 四、事务与数据一致性类坑
分库分表后,单机事务 ACID 被打破,一致性问题是最容易引发资损和业务故障的重灾区。
坑 16:@Transactional 失效,以为还是单库事务
现象:跨库操作报错了,前面库的数据没回滚,出现脏数据。根因:Spring 的@Transactional只管理单数据源事务,跨分片后就是多个独立事务,异常时不会自动全部回滚。解决方案:
- 尽量避免跨库事务,通过分片键设计让同一业务操作落在同一个分片
- 必须跨库的场景,根据一致性要求选择方案:
- 强一致:XA 事务(性能差,国产数据库需验证驱动兼容性)
- 最终一致:Seata AT/TCC、事务消息 + 补偿机制
坑 17:XA 事务各种异常,回滚失败
现象:用了 ShardingSphere 的 XA 事务,经常出现悬挂事务、提交失败、回滚不彻底,数据不一致。根因:国产数据库的 XA 驱动实现与开源中间件的默认适配存在差异,金仓 / 达梦的 XA 协议细节和 PostgreSQL/Oracle 不完全一致。解决方案:
- 核心系统谨慎使用 XA,优先通过业务设计避免跨库事务
- 使用前必须做完整的异常场景测试:宕机、网络中断、超时回滚等
- 配合数据库厂商优化驱动版本,修复已知 XA 兼容性问题
坑 18:Seata AT 模式建表失败,回滚日志不生效
现象:Seata 的 undo_log 表在国产库上建表失败,语法不兼容,事务回滚时直接报错。根因:Seata 默认的建表 SQL 是 MySQL 语法,金仓 / 达梦需要适配字段类型、自增语法等。解决方案:
- 根据国产数据库语法手动改写 undo_log 建表语句,适配数据类型与主键策略
- 验证回滚逻辑:正常提交、异常回滚、超时重试等场景全覆盖
- 达梦建议用 Oracle 模式兼容,金仓用 PG 模式兼容
坑 19:读写分离 + 分库分表叠加,读到脏数据
现象:刚写入的数据马上查不到,过几秒又出现了,业务逻辑错乱。根因:分库分表同时配置读写分离,写入主库后,主从同步有延迟,查询路由到备库就读不到最新数据。解决方案:
- 强一致性读强制走主库,通过 Hint 动态指定路由
- 配置主从延迟阈值,延迟过大时自动切回主库查询
- 金仓 / 达梦主备集群配置同步模式,保证提交时备库已回放
🔧 五、运维与扩容类坑
分库分表后,运维复杂度指数级上升,很多团队前期完全没考虑运维成本。
坑 20:扩容数据重分布,数据丢失 / 重复
现象:新增分片后,数据迁移过程中出现丢数据、重复数据,业务校验不通过。根因:
- 自研迁移脚本有 bug,边界处理不当
- 迁移过程中业务还在写入,数据变化没同步
- 国产数据库特有数据类型迁移时转换错误解决方案:
- 迁移前全量备份所有分片数据
- 采用「双写 + 迁移 + 校验」方案:先开启双写,再迁移历史数据,最后全量校验一致后切换路由
- 迁移工具提前做数据类型兼容性验证
坑 21:备份恢复困难,时间点对不上
现象:出问题需要回滚时,十几个分库备份时间点不一致,恢复后数据错乱。根因:每个分库独立备份,无法保证全局一致性时间点,恢复后跨库数据不一致。解决方案:
- 备份前先停写或做全局一致性快照
- 所有分库统一备份时间点,同步执行备份
- 关键系统搭建延迟从库,需要回滚时先切到延迟从库恢复
坑 22:监控缺失,排障全靠猜
现象:系统慢了不知道哪个库慢,报错了不知道路由到哪个分片,排查问题像盲人摸象。根因:只监控了整体数据库指标,没有分片维度的监控,也没有 SQL 路由链路追踪。解决方案:
- 监控覆盖每个分片的 CPU、连接数、慢 SQL、表空间使用率
- 接入链路追踪,打印每条 SQL 的实际路由节点、执行时间
- 慢 SQL 必须能定位到具体分库分表,针对性优化
🇨🇳 六、国产数据库专属坑(最容易忽略)
这些坑是国产数据库特有场景,照搬 MySQL 经验一定会翻车。
坑 23:大小写敏感导致路由错误
现象:分片键值大小写不一致时,路由到错误的分库,数据查不到。根因:达梦默认开启大小写敏感,金仓可配置,分片中间件按字符串哈希计算路由时,大小写不同会得到完全不同的哈希值。解决方案:
- 初始化实例时统一关闭大小写敏感(
CASE_SENSITIVE=0),政务项目推荐配置 - 分片键入库前统一转大写或小写,保证路由计算一致性
- 分片算法中增加大小写归一化逻辑
坑 24:国密透明加密 + 分片,扩容迁移失败
现象:每个分库的透明加密密钥独立,数据迁移到新分片后无法解密,数据乱码。根因:国产数据库透明加密按实例生成密钥,不同分片实例密钥不同,加密数据无法跨实例迁移。解决方案:
- 所有分片统一使用外部密钥管理系统(KMS),密钥全局一致
- 敏感字段加密在应用层做,不使用数据库透明加密
- 扩容迁移时先解密、再迁移、再加密,全程校验数据完整性
七、分库分表避坑黄金准则
7.1 设计三原则
- 能不分就不分:优先分区表、读写分离,分库分表是最后选项
- 分片键优先:所有设计围绕分片键展开,尽量让核心操作都落在单分片
- 复杂度可控:分片数量不宜过多,单库单表数量控制在合理范围
7.2 SQL 编写规范
- 所有查询尽量带上分片键,避免全库路由
- 禁止跨库复杂 Join、子查询、存储过程
- 使用标准 SQL,不依赖数据库特有语法
- 核心 SQL 必须做分片路由验证,确认执行计划符合预期
7.3 运维红线
- 所有 DDL 必须批量执行,全分片结构一致性校验通过才能上线
- 扩容必须有完整回滚方案,先演练再生产执行
- 监控必须覆盖到单分片维度,故障可快速定位
八、全文总结
分库分表从来不是性能银弹,而是用复杂度换扩展性的技术方案。在国产数据库生态尚不完善的阶段,盲目照搬 MySQL 生态的玩法,必然会踩进各种兼容性大坑。
比 “怎么分” 更重要的是 “要不要分”。前期做好评估,选对分片键,中间严控 SQL 质量,后期完善运维体系,才能真正发挥分库分表的价值,同时把风险控制在可接受范围内。希望这 24 个踩坑总结,能帮你在信创改造路上少走弯路。