索引优化
概述
索引是数据库性能优化的核心工具,正确的索引策略可以将查询性能提升几个数量级。本文详细介绍如何在Node.js应用中进行索引设计、创建、监控和优化。
索引基础理论
1. 索引类型和特点
javascript
// 索引类型分析器
class IndexTypeAnalyzer {
constructor() {
this.indexTypes = {
btree: {
name: 'B-Tree索引',
description: '最常用的索引类型,适用于等值查询和范围查询',
advantages: ['支持排序', '支持范围查询', '支持前缀匹配'],
disadvantages: ['存储开销大', '维护成本高'],
bestFor: ['等值查询', '范围查询', 'ORDER BY', 'GROUP BY']
},
hash: {
name: '哈希索引',
description: '基于哈希表的索引,只支持等值查询',
advantages: ['等值查询极快', '存储空间小'],
disadvantages: ['不支持范围查询', '不支持排序', '不支持前缀匹配'],
bestFor: ['等值查询', '唯一性约束']
},
fulltext: {
name: '全文索引',
description: '专门用于文本搜索的索引',
advantages: ['支持文本搜索', '支持相关性排序'],
disadvantages: ['存储开销大', '维护复杂'],
bestFor: ['全文搜索', '模糊匹配']
},
spatial: {
name: '空间索引',
description: '用于地理位置数据的索引',
advantages: ['支持空间查询', '支持地理位置计算'],
disadvantages: ['特定用途', '复杂度高'],
bestFor: ['地理位置查询', '空间范围查询']
}
};
}
// 推荐索引类型
recommendIndexType(queryPattern, dataType, cardinality) {
const recommendations = [];
if (queryPattern.includes('=') && !queryPattern.includes('LIKE')) {
if (cardinality > 0.8) { // 高基数
recommendations.push({
type: 'btree',
confidence: 0.9,
reason: '等值查询且数据基数高,B-Tree索引最适合'
});
} else {
recommendations.push({
type: 'hash',
confidence: 0.7,
reason: '等值查询且数据基数低,哈希索引可能更高效'
});
}
}
if (queryPattern.includes('BETWEEN') || queryPattern.includes('>') || queryPattern.includes('<')) {
recommendations.push({
type: 'btree',
confidence: 0.95,
reason: '范围查询必须使用B-Tree索引'
});
}
if (queryPattern.includes('ORDER BY')) {
recommendations.push({
type: 'btree',
confidence: 0.9,
reason: '排序操作需要B-Tree索引'
});
}
if (queryPattern.includes('LIKE') && dataType === 'text') {
if (queryPattern.includes('%')) {
recommendations.push({
type: 'fulltext',
confidence: 0.8,
reason: '模糊匹配建议使用全文索引'
});
} else {
recommendations.push({
type: 'btree',
confidence: 0.7,
reason: '前缀匹配可以使用B-Tree索引'
});
}
}
return recommendations.sort((a, b) => b.confidence - a.confidence);
}
// 分析索引效果
analyzeIndexEffectiveness(indexStats) {
const analysis = {
utilization: 0,
selectivity: 0,
effectiveness: 'unknown',
recommendations: []
};
// 计算索引利用率
if (indexStats.totalQueries > 0) {
analysis.utilization = (indexStats.indexUsage / indexStats.totalQueries) * 100;
}
// 计算索引选择性
if (indexStats.totalRows > 0) {
analysis.selectivity = indexStats.distinctValues / indexStats.totalRows;
}
// 评估索引效果
if (analysis.utilization > 80 && analysis.selectivity > 0.5) {
analysis.effectiveness = 'excellent';
} else if (analysis.utilization > 50 && analysis.selectivity > 0.3) {
analysis.effectiveness = 'good';
} else if (analysis.utilization > 20 && analysis.selectivity > 0.1) {
analysis.effectiveness = 'fair';
} else {
analysis.effectiveness = 'poor';
}
// 生成建议
if (analysis.utilization < 10) {
analysis.recommendations.push('索引使用率过低,考虑删除该索引');
}
if (analysis.selectivity < 0.1) {
analysis.recommendations.push('索引选择性过低,考虑创建复合索引');
}
if (analysis.effectiveness === 'poor') {
analysis.recommendations.push('索引效果不佳,需要重新设计索引策略');
}
return analysis;
}
}
2. 索引设计原则
javascript
// 索引设计顾问
class IndexDesignAdvisor {
constructor() {
this.designPrinciples = [
{
name: 'selectivity',
description: '索引选择性原则',
rule: '优先为高选择性字段创建索引',
checker: (column) => this.checkSelectivity(column)
},
{
name: 'cardinality',
description: '基数原则',
rule: '高基数字段更适合创建索引',
checker: (column) => this.checkCardinality(column)
},
{
name: 'query_frequency',
description: '查询频率原则',
rule: '为频繁查询的字段创建索引',
checker: (column) => this.checkQueryFrequency(column)
},
{
name: 'column_order',
description: '列顺序原则',
rule: '复合索引中选择性高的列应放在前面',
checker: (columns) => this.checkColumnOrder(columns)
},
{
name: 'covering_index',
description: '覆盖索引原则',
rule: '考虑创建覆盖索引减少回表操作',
checker: (query) => this.checkCoveringIndex(query)
}
];
}
// 检查选择性
checkSelectivity(column) {
const selectivity = column.distinctValues / column.totalRows;
return {
score: Math.min(selectivity * 100, 100),
recommendation: selectivity > 0.5 ?
'选择性良好,适合创建索引' :
'选择性较低,考虑与其他列组合创建复合索引',
priority: selectivity > 0.5 ? 'high' : 'medium'
};
}
// 检查基数
checkCardinality(column) {
const cardinality = column.distinctValues;
const cardinalityRatio = cardinality / column.totalRows;
let score = 0;
let recommendation = '';
let priority = 'low';
if (cardinalityRatio > 0.8) {
score = 90;
recommendation = '基数很高,强烈建议创建索引';
priority = 'high';
} else if (cardinalityRatio > 0.5) {
score = 70;
recommendation = '基数较高,建议创建索引';
priority = 'medium';
} else if (cardinalityRatio > 0.1) {
score = 40;
recommendation = '基数一般,可考虑创建复合索引';
priority = 'medium';
} else {
score = 20;
recommendation = '基数较低,不建议单独创建索引';
priority = 'low';
}
return { score, recommendation, priority };
}
// 检查查询频率
checkQueryFrequency(column) {
const frequency = column.queryCount || 0;
const totalQueries = column.totalQueries || 1;
const frequencyRatio = frequency / totalQueries;
let score = Math.min(frequencyRatio * 100, 100);
let recommendation = '';
let priority = 'low';
if (frequencyRatio > 0.5) {
recommendation = '查询频率很高,强烈建议创建索引';
priority = 'high';
} else if (frequencyRatio > 0.2) {
recommendation = '查询频率较高,建议创建索引';
priority = 'medium';
} else if (frequencyRatio > 0.05) {
recommendation = '查询频率一般,可考虑创建索引';
priority = 'medium';
} else {
recommendation = '查询频率较低,不建议创建索引';
priority = 'low';
}
return { score, recommendation, priority };
}
// 检查列顺序
checkColumnOrder(columns) {
const sortedBySelectivity = [...columns].sort((a, b) => b.selectivity - a.selectivity);
const isOptimal = JSON.stringify(columns.map(c => c.name)) ===
JSON.stringify(sortedBySelectivity.map(c => c.name));
return {
score: isOptimal ? 100 : 60,
recommendation: isOptimal ?
'列顺序已优化' :
`建议调整列顺序为: ${sortedBySelectivity.map(c => c.name).join(', ')}`,
priority: isOptimal ? 'low' : 'medium'
};
}
// 检查覆盖索引
checkCoveringIndex(query) {
const selectColumns = this.extractSelectColumns(query.sql);
const whereColumns = this.extractWhereColumns(query.sql);
const allColumns = [...new Set([...selectColumns, ...whereColumns])];
return {
score: 70,
recommendation: `考虑创建覆盖索引包含列: ${allColumns.join(', ')}`,
priority: 'medium',
suggestedColumns: allColumns
};
}
// 提取SELECT列
extractSelectColumns(sql) {
const selectMatch = sql.match(/SELECT\s+(.*?)\s+FROM/i);
if (!selectMatch) return [];
const selectClause = selectMatch[1];
if (selectClause.includes('*')) return [];
return selectClause.split(',').map(col => col.trim().replace(/.*\./, ''));
}
// 提取WHERE列
extractWhereColumns(sql) {
const whereMatch = sql.match(/WHERE\s+(.*?)(?:\s+GROUP\s+BY|\s+ORDER\s+BY|\s+LIMIT|$)/i);
if (!whereMatch) return [];
const whereClause = whereMatch[1];
const columns = [];
// 简单的列提取(实际应该用更复杂的解析器)
const columnMatches = whereClause.match(/(\w+)\s*[=<>!]/g);
if (columnMatches) {
columnMatches.forEach(match => {
const column = match.replace(/\s*[=<>!].*/, '').trim();
columns.push(column);
});
}
return columns;
}
// 生成索引设计建议
generateIndexDesign(tableAnalysis, queryPatterns) {
const recommendations = [];
// 分析每个查询模式
queryPatterns.forEach(pattern => {
const columns = this.analyzeQueryColumns(pattern);
columns.forEach(column => {
const columnStats = tableAnalysis.columns[column];
if (!columnStats) return;
const analysis = {
column: column,
selectivity: this.checkSelectivity(columnStats),
cardinality: this.checkCardinality(columnStats),
frequency: this.checkQueryFrequency(columnStats)
};
const totalScore = (analysis.selectivity.score +
analysis.cardinality.score +
analysis.frequency.score) / 3;
if (totalScore > 60) {
recommendations.push({
type: 'single_column',
column: column,
score: totalScore,
analysis: analysis,
sql: `CREATE INDEX idx_${tableAnalysis.tableName}_${column} ON ${tableAnalysis.tableName}(${column})`
});
}
});
// 分析复合索引机会
if (columns.length > 1) {
const compositeAnalysis = this.analyzeCompositeIndex(columns, tableAnalysis);
if (compositeAnalysis.score > 70) {
recommendations.push({
type: 'composite',
columns: columns,
score: compositeAnalysis.score,
analysis: compositeAnalysis,
sql: `CREATE INDEX idx_${tableAnalysis.tableName}_${columns.join('_')} ON ${tableAnalysis.tableName}(${columns.join(', ')})`
});
}
}
});
// 去重并排序
const uniqueRecommendations = this.deduplicateRecommendations(recommendations);
return uniqueRecommendations.sort((a, b) => b.score - a.score);
}
// 分析查询列
analyzeQueryColumns(queryPattern) {
const columns = [];
// 提取WHERE条件中的列
const whereColumns = this.extractWhereColumns(queryPattern.sql);
columns.push(...whereColumns);
// 提取ORDER BY中的列
const orderByMatch = queryPattern.sql.match(/ORDER\s+BY\s+([\w\s,]+)/i);
if (orderByMatch) {
const orderColumns = orderByMatch[1].split(',').map(col =>
col.trim().replace(/\s+(ASC|DESC)$/i, '').replace(/.*\./, '')
);
columns.push(...orderColumns);
}
return [...new Set(columns)];
}
// 分析复合索引
analyzeCompositeIndex(columns, tableAnalysis) {
let totalScore = 0;
let validColumns = 0;
columns.forEach(column => {
const columnStats = tableAnalysis.columns[column];
if (columnStats) {
const selectivity = this.checkSelectivity(columnStats);
const cardinality = this.checkCardinality(columnStats);
const frequency = this.checkQueryFrequency(columnStats);
totalScore += (selectivity.score + cardinality.score + frequency.score) / 3;
validColumns++;
}
});
const avgScore = validColumns > 0 ? totalScore / validColumns : 0;
const compositeBonus = columns.length > 1 ? 10 : 0; // 复合索引额外加分
return {
score: avgScore + compositeBonus,
columnCount: columns.length,
recommendation: `创建复合索引可以优化多列查询`,
priority: avgScore > 70 ? 'high' : 'medium'
};
}
// 去重建议
deduplicateRecommendations(recommendations) {
const seen = new Set();
return recommendations.filter(rec => {
const key = rec.type === 'single_column' ?
rec.column :
rec.columns.sort().join(',');
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}
}
索引创建和管理
1. 自动索引创建
javascript
// 自动索引创建器
class AutoIndexCreator {
constructor(pool, options = {}) {
this.pool = pool;
this.options = {
enableAutoCreation: options.enableAutoCreation || false,
dryRun: options.dryRun !== false,
maxIndexesPerTable: options.maxIndexesPerTable || 10,
minQueryCount: options.minQueryCount || 100,
minImprovement: options.minImprovement || 0.3
};
this.pendingIndexes = new Map();
this.createdIndexes = new Map();
}
// 分析并创建索引
async analyzeAndCreateIndexes(queryStats, tableStats) {
const recommendations = [];
// 分析每个表的查询模式
for (const [tableName, stats] of Object.entries(tableStats)) {
const tableRecommendations = await this.analyzeTableIndexes(tableName, stats, queryStats);
recommendations.push(...tableRecommendations);
}
// 按优先级排序
const sortedRecommendations = recommendations.sort((a, b) => b.priority - a.priority);
// 创建索引
const results = [];
for (const recommendation of sortedRecommendations.slice(0, 10)) { // 限制同时创建的索引数量
const result = await this.createIndexIfBeneficial(recommendation);
results.push(result);
}
return results;
}
// 分析表索引需求
async analyzeTableIndexes(tableName, tableStats, queryStats) {
const recommendations = [];
// 获取表的查询统计
const tableQueries = queryStats.filter(q =>
q.sql.toLowerCase().includes(tableName.toLowerCase())
);
if (tableQueries.length < this.options.minQueryCount) {
return recommendations; // 查询量不足,不建议创建索引
}
// 分析慢查询
const slowQueries = tableQueries.filter(q => q.executionTime > 1000);
for (const query of slowQueries) {
const indexRecommendation = await this.analyzeQueryForIndex(query, tableStats);
if (indexRecommendation) {
recommendations.push(indexRecommendation);
}
}
return recommendations;
}
// 分析查询的索引需求
async analyzeQueryForIndex(query, tableStats) {
try {
// 获取执行计划
const plan = await this.getExecutionPlan(query.sql, query.params);
if (!plan || !this.needsIndex(plan)) {
return null;
}
// 提取索引候选列
const candidateColumns = this.extractIndexCandidates(query.sql, plan);
if (candidateColumns.length === 0) {
return null;
}
// 计算预期改进
const expectedImprovement = this.estimateImprovement(plan, candidateColumns);
if (expectedImprovement < this.options.minImprovement) {
return null;
}
return {
tableName: this.extractTableName(query.sql),
columns: candidateColumns,
queryExample: query,
expectedImprovement: expectedImprovement,
priority: this.calculatePriority(query, expectedImprovement),
indexType: this.determineIndexType(candidateColumns, query.sql),
estimatedSize: this.estimateIndexSize(candidateColumns, tableStats)
};
} catch (error) {
console.error('分析查询索引需求失败:', error);
return null;
}
}
// 获取执行计划
async getExecutionPlan(sql, params) {
const connection = await this.pool.getConnection();
try {
const [result] = await connection.execute(`EXPLAIN FORMAT=JSON ${sql}`, params);
return JSON.parse(result[0]['EXPLAIN']);
} catch (error) {
console.error('获取执行计划失败:', error);
return null;
} finally {
connection.release();
}
}
// 判断是否需要索引
needsIndex(plan) {
const issues = this.extractPlanIssues(plan);
return issues.some(issue =>
issue.includes('全表扫描') ||
issue.includes('Using filesort') ||
issue.includes('Using temporary')
);
}
// 提取执行计划问题
extractPlanIssues(plan) {
const issues = [];
const traversePlan = (node) => {
if (node.table) {
if (node.table.access_type === 'ALL') {
issues.push(`表 ${node.table.table_name} 全表扫描`);
}
if (node.table.extra) {
if (node.table.extra.includes('Using filesort')) {
issues.push(`表 ${node.table.table_name} 使用文件排序`);
}
if (node.table.extra.includes('Using temporary')) {
issues.push(`表 ${node.table.table_name} 使用临时表`);
}
}
}
if (node.nested_loop) {
node.nested_loop.forEach(item => traversePlan(item));
}
};
traversePlan(plan.query_block);
return issues;
}
// 提取索引候选列
extractIndexCandidates(sql, plan) {
const candidates = [];
// 从WHERE条件提取
const whereColumns = this.extractWhereColumns(sql);
candidates.push(...whereColumns);
// 从ORDER BY提取
const orderByMatch = sql.match(/ORDER\s+BY\s+([\w\s,]+)/i);
if (orderByMatch) {
const orderColumns = orderByMatch[1].split(',').map(col =>
col.trim().replace(/\s+(ASC|DESC)$/i, '').replace(/.*\./, '')
);
candidates.push(...orderColumns);
}
// 从GROUP BY提取
const groupByMatch = sql.match(/GROUP\s+BY\s+([\w\s,]+)/i);
if (groupByMatch) {
const groupColumns = groupByMatch[1].split(',').map(col =>
col.trim().replace(/.*\./, '')
);
candidates.push(...groupColumns);
}
return [...new Set(candidates)];
}
// 估算改进效果
estimateImprovement(plan, columns) {
let improvement = 0;
// 基于执行计划问题估算改进
const issues = this.extractPlanIssues(plan);
issues.forEach(issue => {
if (issue.includes('全表扫描')) {
improvement += 0.7; // 70%改进
}
if (issue.includes('文件排序')) {
improvement += 0.3; // 30%改进
}
if (issue.includes('临时表')) {
improvement += 0.4; // 40%改进
}
});
// 基于查询成本估算
const cost = plan.query_block?.cost_info?.query_cost || 0;
if (cost > 1000) {
improvement += 0.5;
} else if (cost > 100) {
improvement += 0.3;
}
return Math.min(improvement, 0.9); // 最大90%改进
}
// 计算优先级
calculatePriority(query, expectedImprovement) {
let priority = 0;
// 基于执行时间
if (query.executionTime > 5000) {
priority += 30;
} else if (query.executionTime > 1000) {
priority += 20;
} else {
priority += 10;
}
// 基于查询频率
const frequency = query.count || 1;
if (frequency > 1000) {
priority += 30;
} else if (frequency > 100) {
priority += 20;
} else {
priority += 10;
}
// 基于预期改进
priority += expectedImprovement * 40;
return Math.min(priority, 100);
}
// 确定索引类型
determineIndexType(columns, sql) {
if (columns.length === 1) {
if (sql.toLowerCase().includes('like')) {
return 'fulltext';
}
return 'btree';
} else {
return 'composite';
}
}
// 估算索引大小
estimateIndexSize(columns, tableStats) {
let estimatedSize = 0;
columns.forEach(column => {
const columnStats = tableStats.columns[column];
if (columnStats) {
// 简单估算:行数 * 平均列长度 * 索引开销系数
estimatedSize += tableStats.rowCount * (columnStats.avgLength || 10) * 1.2;
}
});
return Math.round(estimatedSize / 1024 / 1024); // 转换为MB
}
// 创建有益的索引
async createIndexIfBeneficial(recommendation) {
const indexName = `idx_auto_${recommendation.tableName}_${recommendation.columns.join('_')}`;
// 检查索引是否已存在
const existingIndex = await this.checkIndexExists(recommendation.tableName, indexName);
if (existingIndex) {
return {
success: false,
reason: '索引已存在',
recommendation: recommendation
};
}
// 检查表的索引数量限制
const indexCount = await this.getTableIndexCount(recommendation.tableName);
if (indexCount >= this.options.maxIndexesPerTable) {
return {
success: false,
reason: '表索引数量已达上限',
recommendation: recommendation
};
}
// 生成创建索引的SQL
const createSQL = this.generateCreateIndexSQL(indexName, recommendation);
if (this.options.dryRun) {
console.log('DRY RUN - 将创建索引:', createSQL);
return {
success: true,
dryRun: true,
sql: createSQL,
recommendation: recommendation
};
}
// 实际创建索引
if (this.options.enableAutoCreation) {
try {
const startTime = Date.now();
await this.executeCreateIndex(createSQL);
const creationTime = Date.now() - startTime;
// 记录创建的索引
this.createdIndexes.set(indexName, {
recommendation: recommendation,
createdAt: new Date(),
creationTime: creationTime
});
console.log(`索引创建成功: ${indexName}, 耗时: ${creationTime}ms`);
return {
success: true,
indexName: indexName,
creationTime: creationTime,
recommendation: recommendation
};
} catch (error) {
console.error('索引创建失败:', error);
return {
success: false,
error: error.message,
recommendation: recommendation
};
}
} else {
return {
success: false,
reason: '自动创建索引未启用',
recommendation: recommendation,
suggestedSQL: createSQL
};
}
}
// 检查索引是否存在
async checkIndexExists(tableName, indexName) {
const connection = await this.pool.getConnection();
try {
const [rows] = await connection.execute(
'SELECT COUNT(*) as count FROM information_schema.statistics WHERE table_name = ? AND index_name = ?',
[tableName, indexName]
);
return rows[0].count > 0;
} finally {
connection.release();
}
}
// 获取表的索引数量
async getTableIndexCount(tableName) {
const connection = await this.pool.getConnection();
try {
const [rows] = await connection.execute(
'SELECT COUNT(DISTINCT index_name) as count FROM information_schema.statistics WHERE table_name = ?',
[tableName]
);
return rows[0].count;
} finally {
connection.release();
}
}
// 生成创建索引SQL
generateCreateIndexSQL(indexName, recommendation) {
const columns = recommendation.columns.join(', ');
return `CREATE INDEX ${indexName} ON ${recommendation.tableName}(${columns})`;
}
// 执行创建索引
async executeCreateIndex(sql) {
const connection = await this.pool.getConnection();
try {
await connection.execute(sql);
} finally {
connection.release();
}
}
// 提取表名
extractTableName(sql) {
const fromMatch = sql.match(/FROM\s+(\w+)/i);
return fromMatch ? fromMatch[1] : 'unknown';
}
// 提取WHERE列
extractWhereColumns(sql) {
const whereMatch = sql.match(/WHERE\s+(.*?)(?:\s+GROUP\s+BY|\s+ORDER\s+BY|\s+LIMIT|$)/i);
if (!whereMatch) return [];
const whereClause = whereMatch[1];
const columns = [];
const columnMatches = whereClause.match(/(\w+)\s*[=<>!]/g);
if (columnMatches) {
columnMatches.forEach(match => {
const column = match.replace(/\s*[=<>!].*/, '').trim();
columns.push(column);
});
}
return columns;
}
}
索引监控和维护
1. 索引使用情况监控
javascript
// 索引使用监控器
class IndexUsageMonitor {
constructor(pool) {
this.pool = pool;
this.usageStats = new Map();
this.monitoringInterval = null;
}
// 开始监控
startMonitoring(interval = 300000) { // 5分钟
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
}
this.monitoringInterval = setInterval(async () => {
await this.collectIndexUsageStats();
}, interval);
console.log('索引使用监控已启动');
}
// 停止监控
stopMonitoring() {
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = null;
console.log('索引使用监控已停止');
}
}
// 收集索引使用统计
async collectIndexUsageStats() {
try {
const stats = await this.getIndexUsageFromDatabase();
stats.forEach(stat => {
const key = `${stat.table_schema}.${stat.table_name}.${stat.index_name}`;
const existing = this.usageStats.get(key) || {
schema: stat.table_schema,
table: stat.table_name,
index: stat.index_name,
columns: stat.columns,
usageHistory: []
};
existing.usageHistory.push({
timestamp: new Date(),
reads: stat.reads,
writes: stat.writes,
size: stat.size_mb
});
// 保持最近100条记录
if (existing.usageHistory.length > 100) {
existing.usageHistory.shift();
}
this.usageStats.set(key, existing);
});
console.log(`收集了 ${stats.length} 个索引的使用统计`);
} catch (error) {
console.error('收集索引使用统计失败:', error);
}
}
// 从数据库获取索引使用统计
async getIndexUsageFromDatabase() {
const connection = await this.pool.getConnection();
try {
// MySQL 5.7+的性能模式查询
const [rows] = await connection.execute(`
SELECT
s.table_schema,
s.table_name,
s.index_name,
GROUP_CONCAT(s.column_name ORDER BY s.seq_in_index) as columns,
COALESCE(ius.count_read, 0) as reads,
COALESCE(ius.count_write, 0) as writes,
ROUND(
(SELECT SUM(stat_value * @@innodb_page_size) / 1024 / 1024
FROM information_schema.innodb_index_stats
WHERE database_name = s.table_schema
AND table_name = s.table_name
AND index_name = s.index_name
AND stat_name = 'size'), 2
) as size_mb
FROM information_schema.statistics s
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage ius
ON s.table_schema = ius.object_schema
AND s.table_name = ius.object_name
AND s.index_name = ius.index_name
WHERE s.table_schema NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
GROUP BY s.table_schema, s.table_name, s.index_name
ORDER BY reads DESC
`);
return rows;
} finally {
connection.release();
}
}
// 分析索引使用效率
analyzeIndexEfficiency() {
const analysis = {
totalIndexes: this.usageStats.size,
unusedIndexes: [],
lowUsageIndexes: [],
highUsageIndexes: [],
recommendations: []
};
this.usageStats.forEach((stats, key) => {
const latestStats = stats.usageHistory[stats.usageHistory.length - 1];
if (!latestStats) return;
const usageScore = this.calculateUsageScore(stats);
if (usageScore === 0) {
analysis.unusedIndexes.push({
key: key,
stats: stats,
recommendation: '考虑删除未使用的索引'
});
} else if (usageScore < 10) {
analysis.lowUsageIndexes.push({
key: key,
stats: stats,
usageScore: usageScore,
recommendation: '使用率较低,评估是否需要保留'
});
} else if (usageScore > 80) {
analysis.highUsageIndexes.push({
key: key,
stats: stats,
usageScore: usageScore,
recommendation: '高使用率索引,保持现状'
});
}
});
// 生成总体建议
if (analysis.unusedIndexes.length > 0) {
analysis.recommendations.push({
type: 'cleanup',
description: `发现 ${analysis.unusedIndexes.length} 个未使用的索引,建议清理`,
priority: 'medium'
});
}
if (analysis.lowUsageIndexes.length > analysis.totalIndexes * 0.3) {
analysis.recommendations.push({
type: 'optimization',
description: '超过30%的索引使用率较低,建议优化索引策略',
priority: 'high'
});
}
return analysis;
}
// 计算使用评分
calculateUsageScore(indexStats) {
if (indexStats.usageHistory.length === 0) return 0;
const recentStats = indexStats.usageHistory.slice(-10); // 最近10次记录
const totalReads = recentStats.reduce((sum, stat) => sum + (stat.reads || 0), 0);
const totalWrites = recentStats.reduce((sum, stat) => sum + (stat.writes || 0), 0);
const totalUsage = totalReads + totalWrites;
if (totalUsage === 0) return 0;
// 基于使用频率计算分数
const avgUsage = totalUsage / recentStats.length;
if (avgUsage > 1000) return 100;
if (avgUsage > 100) return 80;
if (avgUsage > 10) return 60;
if (avgUsage > 1) return 40;
return 20;
}
// 获取索引使用报告
getUsageReport() {
const analysis = this.analyzeIndexEfficiency();
return {
summary: {
totalIndexes: analysis.totalIndexes,
unusedCount: analysis.unusedIndexes.length,
lowUsageCount: analysis.lowUsageIndexes.length,
highUsageCount: analysis.highUsageIndexes.length
},
unusedIndexes: analysis.unusedIndexes.map(item => ({
table: `${item.stats.schema}.${item.stats.table}`,
index: item.stats.index,
columns: item.stats.columns,
size: this.getLatestSize(item.stats),
recommendation: item.recommendation
})),
recommendations: analysis.recommendations,
topUsedIndexes: analysis.highUsageIndexes.slice(0, 10).map(item => ({
table: `${item.stats.schema}.${item.stats.table}`,
index: item.stats.index,
columns: item.stats.columns,
usageScore: item.usageScore,
size: this.getLatestSize(item.stats)
}))
};
}
// 获取最新大小
getLatestSize(indexStats) {
if (indexStats.usageHistory.length === 0) return 0;
const latest = indexStats.usageHistory[indexStats.usageHistory.length - 1];
return latest.size || 0;
}
// 生成清理建议
generateCleanupSuggestions() {
const analysis = this.analyzeIndexEfficiency();
const suggestions = [];
// 未使用的索引
analysis.unusedIndexes.forEach(item => {
suggestions.push({
type: 'drop',
priority: 'high',
table: `${item.stats.schema}.${item.stats.table}`,
index: item.stats.index,
reason: '索引从未被使用',
sql: `DROP INDEX ${item.stats.index} ON ${item.stats.schema}.${item.stats.table}`,
estimatedSavings: this.getLatestSize(item.stats)
});
});
// 低使用率的索引
analysis.lowUsageIndexes.forEach(item => {
if (item.usageScore < 5) {
suggestions.push({
type: 'consider_drop',
priority: 'medium',
table: `${item.stats.schema}.${item.stats.table}`,
index: item.stats.index,
reason: `索引使用率很低 (${item.usageScore}分)`,
sql: `-- 考虑删除: DROP INDEX ${item.stats.index} ON ${item.stats.schema}.${item.stats.table}`,
estimatedSavings: this.getLatestSize(item.stats)
});
}
});
return suggestions.sort((a, b) => {
const priorityOrder = { high: 3, medium: 2, low: 1 };
return priorityOrder[b.priority] - priorityOrder[a.priority];
});
}
}
总结
索引优化是数据库性能提升的关键技术:
- 科学设计:基于查询模式和数据特征设计索引
- 自动化管理:实现索引的自动分析、创建和维护
- 持续监控:监控索引使用情况和效果
- 定期优化:清理无用索引,优化索引结构
- 性能评估:量化索引对查询性能的影响
- 最佳实践:遵循索引设计的最佳实践原则
通过系统化的索引优化策略,可以显著提升数据库查询性能,减少资源消耗。