用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tools-only/X-Skills --skill index-advisor命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | index-advisor |
| description | Analyze query patterns and recommend optimal database indexes |
| shortcut | inde |
Analyze query workloads, identify missing indexes, detect unused indexes, and recommend optimal indexing strategies with automated index impact analysis and maintenance scheduling for production databases.
Use /index-advisor when you need to:
DON'T use this when:
This command implements workload-based index analysis because:
Alternative considered: Static schema analysis
Alternative considered: Manual EXPLAIN analysis
Before running this command:
Capture real production queries from logs or pg_stat_statements.
Run EXPLAIN ANALYZE to identify sequential scans and suboptimal query plans.
Identify missing indexes, composite index opportunities, and covering indexes.
Estimate query performance improvements with hypothetical indexes.
Create recommended indexes and track query performance improvements.
The command generates:
analysis/missing_indexes.sql - CREATE INDEX statements for missing indexesanalysis/unused_indexes.sql - DROP INDEX statements for unused indexesreports/index_impact_report.html - Visual impact analysis with before/after metricsmonitoring/index_health.sql - Queries to monitor index bloat and usagemaintenance/reindex_schedule.sh - Automated index maintenance script-- Enable pg_stat_statements extension for query tracking
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Configure extended statistics
ALTER SYSTEM SET pg_stat_statements.track = 'all';
ALTER SYSTEM SET pg_stat_statements.max = 10000;
SELECT pg_reload_conf();
-- View most expensive queries without proper indexes
CREATE OR REPLACE VIEW slow_queries_needing_indexes AS
SELECT
queryid,
LEFT(query, 100) AS query_snippet,
calls,
total_exec_time,
mean_exec_time,
max_exec_time,
stddev_exec_time,
ROUND((100.0 * total_exec_time / SUM(total_exec_time) OVER ()), 2) AS pct_total_time
FROM pg_stat_statements
WHERE query NOT LIKE '%pg_stat%'
AND mean_exec_time > 100 -- Queries averaging >100ms
ORDER BY total_exec_time DESC
LIMIT 50;
-- Identify missing indexes by analyzing sequential scans
CREATE OR REPLACE tables_needing_indexes
schemaname,
tablename,
seq_scan sequential_scans,
seq_tup_read rows_read_sequentially,
idx_scan index_scans,
idx_tup_fetch rows_fetched_via_index,
pg_size_pretty(pg_relation_size(schemanametablename)) table_size,
seq_scan ROUND( seq_scan (seq_scan (idx_scan, )), )
pct_sequential_scans
pg_stat_user_tables
seq_scan
seq_tup_read
seq_tup_read ;
REPLACE unused_indexes
schemaname,
tablename,
indexname,
idx_scan index_scans,
idx_tup_read tuples_read,
idx_tup_fetch tuples_fetched,
pg_size_pretty(pg_relation_size(indexrelid)) index_size,
pg_get_indexdef(indexrelid) index_definition
pg_stat_user_indexes
idx_scan
indexname
indexname
pg_relation_size(indexrelid) ;
REPLACE index_bloat_analysis
schemaname,
tablename,
indexname,
pg_size_pretty(pg_relation_size(indexrelid)) index_size,
pg_size_pretty(pg_relation_size(tablename::regclass)) table_size,
ROUND( pg_relation_size(indexrelid) (pg_relation_size(tablename::regclass), ), ) index_to_table_ratio,
pg_relation_size(indexrelid) pg_relation_size(tablename::regclass)
pg_relation_size(indexrelid) pg_relation_size(tablename::regclass)
bloat_status
pg_stat_user_indexes
pg_relation_size(indexrelid)
pg_relation_size(indexrelid) ;
# scripts/index_advisor.py - Comprehensive Index Analysis Tool
import psycopg2
from psycopg2.extras import DictCursor
import re
import logging
from typing import List, Dict, Tuple, Optional
from dataclasses import dataclass, asdict
from collections import defaultdict
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class IndexRecommendation:
"""Represents an index recommendation with impact analysis."""
table_name: str
recommended_index: str
reason: str
affected_queries: List[str]
estimated_speedup: str
storage_cost_mb: float
priority: str # 'high', 'medium', 'low'
def to_dict(self) -> dict:
return asdict(self)
class PostgreSQLIndexAdvisor:
"""Analyze queries and recommend optimal indexes."""
def __init__(self, connection_string: str):
self.conn_string = connection_string
def connect(self):
psycopg2.connect(.conn_string, cursor_factory=DictCursor)
() -> []:
conn = .connect()
:
conn.cursor() cur:
cur.execute(, (min_duration_ms,))
[(row) row cur.fetchall()]
:
conn.close()
() -> [[, ]]:
columns = []
where_pattern =
matches = re.finditer(where_pattern, query, re.IGNORECASE)
matches:
table = .group()
column = .group()
columns.append((table, column))
join_pattern =
matches = re.finditer(join_pattern, query, re.IGNORECASE)
matches:
table = .group()
column = .group()
columns.append((table, column))
columns
() -> :
conn = .connect()
:
conn.cursor() cur:
cur.execute(, (table, ))
result = cur.fetchone()
result[] result
:
conn.close()
() -> [IndexRecommendation]:
recommendations = []
slow_queries = .analyze_slow_queries()
logger.info()
column_usage = defaultdict(: {: , : [], : })
query_info slow_queries:
query = query_info[]
total_time = (query_info[])
columns = .extract_where_columns(query)
table, column columns:
.check_existing_indexes(table, column):
key =
column_usage[key][] +=
column_usage[key][].append(query[:])
column_usage[key][] += total_time
key, usage column_usage.items():
table, column = key.split()
usage[] > :
priority =
speedup =
usage[] > :
priority =
speedup =
:
priority =
speedup =
storage_cost = .estimate_index_size(table)
recommendation = IndexRecommendation(
table_name=table,
recommended_index=,
reason=,
affected_queries=usage[][:],
estimated_speedup=speedup,
storage_cost_mb=storage_cost,
priority=priority
)
recommendations.append(recommendation)
recommendations.sort(
key= r: (
{: , : , : }[r.priority],
-( _ r.affected_queries)
)
)
recommendations
() -> :
conn = .connect()
:
conn.cursor() cur:
cur.execute(, (table, table))
result = cur.fetchone()
result:
(result[] * , )
Exception e:
logger.warning()
:
conn.close()
() -> []:
conn = .connect()
:
conn.cursor() cur:
cur.execute()
[(row) row cur.fetchall()]
:
conn.close()
():
logger.info()
recommendations = .generate_recommendations()
recommendations:
logger.info()
i, rec (recommendations, ):
logger.info()
logger.info()
logger.info()
logger.info()
logger.info()
logger.info()
logger.info()
unused = .find_unused_indexes()
unused:
logger.info()
idx unused:
logger.info()
logger.info()
logger.info()
logger.info()
logger.info()
logger.info()
() -> :
size_str:
(size_str.replace(, ).replace(, )) *
size_str:
(size_str.replace(, ).replace(, ))
size_str:
(size_str.replace(, ).replace(, )) /
__name__ == :
advisor = PostgreSQLIndexAdvisor(
)
advisor.generate_report()
-- Enable performance schema for query analysis
UPDATE performance_schema.setup_instruments
SET ENABLED = 'YES', TIMED = 'YES'
WHERE NAME LIKE 'statement/%';
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME LIKE '%statements%';
-- Identify slow queries needing indexes
CREATE OR REPLACE VIEW slow_queries_analysis AS
SELECT
DIGEST_TEXT AS query,
COUNT_STAR AS executions,
ROUND(AVG_TIMER_WAIT / 1000000000, 2) AS avg_time_ms,
ROUND(MAX_TIMER_WAIT / 1000000000, 2) AS max_time_ms,
ROUND(SUM_TIMER_WAIT / 1000000000, 2) AS total_time_ms,
SUM_ROWS_EXAMINED AS total_rows_examined,
SUM_ROWS_SENT AS total_rows_sent,
ROUND(SUM_ROWS_EXAMINED / COUNT_STAR, 0) AS avg_rows_examined
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT IS NOT NULL
AND SCHEMA_NAME NOT IN (, , )
AVG_TIMER_WAIT
SUM_TIMER_WAIT
LIMIT ;
object_schema database_name,
object_name table_name,
count_read select_count,
count_fetch rows_fetched,
ROUND(count_fetch (count_read, ), ) avg_rows_per_select
performance_schema.table_io_waits_summary_by_table
object_schema (, , )
count_read
count_fetch ;
t1.TABLE_SCHEMA database_name,
t1.TABLE_NAME table_name,
t1.INDEX_NAME index1,
t2.INDEX_NAME index2,
GROUP_CONCAT(t1.COLUMN_NAME t1.SEQ_IN_INDEX) columns
information_schema.STATISTICS t1
information_schema.STATISTICS t2
t1.TABLE_SCHEMA t2.TABLE_SCHEMA
t1.TABLE_NAME t2.TABLE_NAME
t1.INDEX_NAME t2.INDEX_NAME
t1.COLUMN_NAME t2.COLUMN_NAME
t1.SEQ_IN_INDEX t2.SEQ_IN_INDEX
t1.TABLE_SCHEMA (, , )
t1.TABLE_SCHEMA, t1.TABLE_NAME, t1.INDEX_NAME, t2.INDEX_NAME
() (
()
information_schema.STATISTICS
TABLE_SCHEMA t1.TABLE_SCHEMA
TABLE_NAME t1.TABLE_NAME
INDEX_NAME t1.INDEX_NAME
);
// scripts/mysql-index-advisor.js
const mysql = require('mysql2/promise');
class MySQLIndexAdvisor {
constructor(config) {
this.pool = mysql.createPool({
host: config.host,
user: config.user,
password: config.password,
database: config.database,
waitForConnections: true,
connectionLimit: 10
});
}
async analyzeTableIndexes(tableName) {
const [rows] = await this.pool.query(`
SELECT
COLUMN_NAME,
CARDINALITY,
INDEX_NAME,
SEQ_IN_INDEX,
NON_UNIQUE
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
ORDER BY INDEX_NAME, SEQ_IN_INDEX
`, [tableName]);
return rows;
}
async findMissingIndexes() {
// Analyze slow queries from performance schema
const [slowQueries] = await this.pool.query(`
SELECT
DIGEST_TEXT AS query,
COUNT_STAR AS executions,
ROUND(AVG_TIMER_WAIT / 1000000000, 2) AS avg_time_ms,
SUM_ROWS_EXAMINED AS total_rows_examined
FROM performance_schema.events_statements_summary_by_digest
WHERE AVG_TIMER_WAIT > 100000000
AND SCHEMA_NAME = DATABASE()
ORDER BY AVG_TIMER_WAIT DESC
LIMIT 20
`);
recommendations = [];
( query slowQueries) {
whereMatch = query..();
(whereMatch) {
column = whereMatch[];
recommendations.({
: ,
: column,
: query..(, ),
: query.,
:
});
}
}
recommendations;
}
() {
.();
recommendations = .();
.();
recommendations.( {
.();
.();
.();
.();
});
}
}
( () => {
advisor = ({
: ,
: ,
: ,
:
});
advisor.();
})();
| Error | Cause | Solution |
|---|---|---|
| "Index too large" | Index exceeds max key length | Use partial index or hash index for long columns |
| "Duplicate key violation" | Creating unique index on non-unique data | Check for duplicates before creating unique index |
| "Out of disk space" | Index creation requires temporary storage | Free up disk space or use CONCURRENTLY option |
| "Lock timeout" | Index creation blocking queries | Use CREATE INDEX CONCURRENTLY (PostgreSQL) or ALGORITHM=INPLACE (MySQL) |
| "Statistics out of date" | Old cardinality estimates | Run ANALYZE (PostgreSQL) or ANALYZE TABLE (MySQL) |
Index Types
Index Options
CONCURRENTLY: Create without blocking writes (PostgreSQL)ALGORITHM=INPLACE: Online index creation (MySQL)INCLUDE columns: Covering index (PostgreSQL 11+)WHERE clause: Partial index for filtered queriesDO:
DON'T:
/sql-query-optimizer - Rewrite queries for better performance/database-partition-manager - Partition large tables for faster queries/database-health-monitor - Monitor index bloat and maintenance needs/database-backup-automator - Schedule REINDEX during maintenance windows