本文目录导读:

在PHP项目中,会话(Session)历史数据的存储与过期清理通常有以下几种常见方案,我会从简单到复杂逐一说明:
默认文件存储(File-based)
PHP默认使用文件存储session,存储在服务器临时目录(如/tmp)。
清理机制:
// php.ini 配置 session.gc_probability = 1 // GC启动概率(1/1000) session.gc_divisor = 1000 // 分母 session.gc_maxlifetime = 1440 // 24分钟(秒)
手动清理示例:
// 自定义清理脚本
function cleanExpiredSessions($sessionSavePath) {
$maxLifetime = ini_get('session.gc_maxlifetime');
$files = glob($sessionSavePath . '/sess_*');
foreach ($files as $file) {
if (filemtime($file) + $maxLifetime < time()) {
unlink($file);
}
}
}
数据库存储(如MySQL)
更适合分布式或需要持久化session的场景。
表结构设计:
CREATE TABLE sessions (
session_id VARCHAR(128) NOT NULL PRIMARY KEY,
data TEXT,
last_activity INT(11) UNSIGNED NOT NULL,
expires_at INT(11) UNSIGNED NOT NULL,
INDEX idx_expires (expires_at),
INDEX idx_last_activity (last_activity)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
自定义Session处理器:
class DatabaseSessionHandler implements SessionHandlerInterface {
private $db;
private $maxLifetime;
public function __construct(PDO $db) {
$this->db = $db;
$this->maxLifetime = ini_get('session.gc_maxlifetime');
}
public function open($savePath, $sessionName): bool {
return true;
}
public function close(): bool {
return true;
}
public function read($sessionId): string {
$stmt = $this->db->prepare(
"SELECT data FROM sessions WHERE session_id = ? AND expires_at > UNIX_TIMESTAMP()"
);
$stmt->execute([$sessionId]);
$result = $stmt->fetchColumn();
return $result ?: '';
}
public function write($sessionId, $data): bool {
$expires = time() + $this->maxLifetime;
$stmt = $this->db->prepare(
"REPLACE INTO sessions (session_id, data, last_activity, expires_at)
VALUES (?, ?, UNIX_TIMESTAMP(), ?)"
);
return $stmt->execute([$sessionId, $data, $expires]);
}
public function destroy($sessionId): bool {
$stmt = $this->db->prepare("DELETE FROM sessions WHERE session_id = ?");
return $stmt->execute([$sessionId]);
}
public function gc($maxlifetime): int|false {
$stmt = $this->db->prepare(
"DELETE FROM sessions WHERE expires_at < UNIX_TIMESTAMP() OR
last_activity < UNIX_TIMESTAMP() - ?"
);
$stmt->execute([$maxlifetime]);
return $stmt->rowCount();
}
}
// 使用
$handler = new DatabaseSessionHandler($pdo);
session_set_save_handler($handler, true);
session_start();
定时清理(推荐):
-- MySQL事件调度器
CREATE EVENT clean_expired_sessions
ON SCHEDULE EVERY 15 MINUTE
DO
DELETE FROM sessions
WHERE expires_at < UNIX_TIMESTAMP()
OR last_activity < UNIX_TIMESTAMP() - 3600;
Redis存储(高性能方案)
利用Redis过期特性:
class RedisSessionHandler implements SessionHandlerInterface {
private $redis;
private $prefix;
private $ttl;
public function __construct(Redis $redis, $prefix = 'PHPSESSID:', $ttl = 3600) {
$this->redis = $redis;
$this->prefix = $prefix;
$this->ttl = $ttl;
}
public function read($sessionId): string {
$key = $this->prefix . $sessionId;
$data = $this->redis->get($key);
return $data ?: '';
}
public function write($sessionId, $data): bool {
$key = $this->prefix . $sessionId;
return $this->redis->setex($key, $this->ttl, $data);
}
public function destroy($sessionId): bool {
$key = $this->prefix . $sessionId;
return $this->redis->del($key) > 0;
}
public function gc($maxlifetime): int|false {
// Redis自动过期,无需手动清理
return 0;
}
}
最佳实践建议
方案选择:
| 场景 | 推荐方案 |
|---|---|
| 单机小项目 | 文件存储 + cron定时清理 |
| 中型应用 | MySQL/MariaDB存储 |
| 高并发/分布式 | Redis存储 |
| 微服务架构 | Redis + session共享 |
通用清理策略:
概率触发式(适合文件系统):
// 在每次请求时以1%的概率触发清理
if (mt_rand(1, 100) === 1) {
session_gc();
}
Cron定时任务(推荐):
# crontab 每30分钟执行 */30 * * * * php /path/to/clean_sessions.php
懒清理(lazy cleanup):
function lazySessionCleanup($pdo) {
// 只在session读取时检查过期
$stmt = $pdo->prepare(
"SELECT COUNT(*) FROM sessions WHERE expires_at < UNIX_TIMESTAMP()"
);
$stmt->execute();
$count = $stmt->fetchColumn();
if ($count > 1000) { // 只有过期记录过多才清理
$pdo->exec("DELETE FROM sessions WHERE expires_at < UNIX_TIMESTAMP()");
}
}
安全注意事项
-
session固定攻击防护:
session_regenerate_id(true); // 登录成功后重新生成ID
-
敏感数据清理:
// 登出时彻底清除 session_unset(); session_destroy(); setcookie(session_name(), '', time() - 3600, '/');
-
避免未授权访问:
// 检查session是否被篡改 if (isset($_SESSION['last_ip']) && $_SESSION['last_ip'] !== $_SERVER['REMOTE_ADDR']) { session_destroy(); die('Session hijacking detected'); }
根据你的项目规模和需求选择合适的方案,对于生产环境,推荐使用Redis + 自动过期,或MySQL + 定时清理的组合方式。