分库分表
ShardingJDBC 水平分库 + 读写分离 分库分表核心配置
:::hide 核心是通过 sharding-jdbc.yml 配置 数据源、分片规则、表 / 库路由策略,以下是极简关键配置示例(以水平分表为例):
spring:
shardingsphere:
# 1. 配置数据源(分库0主从+分库1主从,共4个数据源)
datasource:
names: db0-master, db0-slave, db1-master, db1-slave
# 分库0-主库数据源
db0-master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://db0-master-IP:3306/db0?useSSL=false&serverTimezone=UTC
username: root
password: 123456
# 分库0-从库数据源
db0-slave:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://db0-slave-IP:3307/db0?useSSL=false&serverTimezone=UTC
username: root
password: 123456
# 分库1-主库数据源
db1-master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://db1-master-IP:3308/db1?useSSL=false&serverTimezone=UTC
username: root
password: 123456
# 分库1-从库数据源
db1-slave:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://db1-slave-IP:3309/db1?useSSL=false&serverTimezone=UTC
username: root
password: 123456
# 2. 配置读写分离规则(关联主从数据源)
rules:
readwrite-splitting:
data-sources:
# 分库0的读写分离配置(数据源组名:db0)
db0:
type: Static # 静态配置(固定主从数据源)
props:
write-data-source-name: db0-master # 写数据源(主库)
read-data-source-names: db0-slave # 读数据源(从库,多个用逗号分隔)
load-balancer-name: round_robin # 读负载均衡算法(轮询)
# 分库1的读写分离配置(数据源组名:db1)
db1:
type: Static
props:
write-data-source-name: db1-master
read-data-source-names: db1-slave
load-balancer-name: round_robin
# 读写分离负载均衡算法(轮询/随机)
load-balancers:
round_robin:
type: ROUND_ROBIN # 轮询算法(按顺序选择从库)
random:
type: RANDOM # 随机算法(可选)
# 3. 配置水平分库规则(关联读写分离的数据源组)
sharding:
tables:
t_order: # 逻辑表名
actual-data-nodes: ${shardingsphere.rules.readwrite-splitting.data-sources.*.props.write-data-source-name.split('-')[0]}${0..1}.t_order # 真实表:db0.t_order、db1.t_order(若分表可加表后缀)
database-strategy: # 分库策略
standard:
sharding-column: user_id # 分库字段(按用户ID取模)
sharding-algorithm-name: db_inline
# 分库算法
sharding-algorithms:
db_inline:
type: INLINE
props:
algorithm-expression: db${user_id % 2} # 分库逻辑:user_id%2=0→db0,=1→db1
# 4. 全局配置
props:
sql-show: true # 打印真实执行SQL(调试用)
readwrite-splitting.allow-read-write-same-data-source: false # 禁止读写同一数据源(避免主从延迟问题)
:::
冷热分离实现方案
核心思路:按时间字段路由,将 “热数据”(近 3 个月)存普通表,“冷数据”(超 3 个月)存低成本存储(如 MySQL 归档表、OSS),通过 ShardingJDBC 路由规则隔离。 表设计:逻辑表 t_order,真实表 t_order_hot(热数据)、t_order_cold(冷数据); 路由规则:以 create_time 为分片字段,配置 RANGE 算法,判断时间落在热 / 冷表区间; 冷数据优化:可定期通过定时任务(如 XXL-Job)将 t_order_hot 中过期数据迁移至 t_order_cold,并对冷表做索引删除、表压缩。
分库分表查询应用层合并效率慢的解决方案
核心是 “减少合并数据量”+“异步并行查询”+“引入中间件加速”,分 3 步优化: 优化分片查询: 避免全表扫描:查询必须携带分片字段(如 user_id),精准路由到目标库表,减少查询节点; 分页优化:使用 LIMIT OFFSET 时,通过分片字段计算偏移量,避免各节点返回大量数据后合并; 应用层优化: 异步并行查询:用线程池(如 Spring Async)并行查询多个分片节点,而非串行,缩短总耗时; 结果缓存:将高频查询结果存入 Redis,有效期内直接返回,避免重复合并; 引入中间件:对复杂聚合查询(如统计、排序),直接用 ES 替代 “分库分表查询 + 应用层合并”,从源头解决效率问题。
ES 解决分库分表查询效率问题的具体方案
核心定位:ES 作为查询层,存储分库分表的 “查询镜像数据”,承接复杂查询;MySQL 作为存储层,保证数据一致性,方案分 4 步:
- 架构设计 存储层:MySQL 分库分表,存储完整业务数据(核心交易数据); 查询层:ES 集群,存储 MySQL 数据的 “查询友好型镜像”(仅保留查询所需字段,如订单号、用户 ID、金额、时间); 同步层:负责 MySQL 数据实时同步到 ES(下文第五点详细说明); 应用层:简单查询(如单订单查询)直接查 MySQL,复杂查询(如多条件筛选、排序、统计)查 ES。
- ES 索引设计(以订单为例) 索引映射(mapping) PUT /t_order_index { "settings": { "number_of_shards": 3, # 分片数(建议与MySQL分库数匹配) "number_of_replicas": 1 # 副本数(保证高可用) }, "mappings": { "properties": { "order_id": { "type": "keyword" }, # 订单号(不分词,精准查询) "user_id": { "type": "keyword" }, # 用户ID(分片字段关联) "order_amount": { "type": "double" },# 订单金额(聚合用) "create_time": { "type": "date" }, # 创建时间(范围查询用) "order_status": { "type": "integer" }# 订单状态(筛选用) } } }
- 查询流程 应用发起复杂查询(如 “查询用户 123 近 6 个月已支付订单,按金额降序”); 直接调用 ES API 查询 t_order_index,利用 ES 分片并行查询和倒排索引,快速返回结果; 若需获取订单详情(如商品列表),再根据 ES 返回的 order_id 精准查询 MySQL 分库分表(单条 / 少量查询效率极高)。
保证 MySQL 与 ES 数据一致性的方案
核心是 “实时同步为主,补偿机制为辅”,推荐 3 种主流方案(按可靠性优先级排序): 方案 1:Binlog + Canal(推荐,实时性高) 原理:Canal 模拟 MySQL 从库,监听 MySQL Binlog 日志; 流程: MySQL 开启 Binlog(row 模式,保证数据细节完整); Canal 读取 Binlog,解析出新增 / 修改 / 删除操作; Canal 将解析后的数据同步到 ES(可通过消息队列如 RocketMQ 缓冲,避免峰值压垮 ES); 一致性保障: 异步确保:通过消息队列的 “ack 机制”,确保消息不丢失; 最终一致:定时任务(每小时)对比 MySQL 与 ES 数据差异,修复不一致数据。 方案 2:业务代码双写(简单,适合中小项目) 流程:应用执行 MySQL 操作后,同步调用 ES API 执行对应操作(如 MySQL 插入订单→ES 插入文档); 一致性保障: 事务控制:用本地事务确保 MySQL 和 ES 操作要么都成功,要么都失败(避免单端成功); 失败重试:ES 操作失败时,存入重试队列,后台线程定时重试。 方案 3:Logstash 同步(适合非核心查询场景) 原理:Logstash 通过 JDBC 连接 MySQL,定期拉取增量数据(按 update_time 筛选),同步到 ES; 特点:配置简单,无需修改业务代码,但实时性差(延迟分钟级),适合对一致性要求不高的统计场景。
ShardingJDBC 与 MyCat 的核心区别是什么?各自适用场景?
区别:ShardingJDBC 是 “轻量级组件”(无独立进程,嵌入应用),MyCat 是 “中间件服务器”(独立部署,应用通过 JDBC 连接); 场景:ShardingJDBC 适合中小项目(部署简单、无额外运维),MyCat 适合大型分布式项目(支持读写分离、分库分表更全面,可独立扩容)。
ShardingJDBC 与 MyCat 的核心区别是什么?各自适用场景?
区别:ShardingJDBC 是 “轻量级组件”(无独立进程,嵌入应用),MyCat 是 “中间件服务器”(独立部署,应用通过 JDBC 连接); 场景:ShardingJDBC 适合中小项目(部署简单、无额外运维),MyCat 适合大型分布式项目(支持读写分离、分库分表更全面,可独立扩容)。
分库分表时,为什么不建议用自增 ID 作为主键?如何解决分布式 ID 问题?
原因:自增 ID 无法跨库表保证唯一性,且无法作为分片字段(无业务含义); 解决方案:① 雪花算法(Snowflake)生成 64 位 ID;② 基于 Redis 自增(INCR 命令);③ ShardingJDBC 内置分布式 ID 生成器(如 SNOWFLAKE 算法)。
ShardingJDBC 实现冷热分离后,如何避免冷数据查询拖慢整体性能?
① 路由隔离:通过分片规则确保热数据查询不涉及冷表,冷数据查询单独路由; ② 冷表优化:对冷表删除无用索引、开启 MySQL 表压缩,或迁移至低成本存储(如 OSS),查询时通过专用数据源连接; ③ 异步查询:冷数据查询用异步线程执行,不阻塞热数据业务流程。
分库分表中,“跨分片分页查询” 效率低的根源是什么?如何优化?
根源:传统分页(LIMIT 10000, 10)会导致所有分片返回前 10010 条数据,应用层合并后再取最后 10 条,数据传输量大; 优化方案:① 基于分片字段分页:用 “分片字段> 上一页最大值” 替代 OFFSET,精准过滤数据;② 借助 ES:跨分片分页查询直接查 ES,避免应用层合并。
为什么说 ES 适合承接分库分表的复杂查询?ES 的查询效率优势体现在哪里?
核心原因:分库分表的复杂查询(多条件、排序、聚合)需跨分片合并,ES 天然支持分布式分片查询,且有倒排索引; 效率优势:① 倒排索引:基于字段关键词快速定位文档,无需全表扫描;② 分片并行查询:ES 索引分片与 MySQL 分库表对应,并行查询后汇总,耗时更短;③ 聚合优化:内置高效聚合算法,支持统计、排序等操作。
Canal 同步 MySQL 数据到 ES 时,如何处理 “Binlog 丢失” 或 “ES 写入失败” 的问题?
① Binlog 丢失:MySQL 开启 Binlog 日志归档,Canal 记录消费位点,若位点丢失,通过归档日志恢复并重放; ② ES 写入失败:引入消息队列(RocketMQ/Kafka),Canal 将数据写入队列,ES 消费队列数据,失败时触发重试(设置重试次数和死信队列); ③ 最终一致性:定时任务对比 MySQL 与 ES 数据(按 update_time 或主键),差异数据批量同步修复。
业务代码双写 MySQL 和 ES 时,如何避免 “MySQL 成功但 ES 失败” 导致的数据不一致?
方案 1:本地事务 + 重试队列:① 用 Spring 事务确保 MySQL 操作成功;② ES 操作失败时,将数据存入本地重试队列;③ 后台线程定时重试,直至 ES 写入成功; 方案 2:分布式事务(适合核心业务):用 Seata 等框架,将 MySQL 和 ES 操作纳入分布式事务,确保要么都成功,要么都回滚。
分库分表 + ES 架构中,如何设计 “读写分离”?确保查询效率的同时不影响写入性能?
① MySQL 读写分离:主库负责写入,从库负责简单查询(如单订单详情),ShardingJDBC 配置读写分离规则; ② ES 读写分离:ES 索引设置 “主分片”(负责写入)和 “副本分片”(负责查询),应用查询时仅访问副本分片,避免写入操作占用查询资源; ③ 写入分流:MySQL 写入后,通过 Canal 异步同步到 ES,不阻塞业务写入流程。
当 ES 集群故障时,如何保证业务查询不中断?有哪些降级方案?
① 降级方案 1:切换查询源:ES 故障时,通过配置中心切换查询到 MySQL 分库分表(仅开放核心简单查询,关闭复杂查询); ② 降级方案 2:缓存兜底:将高频查询结果存入 Redis,ES 故障时直接返回 Redis 缓存数据(设置合理缓存有效期); ③ 高可用保障:ES 集群部署至少 3 个节点,设置副本分片,避免单点故障,减少降级触发概率。
ShardingJDBC 如何同时处理分库和读写分离?路由顺序是什么?
路由顺序:先执行 “分库路由”(根据分片字段确定目标数据源组,如 db0),再执行 “读写分离路由”(写→主库,读→从库); 核心逻辑:分库规则关联的是 “数据源组”(而非单个数据源),每个数据源组包含主从节点,最终由读写分离规则决定具体访问哪个节点。
主从延迟导致读从库获取不到最新数据,如何解决?
① 强制路由主库:对实时性要求高的查询(如刚创建订单后查询详情),用 ShardingJDBC Hint 强制路由主库: java 运行 // 代码中添加Hint注解(或编程式Hint) HintManager.getInstance().setWriteRouteOnly(true); // 强制走写库(主库) ② 优化主从同步:MySQL 开启并行复制、减少大事务,降低主从延迟(控制在 100ms 内); ③ 读写分离策略调整:配置 readwrite-splitting.delay-threshold(延迟阈值),超过阈值自动路由主库。
如果某个分库的从库挂了,ShardingJDBC 会如何处理?
ShardingJDBC 会自动检测从库状态(通过心跳检测),若从库不可用,会将其从读数据源列表中剔除; 所有读操作会路由到该分库的其他可用从库(若有多个从库),若仅一个从库挂了,则读操作暂时路由到主库,直到从库恢复。
水平分库 + 读写分离场景下,分布式事务如何保证?
推荐用 Seata 分布式事务(AT 模式),ShardingJDBC 与 Seata 无缝集成; 核心逻辑:Seata 协调各分库的主库执行事务,确保所有分库的主库要么都提交,要么都回滚;从库通过主从同步获取一致数据。
生产环境中,ShardingJDBC 配置有哪些避坑点?
① 数据源名统一规范:主从数据源名建议包含 “master/slave”,方便配置关联; ② 关闭读写同一数据源:避免因主从延迟导致的脏读; ③ 分库字段必带:查询时必须携带分库字段(如 user_id),避免全库扫描(ShardingJDBC 会拒绝无分片字段的全库查询,需手动开启允许); ④ 主从同步模式:Binlog 必须用 ROW 模式,否则可能导致同步数据不一致(尤其是分库分表 + ES 同步场景)。