支付系统里,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_db
  • payment_db

优点是改动小,缺点是跨库事务能力下降。

水平拆分

把每个库拆成多个逻辑库与逻辑表:

  • transaction_db_00 ~ transaction_db_09
  • payment_db_00 ~ payment_db_09

这样写入能力可线性扩展。

分片策略

Hash 分片

优点:负载均衡、实时写入分散。

缺点:按时间范围查询不友好。

Range 分片

优点:归档方便。

缺点:热点集中,无法均衡写入。

支付系统常选 Hash 分片。

分片键选择

txn_order_tabuser_id,保证同一用户数据聚合。

payment_tabtransaction_id,保证支付与交易能关联定位。

访问分片

只要 API 带有分片键,就能精准定位:

SELECT *
FROM transaction_db_04.txn_order_tab_0456
WHERE user_id = 123456
ORDER BY created_at
LIMIT 10;

迁移与治理

  • 新老分片并行运行
  • 双写与数据校验
  • 逐步切流

小结

分库分表不是目的,目的是把“不可扩展的单点”变成“可水平扩展的系统”。关键在于选对分片键,并保证访问路径简单、稳定。