订阅更新

订单表5000万行查询越来越慢,你怎么办?

MySQL手册 第 2 题 约 4 分钟读完

本文目录
  1. 先说结论
  2. 方案一:索引优化(成本最低)
  3. 方案二:历史数据归档(最实用)
  4. 方案三:分区表(MySQL原生支持)
  5. 方案四:分库分表(最后的手段)
  6. 实战经验:我的选择路径
  7. 总结

先说结论

解决大表问题的思路是先优化、再拆分、最后才分库分表。不要上来就分表,这是最复杂的方案,应该是最后的手段。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

单表数据量过大是MySQL最常见的性能问题。很多人上来就说“分库分表”,但面试官想听的是你的思考过程——为什么选这个方案,有没有更简单的办法。今天把所有方案按“从简单到复杂”的顺序讲清楚。

方案一:索引优化(成本最低)

先检查现有查询的索引是否合理。很多时候“表大”不是根本原因,索引不合理才是。我见过一张5000万的表,索引正确的情况下查询只要5ms。

方案二:历史数据归档(最实用)

大多数场景下,订单表的查询都集中在近30天。把30天前的数据迁移到历史表,主表只保留热数据。

-- 建历史表(结构一样)
CREATE TABLE orders_archive LIKE orders;
 
-- 定期迁移
INSERT INTO orders_archive
SELECT * FROM orders WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
 
DELETE FROM orders WHERE created_at < DATE_SUB(NOW(), INTERVAL 30 DAY);
-- 注意:要分批删除,不要一次删太多,避免长事务和主从延迟

这个方案最简单有效。我在元宇宙社交App做过,主表从5000万降到500万,查询性能提升30%。

方案三:分区表(MySQL原生支持)

如果查询总是带时间条件,可以用MySQL的分区表按月分区:

ALTER TABLE orders PARTITION BY RANGE (TO_DAYS(created_at)) (
  PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')),
  PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')),
  PARTITION p202503 VALUES LESS THAN (TO_DAYS('2025-04-01')),
  PARTITION pmax VALUES LESS THAN MAXVALUE
);

查询带时间条件时,MySQL只扫描相关分区,不扫描全表。但分区表有限制:唯一索引必须包含分区键,某些查询可能无法利用分区裁剪。

方案四:分库分表(最后的手段)

当前面的方案都不够时,才考虑分库分表。常用方案:

水平分表: 按user_id或order_id的Hash值分到多张表。比如orders_0、orders_1、...orders_15,共分刲16张表。

常用中间件: ShardingSphere、Vitess、阿里云DRDS。

分表后最大的问题是跨表查询。比如“查询某个用户的所有订单”很简单(按user_id分表就能定位到一张表),但“查询今天所有用户的订单总金额”就需要查所有表再汇总,很复杂。

实战经验:我的选择路径

我在元宇宙社交App的做法是:

1. 先优化索引→ 解决了80%的问题

2. 历史数据归档→ 主表从5000万降到500万

3. 特别大的表(聊天记录——每天新增几百万条)才做了分表,按user_id取模刈64张表

所以记住顺序:索引优化 → 归档 → 分区表 → 分库分表。从简单到复杂,能不分表就不分表。

总结

方案复杂度适用场景
索引优化低先检查,大多数问题根源在此
历史归档低查询集中在近期数据
分区表中查询总带时间/分类条件
分库分表高单表超过千万级且前面方案不够

面试时按“从简单到复杂”的顺序讲,说明你有完整的思考过程,而不是无脑“上来就分表”。

下一篇聊一个很实战的场景:两个事务同时改同一条数据,一个超时失败了——你怎么排查死锁?

你们生产环境单表最大有多少行?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器