本文目录导读:

针对PHP项目的索引优化,通常需要结合慢查询日志、执行计划和业务查询模式来新增或调整索引,以下是分步骤的最佳实践:
定位需要优化的查询语句
开启MySQL慢查询日志
-- 查看是否开启 SHOW VARIABLES LIKE 'slow_query_log%'; -- 临时开启(生产环境谨慎使用) SET GLOBAL slow_query_log = 1; SET GLOBAL long_query_time = 2; -- 超过2秒记录 SET GLOBAL log_queries_not_using_indexes = 1;
在PHP代码中临时开启慢查询记录
// 在需要调试的代码段前
$pdo->exec("SET SESSION long_query_time = 1");
$pdo->exec("SET SESSION slow_query_log = 1");
// 执行你的查询
// 查看日志文件
使用EXPLAIN分析查询
EXPLAIN SELECT * FROM users WHERE status = 'active' AND created_at > '2024-01-01' ORDER BY last_login DESC;
重点关注字段:
type: 全表扫描(ALL) → 需要索引key: 当前使用的索引rows: 扫描行数Extra: 文件排序(Using filesort) → 需要排序索引
根据分析结果新增索引
WHERE条件查询
-- 原始慢查询 SELECT * FROM orders WHERE user_id = 123 AND status = 'completed'; -- 需要组合索引(注意字段顺序) ALTER TABLE orders ADD INDEX idx_user_status (user_id, status);
口诀:区分度高的放前面,等值查询放前面
排序查询
-- 产生文件排序 SELECT * FROM articles WHERE category_id = 5 ORDER BY created_at DESC; -- 索引优化(包含排序字段) ALTER TABLE articles ADD INDEX idx_category_created (category_id, created_at DESC);
模糊查询
-- 前缀模糊查询可用索引 SELECT * FROM products WHERE sku LIKE 'ABC%'; -- 后缀模糊不能用索引(考虑全文索引) SELECT * FROM products WHERE sku LIKE '%XYZ'; -- 优化方案:存储逆转后的字段 ALTER TABLE products ADD reversed_sku VARCHAR(255);
JOIN查询
-- 在两个表JOIN字段上添加索引 ALTER TABLE orders ADD INDEX idx_user_id (user_id); ALTER TABLE users ADD INDEX idx_id (id);
PHP代码中的索引优化实践
使用ORM框架的查询优化
// Laravel Eloquent - 添加索引建议
Schema::table('users', function (Blueprint $table) {
$table->index(['status', 'created_at']);
$table->index('email');
});
// 或通过迁移新增索引
php artisan make:migration add_index_to_users_table
// 在迁移文件中
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->index(['department_id', 'role']);
});
}
动态索引选择(备选方案)
// 根据查询条件动态选择最优索引
class QueryOptimizer {
public function buildIndexHint($conditions) {
if (isset($conditions['user_id']) && isset($conditions['status'])) {
return "USE INDEX (idx_user_status)";
}
return "";
}
}
$sql = "SELECT * FROM orders {$optimizer->buildIndexHint($where)} WHERE ...";
索引维护与监控
查看当前索引使用情况
-- 查看索引使用频率 SELECT * FROM sys.schema_index_statistics WHERE table_schema = 'your_database'; -- 查看未使用的索引 SELECT * FROM sys.schema_unused_indexes;
使用PHP脚本监控慢查询
// monitoring.php
$db = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
// 获取慢查询日志
$stmt = $db->query("SHOW GLOBAL STATUS LIKE 'Slow_queries'");
$row = $stmt->fetch();
echo "当前慢查询数: " . $row['Value'] . "\n";
// 获取最近慢查询
$stmt = $db->query("SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10");
while ($log = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo $log['sql_text'] . "\n";
}
索引新增检查清单
✅ 确认索引必要性:检查查询是否确实需要
✅ 选择合适的索引类型:B-Tree (普通)、复合、全文、空间
✅ 控制索引数量:单表不超过5-6个索引
✅ 考虑维护成本:写频繁的表要谨慎
✅ 测试新索引效果:通过执行计划对比
✅ 监控写入性能:索引新增后观察写操作延迟
✅ 文档化:记录为什么添加这个索引
索引调整案例
案例:文章列表页优化
// 原始查询(慢)
$articles = Article::where('status', 'published')
->whereIn('category_id', [1,2,3])
->orderBy('publish_time', 'desc')
->paginate(20);
// 执行计划显示:
// - 使用了status索引,但category和排序需要额外处理
// - 产生Using filesort
// 优化方案
ALTER TABLE articles ADD INDEX idx_category_status_time
(status, category_id, publish_time DESC);
// 或使用覆盖索引(如果只查少数字段)
ALTER TABLE articles ADD INDEX idx_cover_query
(status, category_id, publish_time DESC, title, summary);
验证优化效果
EXPLAIN SELECT * FROM articles WHERE status = 'published' AND category_id IN (1,2,3) ORDER BY publish_time DESC; -- 优化后应该显示: -- type: range -- key: idx_category_status_time -- Extra: Using index condition; Using filesort (如果IN查询)
工具推荐
- Percona Toolkit -
pt-query-digest分析慢查询日志 - MySQL Workbench - 可视化执行计划
- Laravel Debugbar - 实时查询监控
- phpMyAdmin - 索引管理界面
最终建议:建立索引变更流程,先在测试环境复现慢查询,添加索引后对比执行计划,确认优化后再灰度上线到生产环境,对于关键查询,可建立持续监控,当扫描行数突然增加时触发告警。