PHP项目如何优化数据库查询缓存?

wen PHP项目 40

本文目录导读:

PHP项目如何优化数据库查询缓存?

  1. 应用层缓存(最常用、最灵活)
  2. 数据库查询缓存机制(MySQL 8.0 之前版本)
  3. 数据库端查询优化
  4. 使用 PDO 的持久连接与缓冲查询
  5. 预编译语句(Prepared Statements)
  6. 使用数据库连接池(高并发场景)
  7. 具体场景的缓存策略
  8. 监控与诊断工具
  9. 最佳实践路线图

在PHP项目中优化数据库查询缓存,可以从应用层缓存数据库内置缓存查询优化以及架构层面四个维度入手,以下是系统性的优化策略和具体实现方案:

应用层缓存(最常用、最灵活)

这是将查询结果直接存储到内存中,避免重复执行SQL。

a. 使用 Redis / Memcached(推荐)

将复杂的、耗时且不频繁更新的查询结果缓存起来。

// 查询缓存示例(使用 Redis)
function getCachedUserList($db, $redis) {
    $cacheKey = 'user:active:list';
    $data = $redis->get($cacheKey);
    if (!$data) {
        // 缓存未命中,执行数据库查询
        $stmt = $db->query("SELECT id, name FROM users WHERE status = 1");
        $data = $stmt->fetchAll(PDO::FETCH_ASSOC);
        // 设置缓存,过期时间 300 秒
        $redis->setex($cacheKey, 300, serialize($data));
    } else {
        $data = unserialize($data);
    }
    return $data;
}

关键点:

  • 缓存键设计:使用 函数名_参数MD5SQL摘要 作为键。
  • 过期策略:根据数据更新频率设置 TTL(如 60s~1h)。
  • 缓存穿透:对于空结果也要缓存(如空数组),防止恶意请求击穿数据库。
  • 缓存雪崩:设置随机过期时间(如 base_ttl + rand(0, 120))。

b. 使用文件缓存(适用于简单场景、无Redis时)

对于低并发、服务器资源有限的情况,可以使用文件缓存。

function getExpensiveData() {
    $cacheFile = '/tmp/data_cache_' . md5('query_key') . '.cache';
    $cacheTime = 300; // 缓存5分钟
    if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTime)) {
        return file_get_contents($cacheFile);
    }
    // 执行数据库查询
    $data = $db->query("SELECT ...")->fetchAll();
    file_put_contents($cacheFile, serialize($data));
    return $data;
}

数据库查询缓存机制(MySQL 8.0 之前版本)

MySQL 的 Query Cache(查询缓存)在 8.0 版本已被移除,因为在高并发场景下性能反而下降,如果你仍在使用 MySQL 5.7 及以下,可以启用:

# my.cnf 配置
query_cache_type = 1
query_cache_size = 64M  # 根据内存情况调整
query_cache_limit = 2M  # 单个查询结果最大缓存大小

注意: 表一旦有写操作(INSERT/UPDATE/DELETE),该表的所有缓存都会失效,适合读多写少的场景。

数据库端查询优化

缓存不是银弹,优化查询本身可以减少不必要的数据库压力。

a. 使用 Explain 分析慢查询

EXPLAIN SELECT * FROM users WHERE status = 1 ORDER BY created_at DESC;
  • 检查 type 是否为 ALL(全表扫描)或 index
  • 检查 Extra 中是否有 Using filesort(需要优化索引)。

b. 合理创建索引

-- 复合索引:最左前缀原则
ALTER TABLE orders ADD INDEX idx_user_status_date (user_id, status, created_at);
-- 覆盖索引:避免回表查询
ALTER TABLE users ADD INDEX idx_cover (id, name, email) WHERE status = 1;

c. 避免重复查询

// 错误:循环中多次查询数据库
$userIds = [1,2,3,4,5];
foreach ($userIds as $id) {
    $stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->execute([$id]);
    $users[] = $stmt->fetch();
}
// 正确:一次查询,IN 子句
$placeholders = implode(',', array_fill(0, count($userIds), '?'));
$stmt = $db->prepare("SELECT * FROM users WHERE id IN ($placeholders)");
$stmt->execute($userIds);
$users = $stmt->fetchAll();

使用 PDO 的持久连接与缓冲查询

持久连接:减少建立/关闭连接的开销(适用于长连接场景,如 Swoole / Workerman)。

$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass', [
    PDO::ATTR_PERSISTENT => true
]);

缓冲查询:默认 PDO 使用缓冲模式(所有结果一次性返回),适用于结果集较小的查询,如果结果集非常大(百万级),可以使用非缓冲模式:

$stmt = $db->prepare("SELECT * FROM large_table", [
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false
]);
// 此时逐行获取,避免内存溢出

预编译语句(Prepared Statements)

不仅防止 SQL 注入,还能让 MySQL 缓存执行计划,对相同结构的查询加速。

// 预编译语句的缓存
$stmt = $db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute(['user1@example.com']);
$stmt->execute(['user2@example.com']); // 第二次执行时,SQL解析被复用

使用数据库连接池(高并发场景)

PHP 传统模型(如 FPM)每个请求独立连接,连接池提升有限,但在 Swoole / Workerman / Hyperf 等常驻内存框架中非常有效:

// Hyperf 的数据库连接池配置
return [
    'default' => [
        'driver' => 'pdo',
        'host' => '127.0.0.1',
        'pool' => [
            'min_connections' => 5,
            'max_connections' => 50,
        ],
        'cache' => [
            'adapter' => 'redis', // 配合查询缓存
        ],
    ],
];

具体场景的缓存策略

数据类型 缓存策略 过期时间 示例
用户会话 Redis + Hash 30分钟 session:user_id
文章列表 Redis List + 分页 5分钟 posts:page:1
计数器(点赞) Redis INCR 同步到DB post:123:likes
配置信息 Redis String 1小时 site:config:json
统计报表 文件缓存 + 定时任务 1天 report/daily_20241005.cache

监控与诊断工具

  • 慢查询日志SET GLOBAL slow_query_log = 1;
  • EXPLAIN 分析:逐步优化索引。
  • Redis 监控INFO commandstats 查看执行次数。
  • APM 工具:如 SkyWalkingPinpoint 检测数据库调用。

最佳实践路线图

  1. 先优化 SQL 和索引(成本最低,效果最明显)。
  2. 对热点查询(如首页、列表页)添加 Redis 缓存
  3. 为缓存设置合理的过期时间和防御策略(防穿透、防雪崩)。
  4. 如果使用 MySQL 5.7,可考虑 Query Cache;8.0 则忽略
  5. 高并发场景下考虑 Swoole + 连接池
  6. 持续监控,对慢查询进一步优化

通过以上方法,你可以将大部分数据库查询命中率提升到 85% 以上,有效降低数据库 CPU 和 IO 压力。

抱歉,评论功能暂时关闭!