表格存储 Tablestore 实践:千里达票务系统海量订单明细存储
千里达票务系统累计出票过亿张,订单明细表在 RDS MySQL 里已经 8000 万行,查询越来越慢,分库分表改造成本又高。把历史订单明细迁到阿里云表格存储 Tablestore(NoSQL 宽表),热订单还在 MySQL,冷订单全在 Tablestore,查询接口对业务透明。
一、为什么用 NoSQL
- 订单明细写多读少,按 order_no / user_id / scene_id 查;
- 不需要跨表事务,只要单行读写;
- 数据量未来 5 年千万级到亿级,MySQL 撑不住。
二、架构
写入: 业务订单 → MySQL(热数据) + Tablestore(全量, 双写)
查询: 先查 MySQL, 没有再查 Tablestore
三、关键实现
3.1 表结构
表名: order_detail
主键: scene_id (分区键) + order_no (排序键)
列: user_id, ticket_type, price, status, created_at
索引: user_id_index (按用户查历史订单)
3.2 写入
client.put_row(
table_name="order_detail",
primary_key=[("scene_id", "SC1001"), ("order_no", "O20261018001")],
attribute_columns=[
("user_id", "U888"),
("price", 190),
("status", "paid")
]
)
3.3 查询某用户历史订单
range_query(
table_name="order_detail",
index_name="user_id_index",
primary_key=[("user_id", "U888")],
limit=20
)
3.4 生命周期
- 订单 3 年内数据在线;
- 超过 3 年自动归档到 OSS,需要时再导回。
四、踩坑复盘
- 分区键设计错:按 order_no 做分区,热点订单全落一个分区。解决:按 scene_id 分区,同景区数据分散;
- 范围扫描性能差:没建二级索引,按 user_id 查全表扫。解决:建多元索引;
- 双写一致性:MySQL 写成功 Tablestore 写失败,数据对不上。解决:本地消息表兜底,异步补偿;
- 容量规划:早期按最大容量买,月账单浪费。解决:按量 + 预留实例组合。
五、效果与总结
Tablestore 接进来后,历史订单查询不再拖累主库,业务方查"半年前的订单"响应也在百毫秒内。对千里达票务系统这种订单量随时间线性涨的业务,NoSQL 宽表是比分库分表成本低得多的方案。