在做旅游平台开发的时候,经常碰到用户查机票、酒店,一搜就是成千上万条数据。后台数据库的订单表、行程表动不动就上千万条记录,稍微不注意,页面加载就得十几秒,用户早就关掉了。这时候,光靠堆硬件解决不了根本问题,得从查询本身想办法。
合理设计索引是第一步
比如用户常按“出发城市+出发日期”查航班,那这两个字段组合起来建个联合索引最有效。单独给“出发城市”建索引效果差很多,因为匹配到的数据太多,还得回表筛选。联合索引能让数据库直接定位到目标数据段,减少扫描行数。
CREATE INDEX idx_depart_city_date ON t_flight (departure_city, departure_date);
但也不能啥字段都加索引。每多一个索引,写入时就要多维护一份结构,插入更新变慢。就像火车站多了几个检票口,虽然进站快了,但管理成本也高了。
分页别用 OFFSET LIMIT
传统分页写法像 LIMIT 10 OFFSET 999990,数据库得先跳过前99万多条记录,效率极低。正确做法是记住上一页最后一条数据的关键字段值,比如时间或ID,用条件过滤代替跳过。
SELECT id, flight_no, departure_time
FROM t_flight
WHERE departure_city = '上海'
AND departure_date = '2025-04-01'
AND id > 1000000
ORDER BY id
LIMIT 10;
这种“游标式分页”响应几乎不受数据位置影响,翻到第100万页也很快。
冷热数据分离
大多数用户只关心最近三个月的订单,更早的很少查。可以把一年前的数据归档到历史表,主表体积直接缩小80%以上。查旧数据走专门接口,不影响日常查询性能。
适当冗余,少用 JOIN
订单表里如果每次都要 JOIN 用户表取姓名、联系方式,两千万对一千万的关联非常耗资源。不如在下单时就把关键用户信息冗余进来,虽然占点空间,但查询快得多,就像高铁站提前打印好乘车凭证,不用每次都联网核验。
用缓存扛住高频请求
热门路线比如“北京—三亚”这种,每天被查成千上万次,完全可以把结果缓存几分钟。Redis 存个哈希,键是查询条件拼接,值是结果集序列化。后面相同请求直接读缓存,数据库压力立马下降。
考虑分区表
按时间分区是个常见策略。比如订单表按月分,查2025年4月的数据,数据库只扫对应分区,其他几十个分区直接忽略。相当于图书馆按年份分架,找某个月的报纸不用跑遍整个馆。
CREATE TABLE t_order (
id BIGINT,
order_date DATE,
user_id INT
) PARTITION BY RANGE (YEAR(order_date)) (
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026)
);
不过分区不是银弹,跨分区查询反而可能更慢,得结合实际查询模式来定。
SQL 写法也有讲究
避免 SELECT *,只取需要的字段。尤其是大文本字段 like 备注、日志,拖慢传输和解析。另外,IN 里面别塞上千个ID,改成临时表关联更稳妥。
有时候业务允许模糊结果,比如显示“约120万条”,完全可以用近似统计代替精确 COUNT(*),速度能快几十倍。