我网站跑的是WordPress + 子比主题。子比这玩意儿,懂的都懂——登录、会员、下载权限、QQ登录、回复可见……功能塞得满满当当,每次刷新都要查几十次数据库。
于是我想简单改造一下。
代码已经放好。
https://github.com/1810216796/wordpress-sqlite-cache/
这套方案到底干了啥?
核心思路就两条:
-
对象缓存(Object Cache):替代Redis,把数据库查询结果(如文章数据、用户信息)存到SQLite文件里。WordPress每次查询前先问SQLite,有就直接拿,省掉MySQL的IO。
-
全页面静态缓存(Page Cache):对于不登录的游客,直接输出整个页面的HTML,连PHP模板渲染都省了,相当于把动态站伪装成纯静态站。
这两层一上,99元的服务器也能跑出“土豪服务器”的感觉。
第一步:ApiCache.php,替换Redis的活儿
这篇文章:高效率网页缓存页面代码
这个类负责读写SQLite,支持MD5分片(把缓存分散到多个.db文件里,避免单文件过大),还带访问计数,方便后续清理冷数据。
直接上代码,保存为/wp-content/ApiCache.php:
<?php
// ApiCache.php - 完全版,包含 delete 方法
define('CACHE_ROOT', __DIR__ . '/Mcache');
if (!defined('CACHE_PROBABILITY')) {
define('CACHE_PROBABILITY', 100);
}
if (!defined('CACHE_DEFAULT_SHARD')) {
define('CACHE_DEFAULT_SHARD', 1); // 1=16×16, 2=16×256, 3=256×256
}
class ApiCache
{
private $dirName;
private $root;
private $level1;
private $level2;
private $dbPool = [];
private $obStack = [];
public function __construct($dirName, $shardType = null)
{
if ($shardType === null) $shardType = CACHE_DEFAULT_SHARD;
$this->dirName = $dirName;
$this->root = rtrim(CACHE_ROOT, '/') . '/' . $dirName . '/';
if (!is_dir($this->root)) mkdir($this->root, 0755, true);
switch ($shardType) {
case 1: $this->level1 = 1; $this->level2 = 2; break;
case 2: $this->level1 = 1; $this->level2 = 3; break;
case 3: $this->level1 = 2; $this->level2 = 4; break;
default: throw new Exception("无效分片类型");
}
}
// ---------- 基础读写 ----------
public function get($key)
{
$md5 = md5($key);
$db = $this->getDb($md5);
if (!$db) return false;
try {
$stmt = $db->prepare("SELECT content, access_count, expire_time FROM cache WHERE md5 = ?");
$stmt->execute([$md5]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) return false;
// 过期判断:过期则删除并视为未命中
if ($row['expire_time'] > 0 && $row['expire_time'] < time()) {
$del = $db->prepare("DELETE FROM cache WHERE md5 = ?");
$del->execute([$md5]);
return false;
}
// 访问计数采样
$prob = (int) CACHE_PROBABILITY;
if ($prob > 0 && ($prob >= 100 || mt_rand(1, 100) <= $prob)) {
$upd = $db->prepare("UPDATE cache SET access_count = access_count + 1 WHERE md5 = ?");
$upd->execute([$md5]);
}
$raw = @gzuncompress($row['content']);
return ($raw !== false) ? $raw : $row['content'];
} catch (Exception $e) {
error_log("ApiCache get error: " . $e->getMessage());
return false;
}
}
public function set($key, $data, $ttl = 0)
{
$md5 = md5($key);
$db = $this->getDb($md5);
if (!$db) return false;
$expire = ($ttl > 0) ? time() + $ttl : 0;
$compressed = (strlen($data) < 200) ? $data : gzcompress($data, 6);
try {
$stmt = $db->prepare("UPDATE cache SET content = ?, expire_time = ? WHERE md5 = ?");
$stmt->execute([$compressed, $expire, $md5]);
if ($stmt->rowCount() > 0) return true;
$stmt = $db->prepare("INSERT INTO cache (md5, content, access_count, first_time, expire_time) VALUES (?, ?, 0, ?, ?)");
return $stmt->execute([$md5, $compressed, time(), $expire]);
} catch (Exception $e) {
error_log("ApiCache set error: " . $e->getMessage());
return false;
}
}
public function delete($key)
{
$md5 = md5($key);
$db = $this->getDb($md5);
if (!$db) return false;
try {
$stmt = $db->prepare("DELETE FROM cache WHERE md5 = ?");
$stmt->execute([$md5]);
return $stmt->rowCount() > 0;
} catch (Exception $e) {
error_log("ApiCache delete error: " . $e->getMessage());
return false;
}
}
// ---------- 片段缓存 ----------
public function start($key, $ttl = 0)
{
$content = $this->get($key);
if ($content !== false) {
echo $content;
return true;
}
ob_start();
$this->obStack[] = [
'key' => $key,
'level' => ob_get_level(),
'ttl' => $ttl
];
return false;
}
public function end()
{
if (empty($this->obStack)) {
if (ob_get_level() > 0) ob_end_clean();
return;
}
$info = array_pop($this->obStack);
if (ob_get_level() != $info['level'] + 1) {
echo ob_get_clean();
return;
}
$content = ob_get_clean();
$this->set($info['key'], $content, $info['ttl']);
echo $content;
}
// ---------- 全页面缓存 ----------
public function page($key, $ttl = 0)
{
$content = $this->get($key);
if ($content !== false) {
echo $content;
exit;
}
ob_start(function($buffer) use ($key, $ttl) {
$this->set($key, $buffer, $ttl);
return $buffer;
});
}
// ---------- 内部工具 ----------
private function getDb($md5)
{
$dir1 = substr($md5, 0, $this->level1);
$filePrefix = substr($md5, 0, $this->level2);
$dirPath = $this->root . $dir1;
$dbFile = $dirPath . '/' . $filePrefix . '.db';
$poolKey = $this->root . '|' . $dir1 . '/' . $filePrefix;
if (isset($this->dbPool[$poolKey])) return $this->dbPool[$poolKey];
if (!is_dir($dirPath)) {
if (!mkdir($dirPath, 0755, true)) return false;
}
try {
$pdo = new PDO("sqlite:" . $dbFile);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 建表语句直接包含 expire_time,无需后续 ALTER
$pdo->exec("CREATE TABLE IF NOT EXISTS cache (
md5 TEXT PRIMARY KEY,
content BLOB,
access_count INTEGER DEFAULT 0,
first_time INTEGER,
expire_time INTEGER DEFAULT 0
)");
$pdo->exec("CREATE INDEX IF NOT EXISTS idx_first_time ON cache(first_time)");
$pdo->exec("CREATE INDEX IF NOT EXISTS idx_access_count ON cache(access_count)");
$pdo->exec("CREATE INDEX IF NOT EXISTS idx_expire_time ON cache(expire_time)");
$pdo->exec("PRAGMA journal_mode = WAL");
$pdo->exec("PRAGMA synchronous = OFF");
$this->dbPool[$poolKey] = $pdo;
return $pdo;
} catch (PDOException $e) {
error_log("SQLite open failed: " . $e->getMessage());
return false;
}
}
public function close() { $this->dbPool = []; }
public function getRoot() { return $this->root; }
}
// ---------- 全局辅助函数 ----------
function get_cache_instance($dirName, $shardType = null)
{
static $instances = [];
if ($shardType === null) $shardType = CACHE_DEFAULT_SHARD;
$key = $dirName . '|' . $shardType;
if (!isset($instances[$key])) {
$instances[$key] = new ApiCache($dirName, $shardType);
}
return $instances[$key];
}
// 全页面缓存(支持 $ttl,单位:秒)
function cache_page($key, $dirName = 'Mpage', $shardType = null, $ttl = 0)
{
if ($shardType === null) $shardType = CACHE_DEFAULT_SHARD;
$cache = get_cache_instance($dirName, $shardType);
$cache->page($key, $ttl);
}
// 片段缓存开始(支持 $ttl,单位:秒)
function cstart($key, $dirName = 'Mscene', $shardType = null, $ttl = 0)
{
global $_cache_stack;
if (!isset($_cache_stack) || !is_array($_cache_stack)) $_cache_stack = [];
if ($shardType === null) $shardType = CACHE_DEFAULT_SHARD;
$cache = get_cache_instance($dirName, $shardType);
$result = $cache->start($key, $ttl);
if (!$result) {
$_cache_stack[] = ['dir' => $dirName, 'shard' => $shardType, 'ttl' => $ttl];
}
return $result;
}
function cend()
{
global $_cache_stack;
if (empty($_cache_stack)) {
if (ob_get_level() > 0) ob_end_clean();
return;
}
$info = array_pop($_cache_stack);
$cache = get_cache_instance($info['dir'], $info['shard']);
$cache->end();
}
这个类干了啥?
-
get()和set()负责对象缓存(给后面object-cache.php用)。 -
delete()负责删除单个缓存(当文章更新时需要清空相关缓存)。 -
cache_page()负责全页面缓存,命中就直接输出HTML并退出,未命中就捕获输出并保存。
第二步:写一个object-cache.php,平替Redis插件
WordPress有个特殊机制:如果/wp-content/目录下存在object-cache.php,所有wp_cache_get()、wp_cache_set()这类函数就会自动走这个文件,而不是默认的数据库。
我们就是用这个文件,把所有对象缓存操作指向我们的SQLite类,完全绕过Redis。
保存为/wp-content/object-cache.php:
<?php
/**
* 使用新版 ApiCache(支持 TTL)作为 WordPress 对象缓存后端
* 适配 expire_time 自动过期
*/
if (!defined('WP_USE_EXT_OBJECT_CACHE')) {
define('WP_USE_EXT_OBJECT_CACHE', true);
}
// 引入你的新版 ApiCache(路径根据实际调整)
require_once __DIR__ . '/ApiCache.php';
// 缓存根目录(确保与 ApiCache 中一致)
if (!defined('CACHE_ROOT')) {
define('CACHE_ROOT', WP_CONTENT_DIR . '/Mcache');
}
// 默认前缀(可留空)
if (!defined('WP_REDIS_PREFIX')) {
define('WP_REDIS_PREFIX', '');
}
class WP_Object_Cache {
private $apiCache;
private $cache = []; // 内存缓存
private $global_groups = [
'blog-details', 'blog-id-cache', 'blog-lookup', 'global-posts',
'networks', 'rss', 'sites', 'site-details', 'site-lookup',
'site-options', 'site-transient', 'users', 'useremail',
'userlogins', 'usermeta', 'user_meta', 'userslugs'
];
private $ignored_groups = [];
private $global_prefix = '';
private $blog_prefix = 0;
public $cache_hits = 0;
public $cache_misses = 0;
public function __construct() {
global $blog_id, $table_prefix;
$this->global_prefix = is_multisite() ? '' : $table_prefix;
$this->blog_prefix = is_multisite() ? $blog_id : $table_prefix;
// 分片模式 1(16×16),目录 WpObject
$this->apiCache = get_cache_instance('WpObject', 1);
}
private function build_key($key, $group) {
$salt = WP_REDIS_PREFIX;
$prefix = $this->is_global_group($group) ? $this->global_prefix : $this->blog_prefix;
return "{$salt}{$prefix}:{$group}:{$key}";
}
private function is_global_group($group) {
return in_array($group, $this->global_groups);
}
private function is_ignored_group($group) {
return in_array($group, $this->ignored_groups);
}
// -------- 核心方法 --------
public function get($key, $group = 'default', $force = false, &$found = null) {
$derived_key = $this->build_key($key, $group);
// 内存命中
if (array_key_exists($derived_key, $this->cache) && !$force) {
$found = true;
$this->cache_hits++;
return $this->cache[$derived_key];
}
// 忽略组或缓存不可用
if ($this->is_ignored_group($group)) {
$found = false;
$this->cache_misses++;
return false;
}
// 从 SQLite 读取(get 内部会自动检查 expire_time)
$value = $this->apiCache->get($derived_key);
if ($value === false) {
$found = false;
$this->cache_misses++;
return false;
}
// 反序列化
$data = @unserialize($value);
if ($data === false && $value !== serialize(false)) {
$data = $value;
}
$found = true;
$this->cache_hits++;
$this->cache[$derived_key] = $data;
return $data;
}
public function set($key, $value, $group = 'default', $expiration = 0) {
$derived_key = $this->build_key($key, $group);
if ($this->is_ignored_group($group)) {
$this->cache[$derived_key] = $value;
return true;
}
$serialized = serialize($value);
// 将 WordPress 的过期秒数传给 ApiCache 的 $ttl
$success = $this->apiCache->set($derived_key, $serialized, (int)$expiration);
if ($success) {
$this->cache[$derived_key] = $value;
}
return $success;
}
public function delete($key, $group = 'default', $deprecated = false) {
$derived_key = $this->build_key($key, $group);
unset($this->cache[$derived_key]);
if ($this->is_ignored_group($group)) {
return true;
}
return $this->apiCache->delete($derived_key);
}
public function add($key, $value, $group = 'default', $expiration = 0) {
if (function_exists('wp_suspend_cache_addition') && wp_suspend_cache_addition()) {
return false;
}
$derived_key = $this->build_key($key, $group);
if (array_key_exists($derived_key, $this->cache)) {
return false;
}
// 检查持久层是否存在
if (!$this->is_ignored_group($group)) {
$exists = $this->apiCache->get($derived_key);
if ($exists !== false) {
return false;
}
}
return $this->set($key, $value, $group, $expiration);
}
public function replace($key, $value, $group = 'default', $expiration = 0) {
$derived_key = $this->build_key($key, $group);
if ($this->is_ignored_group($group)) {
if (!array_key_exists($derived_key, $this->cache)) {
return false;
}
} else {
$exists = $this->apiCache->get($derived_key);
if ($exists === false) {
return false;
}
}
return $this->set($key, $value, $group, $expiration);
}
public function flush() {
$this->cache = [];
// 不删除 SQLite 文件,由清理脚本管理
return true;
}
public function flush_runtime() {
$this->cache = [];
return true;
}
public function increment($key, $offset = 1, $group = 'default') {
$value = $this->get($key, $group);
if ($value === false) {
$value = 0;
}
if (!is_numeric($value)) {
return false;
}
$new_value = (int)$value + $offset;
$this->set($key, $new_value, $group);
return $new_value;
}
public function decrement($key, $offset = 1, $group = 'default') {
return $this->increment($key, -$offset, $group);
}
// 批量操作(简单实现)
public function get_multiple($keys, $group = 'default', $force = false) {
$results = [];
foreach ($keys as $key) {
$results[$key] = $this->get($key, $group, $force);
}
return $results;
}
public function set_multiple($data, $group = 'default', $expire = 0) {
$results = [];
foreach ($data as $key => $value) {
$results[$key] = $this->set($key, $value, $group, $expire);
}
return $results;
}
public function delete_multiple($keys, $group = 'default') {
$results = [];
foreach ($keys as $key) {
$results[$key] = $this->delete($key, $group);
}
return $results;
}
public function add_multiple($data, $group = 'default', $expire = 0) {
$results = [];
foreach ($data as $key => $value) {
$results[$key] = $this->add($key, $value, $group, $expire);
}
return $results;
}
// 组管理
public function add_global_groups($groups) {
$this->global_groups = array_unique(array_merge($this->global_groups, (array)$groups));
}
public function add_non_persistent_groups($groups) {
$this->ignored_groups = array_unique(array_merge($this->ignored_groups, (array)$groups));
}
public function switch_to_blog($blog_id) {
if (!function_exists('is_multisite') || !is_multisite()) {
return false;
}
$this->blog_prefix = (int)$blog_id;
return true;
}
// 统计信息(可选)
public function stats() {
echo "<p><strong>Cache Hits:</strong> {$this->cache_hits}<br />";
echo "<strong>Cache Misses:</strong> {$this->cache_misses}</p>";
}
public function info() {
return (object)[
'hits' => $this->cache_hits,
'misses' => $this->cache_misses,
'ratio' => ($this->cache_hits + $this->cache_misses) > 0
? round($this->cache_hits / ($this->cache_hits + $this->cache_misses) * 100, 1)
: 100,
'groups' => (object)[
'global' => $this->global_groups,
'non_persistent' => $this->ignored_groups,
],
'meta' => ['Client' => 'SQLite + ApiCache (TTL)'],
];
}
}
// -------- 全局函数桥接 --------
function wp_cache_init() {
global $wp_object_cache;
if (!($wp_object_cache instanceof WP_Object_Cache)) {
$wp_object_cache = new WP_Object_Cache();
}
}
function wp_cache_add($key, $data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->add($key, $data, $group, $expire);
}
function wp_cache_set($key, $data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->set($key, $data, $group, $expire);
}
function wp_cache_get($key, $group = '', $force = false, &$found = null) {
global $wp_object_cache;
return $wp_object_cache->get($key, $group, $force, $found);
}
function wp_cache_delete($key, $group = '', $deprecated = 0) {
global $wp_object_cache;
return $wp_object_cache->delete($key, $group, $deprecated);
}
function wp_cache_replace($key, $data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->replace($key, $data, $group, $expire);
}
function wp_cache_flush() {
global $wp_object_cache;
return $wp_object_cache->flush();
}
function wp_cache_flush_runtime() {
global $wp_object_cache;
return $wp_object_cache->flush_runtime();
}
function wp_cache_incr($key, $offset = 1, $group = '') {
global $wp_object_cache;
return $wp_object_cache->increment($key, $offset, $group);
}
function wp_cache_decr($key, $offset = 1, $group = '') {
global $wp_object_cache;
return $wp_object_cache->decrement($key, $offset, $group);
}
function wp_cache_get_multiple($keys, $group = '', $force = false) {
global $wp_object_cache;
return $wp_object_cache->get_multiple($keys, $group, $force);
}
function wp_cache_set_multiple($data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->set_multiple($data, $group, $expire);
}
function wp_cache_delete_multiple($keys, $group = '') {
global $wp_object_cache;
return $wp_object_cache->delete_multiple($keys, $group);
}
function wp_cache_add_multiple($data, $group = '', $expire = 0) {
global $wp_object_cache;
return $wp_object_cache->add_multiple($data, $group, $expire);
}
function wp_cache_add_global_groups($groups) {
global $wp_object_cache;
$wp_object_cache->add_global_groups($groups);
}
function wp_cache_add_non_persistent_groups($groups) {
global $wp_object_cache;
$wp_object_cache->add_non_persistent_groups($groups);
}
function wp_cache_switch_to_blog($blog_id) {
global $wp_object_cache;
return $wp_object_cache->switch_to_blog($blog_id);
}
function wp_cache_supports($feature) {
switch ($feature) {
case 'add_multiple':
case 'set_multiple':
case 'get_multiple':
case 'delete_multiple':
case 'flush_runtime':
return true;
default:
return false;
}
}
function wp_cache_close() {
return true;
}
// 自动初始化
wp_cache_init();
第三步:修改根目录index.php,给游客加全页缓存
这个最简单,直接在网站根目录的index.php最前面加上:
<?php
/**
* Front to the WordPress application – 加了全页缓存加速(带排除规则)
*/
// ---------- 加载 WordPress 核心(仅用于判断登录状态) ----------
require_once __DIR__ . '/wp-load.php';
// ---------- 定义不需要缓存的 URI 列表(支持部分匹配) ----------
$excluded_uris = [
'/qq-login/', // 你的 QQ 登录页面路径
'/oauth/', // 通用的 OAuth 回调路径
'/wp-login.php', // WordPress 登录页
'/wp-admin/', // 后台管理(虽然通常不走 index.php,但以防万一)
'/register/', // 注册页面
'/checkout/', // 支付/结账页面(如果有)
// 你可以继续添加更多需要排除的路径
];
// 检查当前请求 URI 是否匹配排除列表
$current_uri = $_SERVER['REQUEST_URI'];
$skip_cache = false;
foreach ($excluded_uris as $uri) {
if (strpos($current_uri, $uri) !== false) {
$skip_cache = true;
break;
}
}
// 如果是登录用户,也跳过缓存(因为要显示个性化内容)
if (is_user_logged_in()) {
$skip_cache = true;
}
// ---------- 游客且未排除,才使用全页缓存 ----------
if (!$skip_cache && !is_user_logged_in()) {
// 引入你的 ApiCache 类
require_once __DIR__ . '/wp-content/ApiCache.php';
// 生成缓存键(基于当前 URL,区分 GET 参数)
$cache_key = 'page_' . md5($_SERVER['REQUEST_URI']);
// 调用全页缓存函数(命中则输出并退出,否则开始捕获)
cache_page($cache_key, 'Mpage', 1, 86400);
// 注意:cache_page 若命中会 exit,若未命中则会继续执行下面的代码
}
// ---------- 正常 WordPress 加载 ----------
define('WP_USE_THEMES', true);
require __DIR__ . '/wp-blog-header.php';
就这几行,游客访问时直接输出SQLite里的HTML,不再执行主题的PHP代码。
第四步:定期清理过期缓存(独立脚本)
SQLite文件不会自动过期,所以我们需要一个脚本定时清理低频数据。我写了一个ClearCache.php,放在命令行里跑(比如每天凌晨):
#!/usr/bin/env php
<?php
// ClearCache.php - 新版清理:过期删除 + 超限冷淘汰
// 适配你的实际路径
if (php_sapi_name() !== 'cli') {
die("仅允许 CLI 运行\n");
}
// ---------- 配置 ----------
define('CACHE_ROOT', '/www/wwwroot/你的网站/wp-content/缓存主路径'); // 请修改为你的实际路径
// 清理任务列表
$CLEAN_TASKS = [
[
'path' => CACHE_ROOT . '/WpObject',
'max_records' => 2000, // 对象缓存最多 20 万条
],
[
'path' => CACHE_ROOT . '/Mpage',
'max_records' => 2000, // 全页缓存最多 5 万条
],
// 如果有其他缓存目录(如 default、Mlyric 等)可继续添加
];
// ---------- 执行 ----------
echo "[" . date('Y-m-d H:i:s') . "] 开始清理(过期删除 + 超限冷淘汰)...\n";
$totalExpired = 0;
$totalOverLimit = 0;
foreach ($CLEAN_TASKS as $task) {
$path = $task['path'];
echo "\n--- 清理任务: $path ---\n";
if (!is_dir($path)) {
echo " 目录不存在,跳过\n";
continue;
}
$dbFiles = getDbFiles($path);
if (empty($dbFiles)) {
echo " 没有找到 .db 文件\n";
continue;
}
echo " 发现 " . count($dbFiles) . " 个 .db 文件\n";
// 第一步:删除所有已过期的记录
$deletedExpired = 0;
$fileCount = 0;
foreach ($dbFiles as $dbFile) {
$fileCount++;
$deleted = cleanExpiredRecords($dbFile);
if ($deleted > 0) {
echo " 过期清理 $dbFile : 删除 $deleted 条\n";
}
$deletedExpired += $deleted;
if ($fileCount % 10 == 0) {
echo " 已处理 $fileCount 个文件...\n";
}
}
echo "第一步完成,共删除过期缓存 $deletedExpired 条\n";
$totalExpired += $deletedExpired;
// 第二步:如果总数仍超限,按访问量删除最冷数据
$totalRecords = countAllRecords($dbFiles);
$max = $task['max_records'];
if ($totalRecords > $max) {
$need = $totalRecords - $max;
echo "记录数 $totalRecords 超过限制($max),需删除 $need 条最冷数据\n";
$deletedCold = deleteColdestRecords($dbFiles, $need);
echo "第二步删除 $deletedCold 条\n";
$totalOverLimit += $deletedCold;
} else {
echo "记录数 $totalRecords 未超限 $max\n";
}
// 清理空目录
removeEmptyDirs($path, false);
}
echo "\n[" . date('Y-m-d H:i:s') . "] 清理完成\n";
echo "汇总:过期删除 $totalExpired 条,超限冷淘汰 $totalOverLimit 条\n";
// ---------- 辅助函数 ----------
function getDbFiles($root) {
$files = [];
if (!is_dir($root)) return $files;
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iter as $file) {
if ($file->isFile() && $file->getExtension() === 'db') {
$files[] = $file->getPathname();
}
}
return $files;
}
function cleanExpiredRecords($dbFile) {
try {
$pdo = new PDO("sqlite:" . $dbFile);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$now = time();
$limit = 1000;
$lastMd5 = '';
$totalDeleted = 0;
do {
$stmt = $pdo->prepare("SELECT md5, expire_time FROM cache WHERE md5 > ? ORDER BY md5 LIMIT ?");
$stmt->execute([$lastMd5, $limit]);
$rows = $stmt->fetchAll();
if (empty($rows)) break;
$toDelete = [];
foreach ($rows as $row) {
if ($row['expire_time'] > 0 && $row['expire_time'] < $now) {
$toDelete[] = $row['md5'];
}
}
if (!empty($toDelete)) {
$in = implode(',', array_fill(0, count($toDelete), '?'));
$del = $pdo->prepare("DELETE FROM cache WHERE md5 IN ($in)");
$del->execute($toDelete);
$totalDeleted += $del->rowCount();
}
$lastMd5 = end($rows)['md5'];
} while (true);
unset($pdo);
return $totalDeleted;
} catch (Exception $e) {
error_log("清理失败 $dbFile: " . $e->getMessage());
return 0;
}
}
function countAllRecords($dbFiles) {
$total = 0;
foreach ($dbFiles as $dbFile) {
try {
$pdo = new PDO("sqlite:" . $dbFile);
$total += (int) $pdo->query("SELECT COUNT(*) FROM cache")->fetchColumn();
unset($pdo);
} catch (Exception $e) {
// 忽略
}
}
return $total;
}
function deleteColdestRecords($dbFiles, $need) {
$dbStats = [];
$totalRecords = 0;
foreach ($dbFiles as $dbFile) {
try {
$pdo = new PDO("sqlite:" . $dbFile);
$count = (int) $pdo->query("SELECT COUNT(*) FROM cache")->fetchColumn();
$dbStats[$dbFile] = $count;
$totalRecords += $count;
unset($pdo);
} catch (Exception $e) {
// 忽略
}
}
if ($totalRecords == 0) return 0;
$deleted = 0;
foreach ($dbStats as $dbFile => $count) {
if ($count == 0) continue;
$deleteCount = (int) ceil($need * ($count / $totalRecords));
if ($deleteCount <= 0) continue;
try {
$pdo = new PDO("sqlite:" . $dbFile);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$del = $pdo->prepare("DELETE FROM cache ORDER BY access_count ASC, first_time ASC LIMIT ?");
$del->execute([$deleteCount]);
$deleted += $del->rowCount();
unset($pdo);
} catch (Exception $e) {
// 忽略
}
}
// 补删
if ($deleted < $need) {
$remaining = $need - $deleted;
foreach ($dbFiles as $dbFile) {
if ($remaining <= 0) break;
try {
$pdo = new PDO("sqlite:" . $dbFile);
$del = $pdo->prepare("DELETE FROM cache ORDER BY access_count ASC, first_time ASC LIMIT 1");
$del->execute();
if ($del->rowCount() > 0) {
$deleted++;
$remaining--;
}
unset($pdo);
} catch (Exception $e) {
// 忽略
}
}
}
return $deleted;
}
function removeEmptyDirs($dir, $isRoot = true) {
if (!is_dir($dir)) return;
foreach (scandir($dir) as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
removeEmptyDirs($path, false);
}
}
if (!$isRoot && count(scandir($dir)) == 2) {
@rmdir($dir);
}
}
至此。
可以实现飞速加载页面。
当然,如果网站每天更新内容,可以不整页缓存。











暂无评论内容