true, 'keep_days' => $keepDays, 'dry_run' => $dryRun, ], JSON_UNESCAPED_UNICODE); } $threshold = time() - ($keepDays * 86400); Log::info("xhprof_clean: 阈值 create_time<" . date('Y-m-d H:i:s', $threshold) . " ({$keepDays} 天)" . ($dryRun ? ' [dry-run]' : '')); // 3. 先收集过期 run id(必须在删 run 之前;detail 豁免表无 create_time,只能经主键关联删) $expiredRunIds = Db::name('xhprof_run') ->where('create_time', '<', $threshold) ->column('id'); // 4. 按依赖顺序清理:run_watch → detail → run $runWatchDeleted = $this->cleanByCreateTime('xhprof_run_watch', $threshold, $dryRun); $detailDeleted = $this->cleanDetailByRunIds($expiredRunIds, $dryRun); $runDeleted = $dryRun ? count($expiredRunIds) : $this->cleanByCreateTime('xhprof_run', $threshold, false); Log::info("xhprof_clean: run_watch {$runWatchDeleted} 行, detail {$detailDeleted} 行, run {$runDeleted} 行" . ($dryRun ? ' [dry-run]' : '')); return json_encode([ 'run_watch_deleted' => $runWatchDeleted, 'detail_deleted' => $detailDeleted, 'run_deleted' => $runDeleted, 'dry_run' => $dryRun, ], JSON_UNESCAPED_UNICODE); } /** * 按 create_time 分批清理有约定字段的表(run_watch / run). * * 每批 5000 行 + usleep 100ms,避免长事务锁表(照抄 nginx 清理模板的批量模式)。 * * @param string $table 表名(不含前缀,由 Db::name 自动补) * @param int $threshold 过期阈值(create_time 早于该时间戳删除) * @param bool $dryRun true 时只统计将删数量不执行删除 * @return int 累计删除行数(dryRun 为将删行数) */ protected function cleanByCreateTime(string $table, int $threshold, bool $dryRun): int { $query = Db::name($table)->where('create_time', '<', $threshold); if ($dryRun) { return (int) $query->count(); } $total = 0; // 循环分批 DELETE:每批 limit 5000,直到该阈值区间内无残留 while (true) { $deleted = Db::name($table) ->where('create_time', '<', $threshold) ->limit(self::RAW_BATCH_SIZE) ->delete(); if ($deleted <= 0) { break; } $total += $deleted; if ($deleted === self::RAW_BATCH_SIZE) { // 批次间休眠,降低 DB 压力(避免主从延迟) usleep(self::RAW_BATCH_USLEEP); } else { // 末批(< 5000)说明已清完该阈值区间 break; } } return $total; } /** * 清理 detail 豁免表(按 run id 主键关联删). * * ul_xhprof_run_detail 无 create_time(T1 豁免表设计),无法按时间条件删; * detail.id = run.id(1:1 非自增主键),把过期 run id 分批 IN 删除。 * * @param array $expiredRunIds 过期的 run 主键列表(删 run 之前收集) * @param bool $dryRun true 时只统计将删数量不执行删除 * @return int 累计删除行数(dryRun 为将删行数) */ protected function cleanDetailByRunIds(array $expiredRunIds, bool $dryRun): int { if (empty($expiredRunIds)) { return 0; } $total = 0; // 按 RAW_BATCH_SIZE 分块 IN 删除,避免单条 DELETE ... IN (...) 过大 foreach (array_chunk($expiredRunIds, self::RAW_BATCH_SIZE) as $chunk) { $query = Db::name('xhprof_run_detail')->whereIn('id', $chunk); if ($dryRun) { $total += (int) $query->count(); continue; } $total += $query->delete(); usleep(self::RAW_BATCH_USLEEP); } return $total; } }