支付系统里,MySQL 是最容易成为瓶颈的组件。分库分表是避免“单库单表天花板”的必经之路。
背景
- 架构:1 主 3 从
- 数据量:数十 TB
- QPS:数十万级
- Redis 缓存降低读压力,但写压力仍增长
目标
- 解决磁盘容量风险
- 扩展写入能力
- 保持稳定与可维护
核心表
交易订单表 txn_order_tab
CREATE TABLE `txn_order_tab_0000` (
`transaction_id` bigint(20) unsigned NOT NULL,
`transaction_type` tinyint(3) unsigned NOT NULL,
`transaction_status` tinyint(3) unsigned NOT NULL,
`biz_id` int(11) unsigned NOT NULL,
`biz_order_no` varchar(64) DEFAULT NULL,
`user_id` bigint(20) unsigned NOT NULL,
`amount` bigint(20) NOT NULL,
`created_at` int(11) unsigned NOT NULL,
`updated_at` int(11) unsigned NOT NULL,
`ext_data` JSON,
PRIMARY KEY (`transaction_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_created_at` (`created_at`),
UNIQUE KEY `uk_biz_order_no_biz_id` (`biz_order_no_biz_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
支付表 payment_tab
CREATE TABLE `payment_tab_0000` (
`payment_id` bigint(20) unsigned NOT NULL,
`transaction_id` bigint(20) unsigned NOT NULL,
`payment_type` tinyint(3) unsigned NOT NULL,
`amount` bigint(20) unsigned NOT NULL,
`payment_status` tinyint(3) unsigned NOT NULL,
`psp_id` int(11) unsigned NOT NULL,
`psp_order_no` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`psp_order_status` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` int(11) unsigned NOT NULL,
`updated_at` int(11) unsigned NOT NULL,
`ext_data` JSON,
PRIMARY KEY (`payment_id`),
KEY `idx_transaction_id` (`transaction_id`),
KEY `idx_psp_order_no` (`psp_order_no`),
KEY `idx_updated_at` (`updated_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
索引表 payment_index_tab
CREATE TABLE `payment_index_tab` (
`payment_id` bigint(20) NOT NULL PRIMARY KEY,
`transaction_id` bigint(20) unsigned NOT NULL,
`psp_id` int(11) unsigned NOT NULL,
`psp_order_no` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`payment_id`),
KEY `idx_transaction_id` (`transaction_id`),
UNIQUE KEY `idx_psp_order_no_psp_id` (`psp_order_no`, `psp_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
分库分表方案
垂直拆分
先按业务拆库:
transaction_dbpayment_db
优点是改动小,缺点是跨库事务能力下降。
水平拆分
把每个库拆成多个逻辑库与逻辑表:
transaction_db_00~transaction_db_09payment_db_00~payment_db_09
这样写入能力可线性扩展。
分片策略
Hash 分片
优点:负载均衡、实时写入分散。
缺点:按时间范围查询不友好。
Range 分片
优点:归档方便。
缺点:热点集中,无法均衡写入。
支付系统常选 Hash 分片。
分片键选择
txn_order_tab 选 user_id,保证同一用户数据聚合。
payment_tab 选 transaction_id,保证支付与交易能关联定位。
访问分片
只要 API 带有分片键,就能精准定位:
SELECT *
FROM transaction_db_04.txn_order_tab_0456
WHERE user_id = 123456
ORDER BY created_at
LIMIT 10;
迁移与治理
- 新老分片并行运行
- 双写与数据校验
- 逐步切流
小结
分库分表不是目的,目的是把“不可扩展的单点”变成“可水平扩展的系统”。关键在于选对分片键,并保证访问路径简单、稳定。