本文目录导读:

在 PHP 项目中识别和批量删除冗余索引,可以遵循以下步骤,这里提供一个安全、可操作的流程,主要基于 MySQL(最常用),但思路也适用于其他数据库。
核心原则:先识别,再备份,后删除
千万不要直接在线上库执行删除,冗余索引的判断标准通常是:
- 最左前缀冗余:
index(a,b)包含了index(a),后者是冗余的。 - 主键与唯一索引冗余:
id是主键,且有一个唯一索引uniq(id),uniq(id)是冗余的。 - 过长前缀冗余:
index(a)和index(a(10))同时存在。 - 未被使用的索引:根据
performance_schema或慢查询日志,长期未被使用的索引。
第一步:使用 SQL 查询识别冗余索引
注意:MySQL 5.7+ / 8.0 没有内置的“冗余索引列表”视图,需要我们自己组合。
脚本 1:查找完全重复或前缀重复的索引
SELECT
a.TABLE_SCHEMA AS '数据库',
a.TABLE_NAME AS '表名',
a.INDEX_NAME AS '冗余索引名',
a.COLUMN_NAME AS '冗余列',
a.SEQ_IN_INDEX AS '列序号',
b.INDEX_NAME AS '可被替代的索引名',
b.COLUMN_NAME AS '替代索引列'
FROM
information_schema.STATISTICS a
INNER JOIN information_schema.STATISTICS b ON a.TABLE_SCHEMA = b.TABLE_SCHEMA
AND a.TABLE_NAME = b.TABLE_NAME
AND a.SEQ_IN_INDEX = b.SEQ_IN_INDEX -- 同一位置的列相同
AND a.COLUMN_NAME = b.COLUMN_NAME -- 列名相同
AND a.INDEX_NAME != b.INDEX_NAME -- 不同索引
WHERE
a.NON_UNIQUE = 1 -- 只关注非唯一索引(冗余主要发生在非唯一上)
AND a.INDEX_NAME NOT LIKE 'PRIMARY'
AND b.INDEX_NAME NOT LIKE 'PRIMARY'
ORDER BY
a.TABLE_SCHEMA, a.TABLE_NAME, a.INDEX_NAME, a.SEQ_IN_INDEX;
解释:
- 这个查询会找到那些第一列完全相同的索引对。
index(a,b)和index(a)都存在,它们的第一列都是a,就会被标记。- 注意:这个查询只能识别“第一列相同”的冗余。
index(a,b)和index(b)不会被识别(这是另一种冗余)。
脚本 2:更精确的“前缀包含”检测
-- 查找所有组合索引,并检查是否有更短的索引包含了它的前几列
SELECT
r.TABLE_SCHEMA,
r.TABLE_NAME,
r.INDEX_NAME AS '冗余索引',
r.COLUMN_NAME,
r.SEQ_IN_INDEX,
k.INDEX_NAME AS '替代索引'
FROM information_schema.STATISTICS r
JOIN information_schema.STATISTICS k
ON r.TABLE_SCHEMA = k.TABLE_SCHEMA
AND r.TABLE_NAME = k.TABLE_NAME
AND r.INDEX_NAME != k.INDEX_NAME
AND r.SEQ_IN_INDEX = k.SEQ_IN_INDEX
AND r.COLUMN_NAME = k.COLUMN_NAME
WHERE
r.NON_UNIQUE = 1
AND k.NON_UNIQUE = 1
AND r.INDEX_NAME NOT LIKE 'PRIMARY'
AND k.INDEX_NAME NOT LIKE 'PRIMARY'
-- 过滤:只显示组合索引 (SEQ_IN_INDEX > 1) 的子索引
AND EXISTS (
SELECT 1 FROM information_schema.STATISTICS sub
WHERE sub.TABLE_SCHEMA = r.TABLE_SCHEMA
AND sub.TABLE_NAME = r.TABLE_NAME
AND sub.INDEX_NAME = r.INDEX_NAME
AND sub.SEQ_IN_INDEX = 1
)
AND EXISTS (
SELECT 1 FROM information_schema.STATISTICS sub2
WHERE sub2.TABLE_SCHEMA = k.TABLE_SCHEMA
AND sub2.TABLE_NAME = k.TABLE_NAME
AND sub2.INDEX_NAME = k.INDEX_NAME
AND sub2.SEQ_IN_INDEX = 1
AND sub2.COLUMN_NAME = r.COLUMN_NAME
)
ORDER BY r.TABLE_SCHEMA, r.TABLE_NAME, r.INDEX_NAME, r.SEQ_IN_INDEX;
第二步:使用 PHP 脚本批量生成删除语句
上面的 SQL 查询会返回冗余索引的列表,你可以用 PHP 处理查询结果,并生成 DROP INDEX 语句。
示例 PHP 脚本 (index_cleaner.php)
<?php
/**
* 冗余索引批量清理生成器
* 用法: php index_cleaner.php
* 注意: 此脚本默认只输出 SQL,不会直接执行,如要执行,需解开最后注释。
*/
$dbHost = '127.0.0.1';
$dbUser = 'root';
$dbPass = 'your_password';
$dbName = 'your_database'; // 如果需要指定数据库,或设为 null 扫描所有
try {
$pdo = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4", $dbUser, $dbPass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die("数据库连接失败: " . $e->getMessage());
}
// --- 1. 查询冗余索引(使用上面的第一个 SQL 简化版)---
$sqlRedundantIndexes = "
SELECT
a.TABLE_SCHEMA,
a.TABLE_NAME,
a.INDEX_NAME AS redundant_index,
GROUP_CONCAT(DISTINCT a.COLUMN_NAME ORDER BY a.SEQ_IN_INDEX SEPARATOR ', ') AS redundant_columns,
b.INDEX_NAME AS suggested_index,
GROUP_CONCAT(DISTINCT b.COLUMN_NAME ORDER BY b.SEQ_IN_INDEX SEPARATOR ', ') AS suggested_columns
FROM information_schema.STATISTICS a
INNER JOIN information_schema.STATISTICS b
ON a.TABLE_SCHEMA = b.TABLE_SCHEMA
AND a.TABLE_NAME = b.TABLE_NAME
AND a.SEQ_IN_INDEX = b.SEQ_IN_INDEX
AND a.COLUMN_NAME = b.COLUMN_NAME
AND a.INDEX_NAME != b.INDEX_NAME
WHERE
a.NON_UNIQUE = 1
AND a.INDEX_NAME NOT LIKE 'PRIMARY'
AND b.INDEX_NAME NOT LIKE 'PRIMARY'
AND a.TABLE_SCHEMA NOT IN ('mysql','information_schema','performance_schema','sys')
GROUP BY
a.TABLE_SCHEMA, a.TABLE_NAME, a.INDEX_NAME, b.INDEX_NAME
HAVING
COUNT(*) > 1 -- 至少有两列相同,才可能是真正的冗余(非唯一索引)
ORDER BY
a.TABLE_SCHEMA, a.TABLE_NAME, a.INDEX_NAME;
";
$stmt = $pdo->query($sqlRedundantIndexes);
$redundantIndexes = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($redundantIndexes)) {
echo "✅ 未检测到明显的冗余索引。\n";
exit(0);
}
// --- 2. 去重并生成 SQL ---
$sqlStatements = [];
$processedPairs = []; // 防止重复生成 (表名+索引名)
foreach ($redundantIndexes as $index) {
$db = $index['TABLE_SCHEMA'];
$table = $index['TABLE_NAME'];
$redundantIndexName = $index['redundant_index'];
$pairKey = $db . '.' . $table . '.' . $redundantIndexName;
// 避免为同一个索引生成多次
if (isset($processedPairs[$pairKey])) {
continue;
}
$processedPairs[$pairKey] = true;
// 生成 DROP 语句
$dropSQL = "ALTER TABLE `{$db}`.`{$table}` DROP INDEX `{$redundantIndexName}`;";
$sqlStatements[] = [
'sql' => $dropSQL,
'reason' => "在 `{$db}`.`{$table}` 中,索引 `{$redundantIndexName}` ({$index['redundant_columns']}) 被 `{$index['suggested_index']}` ({$index['suggested_columns']}) 覆盖",
];
}
// --- 3. 输出预览 ---
echo "===============================\n";
echo "冗余索引清理计划(预览)\n";
echo "共发现 " . count($sqlStatements) . " 个可能冗余的索引\n";
echo "===============================\n\n";
foreach ($sqlStatements as $i => $item) {
echo "[" . ($i+1) . "] " . $item['sql'] . "\n";
echo " 原因: " . $item['reason'] . "\n\n";
}
// --- 4. 可选:批量执行(请谨慎!)---
// echo "\n是否执行这些 SQL?(yes/no): ";
// $input = trim(fgets(STDIN));
// if (strtolower($input) === 'yes') {
// echo "开始执行...\n";
// $pdo->beginTransaction();
// try {
// foreach ($sqlStatements as $item) {
// echo "执行: " . $item['sql'] . "\n";
// $pdo->exec($item['sql']);
// }
// $pdo->commit();
// echo "✅ 所有索引删除成功!\n";
// } catch (Exception $e) {
// $pdo->rollBack();
// echo "❌ 执行失败: " . $e->getMessage() . "\n";
// }
// } else {
// echo "已取消执行。\n";
// }
第三步:手动复核与安全删除
-
导出备份: 在执行任何修改前,备份表结构:
mysqldump -u root -p --no-data your_database > db_schema_backup.sql
-
检查执行计划: 针对要删除的索引,手动执行一个依赖它的查询的
EXPLAIN,确认删除后不会全表扫描。 -
逐步删除: 如果索引很多,不要一次性全部删除,建议分批次:
- 每次删除 3-5 个索引。
- 观察一段时间(如 1 小时)的 CPU、慢查询日志。
- 确认无异常,再继续下一批。
-
代码层面检查:
- 搜索项目代码中是否使用 FORCE INDEX 或 USE INDEX 强制指定索引。
- 搜索有没有隐式索引(如 ORM 框架自动生成的索引名)。
第四步:使用 Percona Toolkit(推荐)
如果允许安装工具,pt-duplicate-key-checker 是行业标准:
# 安装 wget percona.com/get/pt-duplicate-key-checker # 或使用 docker docker run --rm perconalab/pt-duplicate-key-checker h=localhost,u=root,p=yourpass # 生成删除 SQL (加上 --drop 参数会自动生成 ALTER TABLE DROP INDEX 语句) pt-duplicate-key-checker --databases your_database --drop
它将直接输出类似:
# ALTER TABLE `your_db`.`users` DROP INDEX `idx_username`; # 冗余于 `uniq_username`
总结建议
- 最安全的方法: 使用
pt-duplicate-key-checker输出报告,由人工逐条确认后执行。 - 不要相信 100% 自动化: 数据库的索引优化有时取决于具体的查询模式,工具无法完全理解业务逻辑(唯一索引虽然冗余,但可能是为了
INSERT ... ON DUPLICATE KEY UPDATE等特定场景)。 - 保留唯一/主键: 永远不要删除 PRIMAY KEY 或 UNIQUE 约束索引(除非你知道自己在做什么)。
- 监控: 删除后,继续监控
performance_schema.table_io_waits_summary_by_index_usage以确保没有索引被意外删除后导致性能下降。
通过以上步骤,你可以安全、系统化地清理 PHP 项目中的冗余索引。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播