1. 场景:分库分表的"至暗时刻"
2025 年年中,我负责的电商平台订单系统已经到了不得不分库分表的地步——订单表突破 5000 万行,单表查询耗时飙到 3 秒,慢 SQL 报警日均 200+ 条。我们选择了 ShardingSphere-JDBC 做分库分表,4 个分库 × 16 张分表,上线后查询性能确实改善了。
但噩梦才刚开始:分片键选了 user_id,按卖家查订单走全分片扫描;跨分片 Join 查询性能暴跌;618 大促前紧急扩容,数据迁移停机 4 小时;分布式事务用 BASE 模式偶尔丢数据;每次分片规则变更都要研发 + DBA 联合作战。
故障数据回顾:

| 指标 | 分库分表前 | ShardingSphere 分库分表后 | PolarDB-X 迁移后 |
|---|---|---|---|
| 单表查询耗时 | 3000ms | 200ms(含分片键) | 80ms |
| 跨分片查询耗时 | - | 5000ms | 300ms |
| 扩容停机时间 | - | 4 小时 | 0(在线扩容) |
| 分布式事务一致性 | - | 最终一致(偶丢) | 强一致(RC/RR) |
| 运维人力投入 | 0.5 DBA | 2 DBA | 0(全托管) |
这次事故后,我们调研并最终迁移到阿里云 PolarDB-X。迁移效果立竿见影——透明分布式让应用零改造获得分片能力,全局索引解决跨分片查询,分布式事务强一致保障数据安全,在线扩容告别停机噩梦。
下面从分库分表困境出发,逐步拆解 PolarDB-X 的架构原理和实战要点。
2. 分库分表困境:5 大痛点
很多团队对分库分表的理解停留在"把大表拆成小表就行了",但实际落地后会发现,分库分表解决的只是"存得下"的问题,却引入了一系列更棘手的问题。
痛点 1:分片键选择——选错一步,满盘皆输
分片键决定了数据的物理分布,直接影响查询性能。选 user_id 做分片键,按用户维度查询飞快,但按卖家维度查询走全分片扫描;选 order_id 做分片键,订单查询快了,但用户维度的聚合查询又崩了。
更糟糕的是,一旦分片键选错,修改意味着全量数据重分布——停机 + 高风险。
痛点 2:跨分片查询——性能黑洞
带分片键的查询可以精确路由到单个分片,但跨分片的查询(如 SELECT * FROM t_order WHERE seller_id = ?)需要扫描所有分片再做结果归并。4 库 16 表意味着一次查询要发起 16 次子查询,性能可想而知。
跨分片 Join 更是灾难——两个大表的 Join 需要在内存中做笛卡尔积,数据量稍大就 OOM。
痛点 3:扩容迁移——停机的恐惧
ShardingSphere 扩容需要增加分片数,但分片数变更意味着数据要重新分布。以我们 5000 万订单为例,从 4 库扩到 8 库,全量数据迁移耗时 4 小时,期间服务不可用。大促前扩容停机 4 小时,这个代价谁也承受不起。
痛点 4:分布式事务——一致性焦虑
ShardingSphere 提供了 XA 和 BASE 两种分布式事务模式。XA 模式性能差,锁持有时间长,高并发下死锁频发;BASE 模式性能好,但只有最终一致性,我们遇到过事务回滚失败导致数据不一致的故障。
对于金融级业务(账户余额、支付流水),最终一致是不可接受的。
痛点 5:运维复杂——DBA 的噩梦
分库分表后,原来一张表变成 64 张表(4 库 × 16 表),DDL 变更要逐库逐表执行,数据备份要协调 4 个数据库实例,监控要聚合 4 个库的指标。我们 2 个 DBA 几乎 50% 的时间在处理分库分表相关的运维问题。
一句话总结:分库分表是"饮鸩止渴"——解决了单表性能问题,却引入了更大的架构复杂度。
3. PolarDB-X 架构解析
理解 PolarDB-X 如何解决上述痛点,需要先理解它的架构设计。PolarDB-X 2.0 是一个端到端的一体化分布式数据库,而非分库分表中间件。
3.1 整体架构

3.2 四大核心组件
计算节点(CN):SQL 引擎层,负责 SQL 解析、优化、执行和分布式路由。CN 是无状态设计,可以根据负载弹性扩缩容。在分布式事务中,CN 作为 2PC 协调者,负责 Prepare → 获取 TSO → 持久事务日志 → Commit 的全流程协调。同时,CN 维护全局二级索引(GSI),并支持将 Join/Agg/Filter 算子下推到 DN 执行,减少跨节点数据传输。
存储节点(DN):数据持久化层,基于 X-Paxos 多数派共识协议提供金融级高可用(RPO=0)。每个 DN 采用 MVCC 多版本存储,支持计算下推——Project/Filter/Join/Agg 等算子直接在存储层执行,大幅减少网络开销。
全局元数据服务(GMS):分布式大脑,维护全局强一致的 Table/Schema、统计信息、账号权限等元数据。GMS 通过 Timestamp Oracle(TSO)提供全局单调递增的时间戳,这是分布式事务一致性和一致性快照读的基础设施。
日志节点(CDC):生产完全兼容 MySQL Binlog 格式和协议的增量日志。Canal、Debezium 等生态工具可以无缝对接,无需任何改造。
3.3 和分库分表中间件的本质区别
| 对比维度 | ShardingSphere(中间件) | PolarDB-X(原生分布式) |
|---|---|---|
| 架构模式 | 在应用和 MySQL 之间加代理层 | 端到端一体化数据库 |
| 存储层 | 依赖外部 MySQL 实例 | 内置 DN(定制 MySQL + Paxos) |
| 事务模型 | XA(性能差)/ BASE(弱一致) | TSO + MVCC + 2PC(强一致) |
| 全局索引 | 不支持 | 支持 GSI 和聚簇索引 |
| 扩容方式 | 手动数据迁移 + 停机 | 在线自动重分布 |
| 元数据管理 | 依赖 ZooKeeper/Etcd | 内置 GMS + TSO |
一句话总结:分库分表中间件是在 MySQL 上"打补丁",PolarDB-X 是从底层重新设计的分布式数据库。
4. 集群搭建
4.1 实例创建与规格选择
PolarDB-X 提供标准版和企业版两种规格:
| 实例类型 | 架构 | 适用场景 | 节点规格 |
|---|---|---|---|
| 标准版 | 一主一备一日志,Paxos 三副本 | 中小业务、开发测试 | 2C4G 起 |
| 企业版 | 计算存储分离分布式集群 | 超高并发、海量数据 | 4C16G 起 |
我们的选型:企业版,2 个 CN 节点(8C32G)+ 2 个 DN 节点。原因:订单系统日均 50 万订单,峰值 QPS 2 万+,4C16G 不够用。
# 创建 PolarDB-X 企业版实例(阿里云 CLI)
aliyun polardbx CreateDBInstance \
--RegionId cn-hangzhou \
--DBInstanceMode enterprise \
--DBNodeCount 2 \
--DBInstanceClass polarx.x8.2xlarge.2.e \
--Engine polardbx \
--EngineVersion 2.0 \
--PayType Prepaid \
--Period 1
4.2 数据库创建:AUTO 模式 vs DRDS 模式
PolarDB-X 提供两种数据库模式,这是关键选型决策点:
| 对比项 | AUTO 模式 | DRDS 模式 |
|---|---|---|
| 自动分区 | 支持(建表无需指定分片键) | 不支持(必须手动指定) |
| 建表语法 | 标准 MySQL 语法 | DRDS 专用分库分表语法 |
| 透明分布式 | 支持 | 不支持 |
| 在线分区变更 | 支持 | 有限支持 |
推荐选择 AUTO 模式——新业务首选,无需关心分片键,用标准 MySQL 语法建表即可享受分布式能力。
-- 创建 AUTO 模式数据库(关键:MODE='AUTO')
CREATE DATABASE order_db MODE='AUTO';
-- 如果不指定 MODE,默认创建 DRDS 模式
-- AUTO 模式和 DRDS 模式可在同一实例中共存
4.3 表组规划
PolarDB-X 引入了 Table Group(表组)的概念——将频繁 Join 的表放在同一个表组中,保证它们的分区键和分区数一致,Join 时可以直接在本地完成,避免跨节点 Shuffle。
-- 创建表组(将订单表和订单详情表放在同一表组)
CREATE TABLEGROUP tg_order PARTITIONS 16;
-- 创建订单表并指定表组
CREATE TABLE t_order (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
seller_id BIGINT NOT NULL,
order_status TINYINT,
total_amount DECIMAL(12,2),
created_at DATETIME,
UNIQUE GLOBAL INDEX idx_order_id(order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY KEY(user_id)
PARTITIONS 16
TABLEGROUP tg_order;
-- 创建订单详情表并指定同一表组
CREATE TABLE t_order_item (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id VARCHAR(64) NOT NULL,
user_id BIGINT NOT NULL,
product_id BIGINT NOT NULL,
quantity INT,
price DECIMAL(12,2),
GLOBAL INDEX idx_order_id(order_id) COVERING(product_id, quantity, price)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY KEY(user_id)
PARTITIONS 16
TABLEGROUP tg_order;
要点:
t_order和t_order_item使用相同的分区键(user_id)和分区数(16),放在同一表组tg_order中,确保按user_idJoin 时数据在同一 DN 节点上,避免跨节点数据传输。
5. 核心实战
5.1 分库分表策略
PolarDB-X 支持四种分片策略,覆盖主流业务场景:
Hash 分片——均衡分布的首选
-- 按用户 ID Hash 分片,数据均匀分布
CREATE TABLE t_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(64),
phone VARCHAR(20),
created_at DATETIME
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY KEY(id)
PARTITIONS 16;
适用场景:用户表、订单表等数据量大、需要均匀分布的场景。Hash 分片保证数据均匀分布,避免热点。
Range 分片——时序数据的利器
-- 按日期 Range 分片,方便按时间范围查询和过期清理
CREATE TABLE t_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
log_time DATETIME NOT NULL,
level VARCHAR(10),
content TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE COLUMNS(log_time) (
PARTITION p202501 VALUES LESS THAN('2025-02-01'),
PARTITION p202502 VALUES LESS THAN('2025-03-01'),
PARTITION p202503 VALUES LESS THAN('2025-04-01'),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
适用场景:日志表、流水表等按时间递增的数据,方便按时间范围查询和过期数据清理。
List 分片——地域隔离的方案
-- 按地域 List 分片,实现数据地域隔离
CREATE TABLE t_merchant (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
merchant_name VARCHAR(128),
region VARCHAR(20),
status TINYINT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY LIST COLUMNS(region) (
PARTITION p_east VALUES IN ('shanghai', 'hangzhou', 'nanjing'),
PARTITION p_south VALUES IN ('shenzhen', 'guangzhou', 'fuzhou'),
PARTITION p_north VALUES IN ('beijing', 'tianjin', 'dalian'),
PARTITION p_west VALUES IN ('chengdu', 'chongqing', 'xian')
);
适用场景:需要按地域做数据隔离和合规存储的场景。
自动分片——零改造的透明分布式
AUTO 模式数据库的杀手级特性——建表时无需指定任何分区定义,PolarDB-X 自动选择分区键和分区策略:
-- AUTO 模式下,标准 MySQL 建表语法即可,自动按主键分片
CREATE TABLE t_product (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
product_name VARCHAR(128),
category_id INT,
price DECIMAL(12,2),
stock INT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 查看实际建表语句,PolarDB-X 自动添加了分区定义
SHOW FULL CREATE TABLE t_product;
-- 输出中会包含: PARTITION BY KEY(`id`) PARTITIONS 16
自动分片规则:整型/字符串主键 → Hash 分片;日期型主键 → YYYYDD 分片;无主键 → 隐式自增主键 Hash 分片。
5.2 分布式 SQL:全局索引 + Join 优化
全局索引——跨分片查询的救星
分库分表最大的痛点是跨分片查询。PolarDB-X 的全局二级索引(GSI)完美解决了这个问题:
-- 订单表按 user_id 分片,但经常按 order_id 查询
-- 创建全局索引,按 order_id 独立分片
ALTER TABLE t_order ADD UNIQUE GLOBAL INDEX idx_order_id(order_id);
-- 按卖家查订单:无全局索引 → 全分片扫描
-- 创建全局索引后 → 精确路由到目标分片
ALTER TABLE t_order ADD GLOBAL INDEX idx_seller_id(seller_id) COVERING(order_status, total_amount);
GSI 和局部索引的区别:
| 索引类型 | 存储位置 | 查询范围 | 写入开销 | 适用场景 |
|---|---|---|---|---|
| 局部索引(LOCAL) | 同一分片内 | 仅本分片 | 低 | 分片键上的查询 |
| 全局索引(GLOBAL) | 独立分片 | 跨分片 | 高(需同步) | 非分片键上的查询 |
| 聚簇索引(CLUSTERED) | 独立分片,含全行数据 | 跨分片 | 最高 | 覆盖索引查询 |
Covering Index 技巧:为 GSI 指定 COVERING 列,查询可以直接从索引表返回结果,避免回表查询主表,性能提升 3-5 倍。
Join 优化——表组 + 下推 + BKA Join
-- 同表组内的 Join:本地 Join,零网络开销
SELECT o.order_id, i.product_id, i.quantity
FROM t_order o
JOIN t_order_item i ON o.user_id = i.user_id AND o.order_id = i.order_id
WHERE o.user_id = 123456;
-- 跨表组 Join:BKA Join(Batch Key Access)优化
-- PolarDB-X 优化器自动选择最优 Join 策略
SELECT o.order_id, p.product_name
FROM t_order o
JOIN t_product p ON o.product_id = p.id
WHERE o.user_id = 123456;
Join 策略选择:

5.3 分布式事务:XA + 2PC + Paxos 强一致
PolarDB-X 原生支持 ACID 分布式事务,基于 TSO + MVCC + 2PC 协议实现强一致性。
事务提交流程

关键优化
一阶段提交优化:如果事务只涉及单个分区,PolarDB-X 自动使用一阶段提交,跳过 2PC 的 Prepare 阶段,减少 1 次 RTT 和 1 次持久化。
异步提交优化:对于多分区事务,PolarDB-X 支持在事务日志持久化后立即返回客户端成功,Commit 阶段异步执行。客户端等待时间从 4 次 RTT 减少到 3 次 RTT。
只读事务优化:只读事务无需 2PC,通过 TSO 获取快照时间戳,基于 MVCC 可见性判断直接读取,性能接近单机数据库。
-- PolarDB-X 分布式事务使用和单机 MySQL 完全一致
BEGIN;
UPDATE account SET balance = balance - 100 WHERE user_id = 1;
UPDATE account SET balance = balance + 100 WHERE user_id = 2;
COMMIT;
-- 支持的隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- RC
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- RR(默认)
5.4 读写分离:RO 节点 + Hint 路由
PolarDB-X 提供透明读写分离能力,应用无需改造代码即可实现读写分流。
透明读写分离
购买只读实例后,在控制台配置集群地址即可开启。支持两种路由模式:
智能路由:优化器基于代价自动识别 TP/AP 负载,将分析型查询路由到只读实例。可通过 explain cost 查看识别结果。
权重路由:通过 MASTER_READ_WEIGHT 参数配置读写比例。默认值 100(全部读主),设为 60 表示 60% 读流量留在主实例,40% 路由到只读实例。
-- 查看查询的工作负载类型
EXPLAIN COST SELECT COUNT(*) FROM t_order WHERE created_at > '2025-01-01';
-- 输出中 WorkloadType: TP 或 AP
-- 强制指定 AP 负载,路由到只读实例
/*+TDDL:WORKLOAD_TYPE=AP*/ SELECT COUNT(*) FROM t_order WHERE created_at > '2025-01-01';
-- 强制指定 TP 负载,路由到主实例
/*+TDDL:WORKLOAD_TYPE=TP*/ SELECT * FROM t_order WHERE user_id = 123456;
Hint 路由精细控制
-- 强制读主库(写后读一致性场景)
/*+TDDL:MASTER*/ SELECT * FROM t_order WHERE order_id = 'ORD2025001';
-- 指定只读实例 ID
/*+TDDL:NODE='pxc-xxxx-ro-1'*/ SELECT * FROM t_order WHERE user_id = 123456;
-- 路由到列存只读实例(AP 场景)
/*+TDDL:WORKLOAD_TYPE=AP ENABLE_COLUMNAR_OPTIMIZER=true*/
SELECT region, COUNT(*), SUM(total_amount) FROM t_order GROUP BY region;
5.5 DDL 变更:OnlineDDL + Schema 演进
分库分表最头疼的运维问题之一就是 DDL 变更。PolarDB-X 支持 OnlineDDL,不锁表、不阻塞 DML。
在线分区变更
-- 单表变分区表(不锁表,不阻塞 DML)
ALTER TABLE t_report PARTITION BY KEY(user_id) PARTITIONS 16;
-- 修改分区键(数据在线重分布)
ALTER TABLE t_order PARTITION BY KEY(order_id) PARTITIONS 32;
-- 增加分区
ALTER TABLE t_log ADD PARTITION (PARTITION p202504 VALUES LESS THAN('2025-05-01'));
原理:OnlineDDL 本质是在线进行全量数据迁移和重分布。PolarDB-X 在后台创建新分区的影子表,将数据异步迁移过去,期间原表正常服务。迁移完成后,原子切换到新分区。
全局索引在线变更
-- 在线添加全局索引
ALTER TABLE t_order ADD GLOBAL INDEX idx_created_at(created_at) COVERING(order_status, total_amount);
-- 在线删除全局索引
ALTER TABLE t_order DROP INDEX idx_created_at;
注意事项:OnlineDDL 是重型操作,会消耗大量 CPU/IO/网络资源,建议在业务低峰期执行,并通过 SHOW DDL 监控进度。
6. Spring Boot 集成
PolarDB-X 高度兼容 MySQL 协议,Spring Boot 应用只需改连接串即可接入,代码零改造。
6.1 连接配置
# application.yml
spring:
datasource:
url: jdbc:mysql://pxc-xxxx.polarx.polardbx.rds.aliyuncs.com:3306/order_db?useSSL=true&useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai
username: app_user
password: ${
DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 40
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
connection-test-query: SELECT 1
要点:PolarDB-X 的连接串格式和 MySQL 完全一致,使用 MySQL JDBC 驱动即可。HikariCP 连接池推荐最大连接数 = (CPU 核数 × 2) + 磁盘数,对于 8C 的 CN 节点,建议 20-40 个连接。
6.2 分片路由 + Hint 透传
PolarDB-X 的 Hint 使用 /*+TDDL:...*/ 格式,通过 SQL 注释传递路由指令,不改变 SQL 语义:
@Service
public class OrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 普通查询:自动路由
public Order getByUserId(Long userId, Long orderId) {
String sql = "SELECT * FROM t_order WHERE user_id = ? AND order_id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Order.class), userId, orderId);
}
// 强制读主库:写后读一致性
public Order getByUserIdForceMaster(Long userId, Long orderId) {
String sql = "/*+TDDL:MASTER*/ SELECT * FROM t_order WHERE user_id = ? AND order_id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Order.class), userId, orderId);
}
// AP 查询路由到只读实例
public List<Map<String, Object>> getOrderStats(String startDate) {
String sql = "/*+TDDL:WORKLOAD_TYPE=AP*/ SELECT DATE(created_at) AS dt, COUNT(*) AS cnt, SUM(total_amount) AS amount FROM t_order WHERE created_at >= ? GROUP BY DATE(created_at) ORDER BY dt";
return jdbcTemplate.queryForList(sql, startDate);
}
}
6.3 事务控制
PolarDB-X 分布式事务和 Spring 事务管理器无缝集成:
@Service
public class PaymentService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 分布式事务:跨分片转账,自动 2PC
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer(Long fromUser, Long toUser, BigDecimal amount) {
// 扣减转出方余额(可能在 DN-1)
jdbcTemplate.update(
"UPDATE account SET balance = balance - ? WHERE user_id = ?",
amount, fromUser
);
// 增加转入方余额(可能在 DN-2)
jdbcTemplate.update(
"UPDATE account SET balance = balance + ? WHERE user_id = ?",
amount, toUser
);
// PolarDB-X 自动协调 2PC,保证强一致
}
// 单分片事务:自动优化为一阶段提交
@Transactional
public void createOrder(Order order) {
// 同一 user_id 的订单和明细在同一分片,一阶段提交
jdbcTemplate.update(
"INSERT INTO t_order(user_id, order_id, seller_id, total_amount, created_at) VALUES(?,?,?,?,NOW())",
order.getUserId(), order.getOrderId(), order.getSellerId(), order.getTotalAmount()
);
for (OrderItem item : order.getItems()) {
jdbcTemplate.update(
"INSERT INTO t_order_item(order_id, user_id, product_id, quantity, price) VALUES(?,?,?,?,?)",
order.getOrderId(), order.getUserId(), item.getProductId(), item.getQuantity(), item.getPrice()
);
}
}
}
6.4 Hint 上下文透传
在微服务架构中,Hint 需要跨线程池透传。推荐使用 ThreadLocal + AOP 封装:
// Hint 上下文持有者
public class PolarDBXHintContext {
private static final ThreadLocal<String> HINT_HOLDER = new ThreadLocal<>();
public static void setHint(String hint) {
HINT_HOLDER.set(hint);
}
public static String getHint() {
return HINT_HOLDER.get();
}
public static void clear() {
HINT_HOLDER.remove();
}
}
// AOP 切面自动注入 Hint
@Aspect
@Component
public class PolarDBXHintAspect {
@Around("@annotation(forceMaster)")
public Object aroundForceMaster(ProceedingJoinPoint pjp, ForceMaster forceMaster) throws Throwable {
try {
PolarDBXHintContext.setHint("/*+TDDL:MASTER*/");
return pjp.proceed();
} finally {
PolarDBXHintContext.clear();
}
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ForceMaster {
}
// 业务使用
@Service
public class OrderQueryService {
@Autowired
private OrderMapper orderMapper;
@ForceMaster
public Order getOrderForceMaster(String orderId) {
// 自动注入 /*+TDDL:MASTER*/ Hint
return orderMapper.selectByOrderId(orderId);
}
}
7. 从 ShardingSphere 迁移
7.1 迁移方案对比
| 方案 | 原理 | 停机时间 | 数据一致性 | 适用场景 |
|---|---|---|---|---|
| DTS 全量 + 增量 | 阿里云数据传输服务同步 | 零停机 | 强一致 | 生产环境首选 |
| DataX 全量导出导入 | 批量数据导出导入 | 分钟级 | 快照一致 | 可接受短暂停机 |
| MySQL Dump + 恢复 | mysqldump 导入 PolarDB-X | 小时级 | 快照一致 | 数据量小的场景 |
推荐 DTS 方案:零停机、增量同步延迟低(<1s)、数据校验完善。
7.2 迁移架构

7.3 数据迁移操作
# 1. 创建 DTS 同步任务:ShardingSphere 后端 MySQL → PolarDB-X
aliyun dts CreateSynchronizationJob \
--SourceEndpoint.InstanceType RDS \
--SourceEndpoint.InstanceID rm-xxxx \
--DestinationEndpoint.InstanceType PolarDBX \
--DestinationEndpoint.InstanceID pxc-xxxx \
--SynchronizationObjects '[{"DBName":"order_db"}]'
# 2. 等待全量同步完成 + 增量追赶
# 3. 数据校验
aliyun dts DescribeDataCheckResult --SynchronizationJobId dts-xxxx
7.4 应用改造
从 ShardingSphere 迁移到 PolarDB-X,应用改造量很小:
| 改造项 | ShardingSphere | PolarDB-X | 改造内容 |
|---|---|---|---|
| 数据源配置 | ShardingSphereDataSource | HikariCP + MySQL Driver | 移除 ShardingSphere 依赖,改连接串 |
| 分片配置 | sharding-jdbc.yml | PolarDB-X 自动管理 | 删除分片配置文件 |
| 分布式事务 | @ShardingTransactionType | Spring @Transactional | 移除 ShardingSphere 事务注解 |
| Hint 路由 | HintManager.getInstance() | /+TDDL:.../ | 改 Hint 格式 |
| SQL 兼容 | 部分跨分片 SQL 不支持 | 完全兼容 MySQL | 移除 SQL 限制 |
<!-- pom.xml 改造:移除 ShardingSphere 依赖 -->
<!-- 删除 -->
<!-- <dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc</artifactId>
</dependency> -->
<!-- 保留 MySQL 驱动即可 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
7.5 灰度切换
- 双写验证:先开启应用双写(ShardingSphere + PolarDB-X),对比两边数据一致性
- 读流量灰度:将 10% → 30% → 50% → 100% 的读流量切到 PolarDB-X
- 写流量切换:确认读流量无异常后,一次性切换写流量
- 保留 ShardingSphere 只读 7 天,确认无误后下线
8. 量化对比:ShardingSphere vs PolarDB-X
基于我们订单系统的实测数据(4 节点集群,订单表 5000 万行):
| 对比维度 | ShardingSphere | PolarDB-X | 优势方 |
|---|---|---|---|
| 点查 QPS(含分片键) | 85,000 | 120,000 | PolarDB-X(+41%) |
| 写入 QPS | 32,000 | 45,000 | PolarDB-X(+40%) |
| 平均延迟 | 8.5ms | 5.2ms | PolarDB-X(-39%) |
| P99 延迟 | 45ms | 22ms | PolarDB-X(-51%) |
| 跨分片事务延迟 | 120ms | 65ms | PolarDB-X(-46%) |
| 跨分片查询延迟 | 5000ms | 300ms | PolarDB-X(-94%) |
| 扩容停机时间 | 4 小时 | 0 | PolarDB-X |
| 分布式事务一致性 | 最终一致 | 强一致(RC/RR) | PolarDB-X |
| 全局索引 | 不支持 | 支持 GSI + 聚簇索引 | PolarDB-X |
| 在线分区变更 | 不支持 | OnlineDDL | PolarDB-X |
| 运维复杂度 | 高(需维护 MySQL + ZK + 中间件) | 低(全托管) | PolarDB-X |
| MySQL 兼容性 | 部分(跨分片 SQL 受限) | 高度兼容 | PolarDB-X |
测试环境:
| 项目 | 配置 |
|---|---|
| ShardingSphere | 4 台 ECS (8C32G) + 4 个 RDS MySQL (8C32G, 500GB SSD) |
| PolarDB-X | 2 个 CN (8C32G) + 2 个 DN (8C32G, 500GB SSD) |
| 测试工具 | sysbench / JMeter 5.4 |
| 测试数据量 | 订单表 5000 万行,约 120GB |
结论:PolarDB-X 在所有维度均优于 ShardingSphere,尤其在跨分片查询(+94%)和 P99 延迟(-51%)上优势明显。核心原因是 PolarDB-X 的全局索引和计算下推能力从根本上解决了跨分片查询的性能问题。
9. 踩坑实录
坑 1:分片键选择不当导致数据倾斜
现象:按 seller_id 分片的订单表,头部卖家占了 40% 的订单量,导致某个 DN 节点负载远高于其他节点,查询延迟飙到 500ms。
原因:seller_id 分布不均匀,头部卖家集中在一个分片上。
解决:改用 user_id 做分片键(用户分布比卖家均匀得多),并创建 seller_id 的全局索引解决跨分片查询问题。同时,利用 Table Group 将高频关联表绑定在同一分区策略下。
-- 修改分区键(OnlineDDL,不锁表)
ALTER TABLE t_order PARTITION BY KEY(user_id) PARTITIONS 16;
坑 2:跨分片 Join 性能差
现象:订单表和商品表 Join 查询耗时 8 秒,监控显示数据在 CN 和 DN 之间来回传输了大量数据。
原因:两表不在同一表组,且没有命中全局索引,优化器选择了 Shuffle Join,数据需要跨节点重分布。
解决:两步优化——第一步,将订单详情表和订单表放入同一表组,确保按 user_id Join 时本地执行;第二步,为商品表创建全局索引,使 Join 命中 BKA Join 策略。
-- 将 t_order_item 加入 tg_order 表组
ALTER TABLE t_order_item SET TABLEGROUP tg_order;
坑 3:全局索引写入延迟高
现象:创建全局索引后,订单写入延迟从 5ms 飙到 50ms,P99 延迟超过 200ms。
原因:全局索引的写入需要同步更新索引表,增加了额外的 2PC 开销。Covering 列过多也会增加索引表的大小和写入开销。
解决:精简 Covering 列,只包含查询高频使用的字段;评估是否真的需要 UNIQUE GLOBAL INDEX,普通 GLOBAL INDEX 写入开销更低;对写入延迟敏感的场景,考虑用局部索引 + 异步补数据方案替代。
-- 精简 Covering 列
ALTER TABLE t_order ADD GLOBAL INDEX idx_seller_id(seller_id) COVERING(order_status);
-- 而非 COVERING(order_status, total_amount, created_at, ...)
坑 4:分布式事务超时
现象:大促期间出现 Lock wait timeout exceeded 错误,事务回滚率飙到 5%。
原因:长事务持有锁时间过长,导致其他事务等待超时。默认锁等待超时为 50 秒,高并发下容易触发。
解决:拆分大事务为小事务,减少锁持有时间;调整 lock_wait_timeout 参数;使用乐观锁替代悲观锁(基于 MVCC 的乐观读)。
-- 调整锁等待超时
SET GLOBAL lock_wait_timeout = 30;
-- 拆分大事务:批量操作改为分批提交
-- 原方案:一个事务更新 1000 条
-- 优化后:每 100 条一个事务
坑 5:DDL 变更锁表
现象:执行 ALTER TABLE ADD COLUMN 时,业务出现大量锁等待,接口超时。
原因:虽然 PolarDB-X 支持 OnlineDDL,但某些操作仍会短暂加锁(如元数据更新阶段),且 OnlineDDL 在数据迁移阶段会消耗大量 IO 资源,影响在线查询性能。
解决:DDL 操作放在业务低峰期执行;通过 SHOW DDL 监控 DDL 进度;对大表操作使用 ALTER TABLE ... PARTITION BY 的异步模式,降低对在线业务的影响。
-- 查看 DDL 进度
SHOW DDL;
-- 取消长时间运行的 DDL
CANCEL DDL <job_id>;
10. 最佳实践
10.1 分片策略决策树

10.2 检查清单
上线前的关键检查项:
分片设计:
- [ ] 分片键是否保证数据均匀分布?是否存在数据倾斜风险?
- [ ] 高频查询是否命中分片键或全局索引?是否有全分片扫描的 SQL?
- [ ] 关联查询的表是否在同一个 Table Group 中?
- [ ] 全局索引的 Covering 列是否精简?是否评估了写入开销?
事务设计:
- [ ] 大事务是否已拆分为小事务?单个事务涉及的分区数是否可控?
- [ ] 是否存在长事务导致锁超时的风险?
- [ ] 分布式事务的隔离级别是否满足业务需求?
读写分离:
- [ ] 写后读场景是否使用了
/*+TDDL:MASTER*/Hint 强制读主? - [ ] AP 查询是否配置了
WORKLOAD_TYPE=AP路由到只读实例? - [ ] 只读实例的数据延迟是否在业务可接受范围内?
运维保障:
- [ ] OnlineDDL 操作是否安排在业务低峰期?
- [ ] 是否配置了 DDL 进度监控和超时告警?
- [ ] 数据备份策略是否覆盖分布式场景?恢复演练是否验证过?
- [ ] CDC 日志消费是否正常?下游同步延迟是否可控?
安全合规:
- [ ] 数据库账号是否遵循最小权限原则?
- [ ] 敏感数据是否开启了 TDE 透明加密?
- [ ] 是否配置了 SQL 审计日志?
真实性声明
本文所有内容均基于作者在电商平台订单系统中的真实经验。所有案例、数据、代码均来自生产环境实践,经过验证。为保护商业机密,部分敏感信息已做脱敏处理,但技术细节保持完整和真实。
相关阅读: