结论是慢因在于查询写法引发全表扫描,主因包括索引失效、未预编译、未批量处理;whereTime()对字段加函数致索引失效,应改用裸字段时间范围查询。

直接说结论:慢不是因为“循环两个日期”,而是查询写法触发了全表扫描,加上没走索引、没预编译、没批量处理——这几个问题叠加,哪怕只查两天数据,响应也可能从 20ms 拉到 800ms+。
whereTime() 写法错误导致索引失效
很多人写 whereTime('create_time', '>=', $start) 看似简洁,但 ThinkPHP 5.1 的 whereTime 底层会自动包裹 DATE() 或 FROM_UNIXTIME(),比如生成 WHERE DATE(create_time) >= '2026-08-01'。只要对字段做函数操作,MySQL 就没法用索引。
- 错误写法:
whereTime('create_time', 'between', [$start, $end])→ 实际 SQL 含DATE(create_time) BETWEEN ... - 正确写法:改用原生时间范围,确保字段裸露:
where('create_time', '>=', $start)->where('create_time', ' - 如果
create_time是 int 类型(时间戳),别用whereTime,直接比数字:where('create_time', 'between', [$start_ts, $end_ts])
日期范围查询必须配联合索引
单给 create_time 加索引没用——它只是范围条件,MySQL 在复合查询中只能用上「等值 + 范围」的前半段。比如你同时查 status = 1 AND create_time BETWEEN ? AND ?,只建 (create_time) 索引,status 那部分就白费了。
- 高频组合是
where(['status' => 1, 'create_time' => ['BETWEEN', [$s, $e]]])?建联合索引:ALTER TABLE user ADD INDEX idx_status_ctime (status, create_time); - 顺序不能颠倒:
(create_time, status)对这个查询几乎无效,因为create_time是范围,status后面的字段无法被索引有序性覆盖 - 用
EXPLAIN验证:type字段得是range或ref,key字段得显示你刚建的索引名
避免在循环里执行多次日期查询
典型反模式:foreach ($dates as $date) { Db::table('log')->where('day', $date)->select(); }。10 天就是 10 次查询 + 10 次连接开销,还全是全表扫。
- 合并成单次查询:
where('day', 'in', $dates)->select(),前提是day字段有索引 - 如果日期是连续的(如 2026-08-01 到 2026-08-10),用时间范围代替枚举:
where('create_time', '>=', strtotime('2026-08-01'))->where('create_time', ' - 高并发场景下,绕过 Query 构造器,手动用 PDO 预编译:
$stmt = $pdo->prepare('SELECT * FROM log WHERE create_time BETWEEN ? AND ?');,然后$stmt->execute([$start, $end])
别信 buildSql(),必须拿真实 SQL 去 EXPLAIN
buildSql() 返回的是带问号的模板 SQL,看不出是否走索引;getLastSql() 才是你实际执行的语句,但得配合 EXPLAIN 才算闭环。
- 开启日志:
'sql_explain' => true加到数据库配置里,每条 SELECT 自动打印EXPLAIN结果 - 关键看三列:
type(别出现ALL)、key(得是非NULL)、rows(越小越好,别动辄几万) - 如果
EXPLAIN显示Using filesort或Using temporary,说明ORDER BY或GROUP BY没走索引,得把排序字段加进联合索引末尾
真正卡住性能的,往往不是日期本身,而是你写的那行 whereTime、漏建的联合索引、以及循环里没合并的十次查询——这些点不揪出来,光调 PHP 配置或换服务器,基本白忙。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











