PHP项目统计慢跑恢复时间数据的方案
慢跑恢复时间数据统计",我理解可能有两种场景,先帮你区分一下,再给出对应的PHP实现方案。

先明确需求场景
场景A:运动App/健康类项目 — 统计用户跑步后的心率恢复时间(运动生理学概念:跑后心率回落到某个区间所需时间)。
场景B:系统性能监控 — 统计接口/任务"慢"的恢复时间(如慢查询恢复、服务从慢状态恢复正常的耗时)。
下面以场景A(运动数据)为主给方案,场景B在最后补充,如果你说的是另一个,告诉我再调整。
数据模型设计(运动恢复时间)
-- 跑步记录表
CREATE TABLE run_records (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
distance DECIMAL(8,2), -- 公里
avg_pace INT, -- 平均配速(秒/公里)
avg_hr INT, -- 平均心率
max_hr INT, -- 最大心率
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_time (user_id, start_time)
);
-- 心率采样表(核心:用于计算恢复时间)
CREATE TABLE hr_samples (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
run_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
sample_time DATETIME NOT NULL,
heart_rate INT NOT NULL,
phase TINYINT DEFAULT 0, -- 0=运动中 1=恢复期
INDEX idx_run (run_id, sample_time)
);
-- 恢复时间结果表(由脚本异步计算写入)
CREATE TABLE recovery_stats (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
run_id BIGINT NOT NULL UNIQUE,
user_id BIGINT NOT NULL,
hr_at_stop INT, -- 停止时心率
hr_1min INT, -- 1分钟心率
hr_2min INT,
hr_drop_1min INT, -- 1分钟心率下降值(HRR1)
recovery_time_sec INT, -- 恢复到静息心率区间耗时
computed_at DATETIME,
INDEX idx_user (user_id)
);
关键指标:HRR1(跑后1分钟心率下降值)是运动医学公认的恢复能力指标,通常下降 >12 bpm 为正常。
核心计算逻辑(PHP)
class RecoveryCalculator
{
private PDO $db;
// 静息心率阈值:恢复到 静息+10 视为"恢复"
private int $restingHr = 70;
public function __construct(PDO $db)
{
$this->db = $db;
}
/**
* 计算某次跑步的恢复数据
*/
public function calcForRun(int $runId): ?array
{
// 1. 取出该跑步的所有心率采样(按时间排序)
$stmt = $this->db->prepare(
"SELECT sample_time, heart_rate, phase
FROM hr_samples
WHERE run_id = :rid
ORDER BY sample_time ASC"
);
$stmt->execute([':rid' => $runId]);
$samples = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (count($samples) < 2) {
return null;
}
// 2. 找到"停止跑步"的临界点(最后一个 phase=0 的样本)
$stopIdx = null;
foreach ($samples as $i => $s) {
if ((int)$s['phase'] === 0) {
$stopIdx = $i;
}
}
if ($stopIdx === null || !isset($samples[$stopIdx + 1])) {
return null;
}
$stopTime = strtotime($samples[$stopIdx]['sample_time']);
$hrAtStop = (int)$samples[$stopIdx]['heart_rate'];
// 3. 找 1min / 2min 心率点
$hr1 = $this->findHrNear($samples, $stopTime + 60);
$hr2 = $this->findHrNear($samples, $stopTime + 120);
$hrr1 = $hr1 !== null ? $hrAtStop - $hr1 : null;
// 4. 计算恢复到静息区间所需时间
$targetHr = $this->restingHr + 10;
$recoveryTime = null;
foreach (array_slice($samples, $stopIdx + 1) as $s) {
if ((int)$s['heart_rate'] <= $targetHr) {
$recoveryTime = strtotime($s['sample_time']) - $stopTime;
break;
}
}
$result = [
'run_id' => $runId,
'hr_at_stop' => $hrAtStop,
'hr_1min' => $hr1,
'hr_2min' => $hr2,
'hr_drop_1min' => $hrr1,
'recovery_time_sec' => $recoveryTime,
];
$this->save($result);
return $result;
}
/**
* 找最接近目标时间点的心率样本(容差±15秒)
*/
private function findHrNear(array $samples, int $targetTs): ?int
{
$best = null;
$minDiff = PHP_INT_MAX;
foreach ($samples as $s) {
$diff = abs(strtotime($s['sample_time']) - $targetTs);
if ($diff < $minDiff) {
$minDiff = $diff;
$best = (int)$s['heart_rate'];
}
}
return $minDiff <= 15 ? $best : null;
}
private function save(array $r): void
{
$sql = "INSERT INTO recovery_stats
(run_id, user_id, hr_at_stop, hr_1min, hr_2min,
hr_drop_1min, recovery_time_sec, computed_at)
VALUES (:run_id, :user_id, :hr_stop, :hr1, :hr2,
:hrr1, :recovery, NOW())
ON DUPLICATE KEY UPDATE
hr_at_stop = VALUES(hr_at_stop),
hr_1min = VALUES(hr_1min),
hr_2min = VALUES(hr_2min),
hr_drop_1min = VALUES(hr_drop_1min),
recovery_time_sec = VALUES(recovery_time_sec),
computed_at = NOW()";
// ... bind & execute
}
}
批量统计与性能优化
关键原则:实时接口只查汇总表,重计算异步跑。
// 批量补算(建议放到定时任务/队列,分页处理避免内存爆)
$pageSize = 500;
$lastId = 0;
while (true) {
$runs = $db->query(
"SELECT id, user_id FROM run_records
WHERE id > $lastId
AND id NOT IN (SELECT run_id FROM recovery_stats)
ORDER BY id LIMIT $pageSize"
)->fetchAll();
if (!$runs) break;
foreach ($runs as $run) {
$calc->calcForRun((int)$run['id']);
$lastId = $run['id'];
}
}
性能优化要点:
hr_samples按(run_id, sample_time)建联合索引,避免全表扫。- 单次跑步采样数据先一次性拉全在内存算,不要在循环里逐条查库。
- 大量历史数据用分批 + 队列(Redis/RabbitMQ),别在 Web 请求里同步算。
- 汇总统计(如某用户平均恢复时间)在
recovery_stats上用 SQL 聚合:
SELECT user_id,
AVG(hr_drop_1min) AS avg_hrr1,
AVG(recovery_time_sec) AS avg_recovery,
COUNT(*) AS run_count
FROM recovery_stats
WHERE user_id = :uid
AND computed_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)
GROUP BY user_id;
高频查询的用户统计结果可缓存到 Redis(如 TTL 1 小时),写时失效。
如果其实是场景B(系统慢恢复时间)
那"统计"通常指:记录服务从慢/故障状态恢复到正常的耗时,做法:
CREATE TABLE slow_recovery_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
service VARCHAR(64),
metric VARCHAR(64), -- 如 p99_latency
slow_start DATETIME, -- 进入慢阈值时间
normal_time DATETIME, -- 恢复正常时间
duration_ms INT, -- 恢复耗时
INDEX idx_service (service, slow_start)
);
用 PHP 定时采集指标,状态机检测 正常 → 慢 → 正常 的跳变并记录 duration,再做 P50/P95 聚合分析。
能否确认一下你指的是哪种场景? 如果是运动数据,还需要确认:心率采样是实时上传还是事后批量导入?是否已有静息心率数据?这样我可以把计算逻辑再校准。