本文目录导读:

为PHP项目制定数据过期规则,通常需要结合业务逻辑、存储成本、合规要求和性能来综合设计,以下是系统化的制定步骤和常见策略,附带PHP代码示例。
核心决策维度
-
数据性质
- 临时数据(验证码、Session、URL令牌):通常几分钟到几天。
- 日志数据(操作日志、访问日志):保留30天~1年。
- 用户行为数据(购物车、浏览历史):保留7天~6个月。
- 敏感数据(欧盟GDPR、中国《个人信息保护法》要求的数据):按法律要求的最小化保存时间。
-
可容忍的延迟
- 实时删除(如支付倒计时到期的订单)→ 主动触发删除。
- 批量清理(如日志归档)→ 定时任务(Cron)。
-
数据量级
- 千万级以下:直接DELETE。
- 亿级以上:考虑分区表(按时间分区,直接DROP分区)或TTL(Time To Live)索引(如MongoDB、ClickHouse)。
过期规则的常见模式
| 规则类型 | 适用场景 | 实现方式(PHP+MySQL) |
|---|---|---|
| 固定时间淘汰 | 验证码、临时Token | 存储created_at,查询时过滤 WHERE created_at < NOW() - INTERVAL 10 MINUTE,并定时清理 |
| 基于活动时间 | 用户session、在线状态 | 存储last_activity,超过N天未活动视为过期 |
| 基于状态+时间 | 订单(待付款24h未付自动取消) | WHERE status='pending' AND created_at < NOW() - INTERVAL 24 HOUR |
| 软删除+物理清理 | 回收站、历史记录 | 标记deleted_at,30天后物理删除 |
| 批量归档 | 10亿级日志 | 按日期分区,定时 ALTER TABLE logs DROP PARTITION p20240101 |
实现过期规则的PHP方案
单条数据过期(实时查询+后台清理)
// 1. 查询时自动过滤过期数据(推荐用于业务逻辑)
public function getValidCode(string $phone): ?string
{
$stmt = $pdo->prepare(
"SELECT code FROM verification_codes
WHERE phone = :phone AND expired_at > NOW()
LIMIT 1"
); // expired_at 是预先计算好的绝对过期时间
$stmt->execute(['phone' => $phone]);
return $stmt->fetchColumn();
}
// 2. 定时任务清理(每天凌晨执行)
// 在 crontab 中设置:0 3 * * * php /path/to/cleanup.php
// cleanup.php
require 'bootstrap.php';
$pdo->exec("DELETE FROM verification_codes WHERE expired_at < NOW() - INTERVAL 1 DAY"); // 保留一天内的过期记录用于审计
echo "清理完成";
分区表策略(适合海量日志)
-- 建立按月分区表
CREATE TABLE access_logs (
id BIGINT UNSIGNED AUTO_INCREMENT,
user_id INT UNSIGNED,
url VARCHAR(500),
created_at DATETIME,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (TO_DAYS(created_at)) (
PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')),
PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
// PHP 定时任务:每个月1日删除6个月前的分区
$sixMonthsAgo = new DateTime('-6 months');
$partitionName = 'p' . $sixMonthsAgo->format('Ym');
$pdo->exec("ALTER TABLE access_logs DROP PARTITION {$partitionName}");
// 注意:需要先添加新分区,避免数据写入失败
基于TTL的数据库特性(最低成本)
- MySQL 8.0+ 可结合
Event Scheduler+ 存储过程,但通常建议用Cron。 - Redis 原生支持TTL:PHP只用维护过期字段或利用Redis过期回调。
// Redis 验证码5分钟过期
$redis->setex("sms_code:{$phone}", 300, $code);
// 或利用MySQL 8.0的“不可见索引” + 定时任务,但不如Redis方便。
常见陷阱与最佳实践
-
不要依赖PHP进程内定时器
用Cron(Linux)或任务调度系统(如Laravel Scheduler)替代sleep()循环,避免内存泄漏。 -
分批删除避免锁表
对几百万行数据清理时,每次只删除1000行并短暂sleep。while (true) { $affected = $pdo->exec("DELETE FROM logs WHERE created_at < '2023-01-01' LIMIT 1000"); if ($affected === 0) break; usleep(50000); // 50ms } -
索引策略
确保expired_at、created_at、last_activity有索引,否则清理或查询会导致全表扫描。 -
容灾与回滚
清理前备份或采用软删除。$pdo->beginTransaction(); $pdo->exec("INSERT INTO logs_backup SELECT * FROM logs WHERE created_at < ..."); $pdo->exec("DELETE FROM logs WHERE created_at < ..."); $pdo->commit(); -
符合GDPR等法规
如果数据标记为“可被遗忘”,需要提供物理删除接口,并在删除后断开业务关联。
综合示例:订单过期自动取消
// 规则:状态为pending(待支付)且 created_at 超过24小时 ⇒ 自动取消
// 实现方案:每次查询订单时,顺便检查并取消过期的,但更好的做法是定时任务
// 定时任务(每分钟执行一次)
$stmt = $pdo->prepare(
"UPDATE orders
SET status = 'cancelled', cancelled_reason = 'payment_timeout'
WHERE status = 'pending' AND created_at < NOW() - INTERVAL 24 HOUR"
);
$stmt->execute();
echo "Cancelled {$stmt->rowCount()} orders.\n";
制定规则时的业务确认清单
- [ ] 该数据最长可以保留多久?(法律、存储成本)
- [ ] 如果提前删除,用户是否受影响?(例如购物车内容)
- [ ] 过期后是否还需要统计用途?(需要保留脱敏或聚合数据)
- [ ] 是否需要通知用户数据即将被清理?(例如GDPR要求的“数据销毁通知”)
- [ ] 清理频率如何?实时 vs 批量?高峰期能不能执行?
| 场景 | 推荐规则 | 实现方式 |
|---|---|---|
| 验证码/临时令牌 | 固定时间(5~30分钟) | Redis TTL / MySQL WHERE expired_at |
| 用户Session | 最后一次活动后1~7天 | Laravel Redis Session + GC |
| 操作日志 | 保留90天,物理删除 | 分区表 DROP PARTITION |
| 软删除回收站 | 保留30天后物理删除 | 定时 DELETE LIMIT |
| 敏感数据(GDPR) | 按法规+用户请求立即删除 | 物理删除 + 备份清除 |
如果你能提供具体的业务场景(例如是电商订单、用户行为日志还是内容审核),我可以给出更精确的规则建议和SQL示例。