PHP8+MySQL8下如何避免客户端每次连接重复执行相同数据库查询?
解决PHP重复执行相同数据库查询的方案
你的核心问题在于:PHP是请求驱动的脚本语言,每个客户端请求都会启动独立的执行环境,静态变量、单例类的作用域仅限当前请求生命周期,请求结束后就会销毁,所以跨请求没法复用查询结果。下面给你几个直接可用的解决方案:
1. 文件缓存(快速上手)
把查询结果序列化后写入本地文件,下次请求优先读取文件,仅当文件不存在或过期时才重新查询数据库。
<?php $cacheFile = './guest_list_cache.php'; $cacheExpire = 3600; // 缓存有效期1小时 // 优先读取缓存 if (file_exists($cacheFile) && (time() - filemtime($cacheFile)) < $cacheExpire) { $resultData = include($cacheFile); } else { // 数据库查询逻辑 $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; $conn = new mysqli($servername, $username, $password, $dbname); if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT id, firstname, lastname FROM MyGuests"; $result = $conn->query($sql); $resultData = []; if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { $resultData[] = $row; } } $conn->close(); // 将结果写入缓存文件 file_put_contents($cacheFile, '<?php return ' . var_export($resultData, true) . ';'); } // 输出数据 if (!empty($resultData)) { foreach ($resultData as $row) { echo "id: " . $row["id"]. " - Name: " . $row["firstname"]. " " . $row["lastname"]. "<br>"; } } else { echo "0 results"; } ?>
2. 内存缓存(生产环境推荐)
用Redis/Memcached这类内存缓存工具,比文件缓存性能更高,适合高并发场景。下面是Redis的实现示例(需提前安装php-redis扩展):
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $cacheKey = 'guest_list'; $cacheExpire = 3600; // 尝试从Redis获取缓存 $resultData = $redis->get($cacheKey); if (!$resultData) { // 数据库查询逻辑同前 $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; $conn = new mysqli($servername, $username, $password, $dbname); if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT id, firstname, lastname FROM MyGuests"; $result = $conn->query($sql); $resultData = []; if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { $resultData[] = $row; } } $conn->close(); // 序列化后存入Redis并设置过期时间 $redis->setex($cacheKey, $cacheExpire, json_encode($resultData)); } else { // 反序列化缓存数据 $resultData = json_decode($resultData, true); } // 输出数据 if (!empty($resultData)) { foreach ($resultData as $row) { echo "id: " . $row["id"]. " - Name: " . $row["firstname"]. " " . $row["lastname"]. "<br>"; } } else { echo "0 results"; } ?>
3. 进程预加载(常驻进程场景)
如果你的PHP运行在FPM常驻进程模式,或者使用Swoole/Workerman这类框架,可以在进程启动时提前查询数据并存在全局内存里,每个进程只查一次,后续请求直接复用。
示例(FPM环境)
- 写一个预加载脚本
preload.php:
<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; $conn = new mysqli($servername, $username, $password, $dbname); if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } $sql = "SELECT id, firstname, lastname FROM MyGuests"; $result = $conn->query($sql); $GLOBALS['guestList'] = []; if ($result->num_rows > 0) { while($row = $result->fetch_assoc()) { $GLOBALS['guestList'][] = $row; } } $conn->close(); ?>
在
php-fpm.conf里配置pm.preload = /path/to/preload.php,重启FPM生效。业务代码直接调用全局变量:
<?php $resultData = $GLOBALS['guestList'] ?? []; // 输出数据 if (!empty($resultData)) { foreach ($resultData as $row) { echo "id: " . $row["id"]. " - Name: " . $row["firstname"]. " " . $row["lastname"]. "<br>"; } } else { echo "0 results"; } ?>
补充:为什么单例没用?
PHP每个请求都是独立的执行上下文,静态变量、单例对象仅在当前请求内有效,请求结束后就会被销毁,跨请求无法共享状态,这就是单例对你的场景无效的核心原因。
内容的提问来源于stack exchange,提问作者rog
相关产品推荐
相关产品推荐

