慢查询日志是识别数据库性能瓶颈的核心工具,需先配置MySQL记录执行时间超过阈值的查询。
编辑MySQL配置文件(路径通常为/etc/mysql/mysql.conf.d/mysqld.cnf),添加或修改以下参数:
[mysqld]
slow_query_log = 1 # 开启慢查询日志
slow_query_log_file = /var/log/mysql/slow.log # 日志文件路径
long_query_time = 2 # 慢查询阈值(单位:秒,默认10秒,建议根据业务调整)
log_queries_not_using_indexes = 1 # 记录未使用索引的查询(可选,帮助优化索引)
保存后重启MySQL服务使配置生效:
sudo systemctl restart mysql
PHP-FPM的慢日志可记录执行时间过长的PHP请求(包括数据库查询),帮助定位代码中的性能问题。
编辑PHP-FPM池配置文件(路径如/etc/php/7.4/fpm/pool.d/www.conf,根据PHP版本调整),添加或修改以下参数:
[www]
request_slowlog_timeout = 1s # 慢请求阈值(单位:秒,建议从1秒开始逐步调整)
slowlog = /var/log/php-fpm/www-slow.log # 慢日志文件路径
重启PHP-FPM服务生效:
sudo systemctl restart php7.4-fpm
使用tail命令实时查看慢日志,快速定位慢请求:
# 查看MySQL慢日志
tail -f /var/log/mysql/slow.log
# 查看PHP-FPM慢日志
tail -f /var/log/php-fpm/www-slow.log
使用mysqldumpslow(MySQL自带工具)或pt-query-digest(Percona Toolkit工具)分析慢日志,找出高频慢查询。
示例(使用mysqldumpslow):
# 按查询时间排序,显示前10条慢查询
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 显示慢查询的详细信息(包括执行时间、执行次数、SQL语句)
mysqldumpslow -v -t 5 /var/log/mysql/slow.log
索引是提升查询速度的关键,需为经常用于WHERE、JOIN、ORDER BY的字段添加索引。
EXPLAIN命令分析查询执行计划,确认是否使用了索引:EXPLAIN SELECT * FROM users WHERE email = 'test@example.com';
若type列为ALL(全表扫描),则需添加索引:ALTER TABLE users ADD INDEX idx_email (email);
-- 不推荐
SELECT * FROM orders WHERE user_id = 1;
-- 推荐
SELECT id, order_number, amount FROM orders WHERE user_id = 1;
INSERT INTO ... VALUES (...), (...), ...或批量更新。OPTIMIZE TABLE命令,整理表碎片,提升查询效率(适用于InnoDB、MyISAM表)。OPTIMIZE TABLE users;
避免在每次请求中创建新的数据库连接,使用连接池(如PHP-FPM的pm配置)或持久连接(PDO的PDO::ATTR_PERSISTENT属性)。
示例(PDO持久连接):
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password', [
PDO::ATTR_PERSISTENT => true // 开启持久连接
]);
预处理语句可防止SQL注入,并提高查询效率(避免SQL编译)。
示例(PDO预处理):
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $userEmail]);
$results = $stmt->fetchAll();
避免在循环中执行单条SQL,改用批量操作或JOIN查询。
示例(优化前:循环中查询):
foreach ($userIds as $userId) {
$stmt = $pdo->prepare('SELECT name FROM users WHERE id = :id');
$stmt->execute(['id' => $userId]);
$name = $stmt->fetchColumn();
}
优化后(批量查询):
$placeholders = implode(',', array_fill(0, count($userIds), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM users WHERE id IN ($placeholders)");
$stmt->execute($userIds);
$users = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); // 返回[id => name]数组
php.ini文件:[opcache]
zend_extension=opcache.so
opcache.enable=1
opcache.memory_consumption=128 # 缓存大小(MB,根据服务器内存调整)
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60 # 脚本更新检查频率(秒)
重启PHP-FPM生效:sudo systemctl restart php7.4-fpm
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$cacheKey = "user_{$userId}_data";
if ($redis->exists($cacheKey)) {
$userData = json_decode($redis->get($cacheKey), true);
} else {
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $userId]);
$userData = $stmt->fetch();
$redis->setex($cacheKey, 3600, json_encode($userData)); // 缓存1小时
}
编辑PHP-FPM池配置文件(如/etc/php/7.4/fpm/pool.d/www.conf),优化以下参数:
[www]
pm = dynamic # 进程管理模式(动态调整进程数)
pm.max_children = 50 # 最大子进程数(根据服务器内存调整,每个进程约消耗10-20MB内存)
pm.start_servers = 5 # 启动时的子进程数
pm.min_spare_servers = 5 # 最小空闲子进程数(避免频繁创建进程)
pm.max_spare_servers = 35 # 最大空闲子进程数(避免占用过多内存)
pm.max_requests = 1000 # 每个子进程处理的最大请求数(避免内存泄漏)
重启PHP-FPM生效:
sudo systemctl restart php7.4-fpm
根据业务需求调整request_terminate_timeout参数,避免长时间运行的脚本占用资源:
request_terminate_timeout = 30s # 超时时间(秒,建议设置为30-60秒)
mysqldumpslow、pt-query-digest(Percona Toolkit)、Analyzed(Composer包)。