订单日志查询慢主因是数据库索引缺失、未分区或数据堆积,应优先优化SQL和表结构;需用非预处理方式执行EXPLAIN,建立(user_id, created_at)复合索引,超500万行须按月分区,并控制查询粒度、避免SELECT*。

订单日志查询慢,大概率不是 PHP 本身的问题,而是数据库没走索引、日志表缺乏分区或历史数据堆积导致的。直接优化 SQL 和表结构,比改 PHP 代码见效快得多。
查不到执行计划?先确认 EXPLAIN 是否真生效
很多同学在 PHP 中用 mysqli 或 PDO 执行 EXPLAIN SELECT ...,但返回空或报错——因为 MySQL 的 EXPLAIN 不支持预处理语句(PDO::prepare() 默认启用)。
- 临时绕过:用
PDO::exec()或mysqli_query()直接执行EXPLAIN SELECT order_id, created_at FROM order_log WHERE user_id = 123 AND created_at > '2024-01-01' - 真正要查的字段必须和
WHERE条件、ORDER BY字段对齐,否则type=ALL就是全表扫描 - 特别注意
key列是否为NULL,是就说明没走索引
order_log 表没复合索引?立刻补上最常用查询路径
典型查询如按 user_id + created_at 查最近 30 天日志,但只在 user_id 上建了单列索引,MySQL 无法高效过滤时间范围。
- 建复合索引优先级:高频
WHERE字段在前,范围查询字段(如created_at)放最后 ——ALTER TABLE order_log ADD INDEX idx_user_time (user_id, created_at); - 如果还常按
order_id关联主单,加order_id到索引里需谨慎:(user_id, order_id, created_at)会增大索引体积,且order_id通常等值匹配,放中间不如放最后 - 避免冗余索引:已有
(user_id, created_at),再单独建(user_id)就浪费
单表超 500 万行?别硬扛,按月分区或归档
即使加了索引,单表 800 万行时 SELECT COUNT(*) 或 LIMIT 1000,20 仍可能秒级延迟,因为 B+ 树深度变大、缓冲池命中率下降。
立即学习“PHP免费学习笔记(深入)”;
- MySQL 8.0+ 可用
RANGE COLUMNS(created_at)按月分区:ALTER TABLE order_log PARTITION BY RANGE COLUMNS(created_at) ( PARTITION p202312 VALUES LESS THAN ('2024-01-01'), PARTITION p202401 VALUES LESS THAN ('2024-02-01'), PARTITION p202402 VALUES LESS THAN ('2024-03-01'), PARTITION p_future VALUES LESS THAN MAXVALUE ); - 旧数据(如 1 年前)可导出后清空对应分区:
ALTER TABLE order_log TRUNCATE PARTITION p202312; - 应用层查日志时,显式带上
created_at BETWEEN ? AND ?,让优化器自动裁剪分区
PHP 层还能做啥?控制查询粒度,别一次捞 10 万条
有些接口写成 SELECT * FROM order_log WHERE user_id = ? 然后在 PHP 里用 array_filter 做二次筛选,这是把数据库当内存用。
- 分页必须用数据库原生
LIMIT offset, size,且offset超过 10 万时改用游标分页(用上一页最后的created_at和id作为下一页条件) -
前端不需要全部字段?明确指定需要的列,别用
*—— 日志表若有TEXT字段,IO 和网络传输成本陡增 - 高频低变更查询(如某用户最近 10 条操作)可加 Redis 缓存:
SET order_log:uid:123 "[{...},{...}]" EX 300,但注意缓存穿透和一致性
最常被忽略的一点:日志表的 created_at 字段是不是 DATETIME 类型?如果是 TIMESTAMP 且带时区转换,在 JOIN 或函数包裹(如 DATE(created_at))时会强制类型转换,导致索引失效。查之前先 SHOW CREATE TABLE order_log 看一眼字段定义。











