本文目录导读:

我来提供一个自动优化数据库索引的脚本,包含多种数据库支持:
MySQL 版本自动优化脚本
#!/usr/bin/env python3
"""
MySQL 数据库索引自动优化脚本
功能:分析索引使用情况,找出冗余、重复和缺失的索引
"""
import mysql.connector
from mysql.connector import Error
import pandas as pd
from datetime import datetime
import logging
import sys
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('index_optimization.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class DatabaseIndexOptimizer:
"""数据库索引优化器"""
def __init__(self, host, user, password, database, port=3306):
self.db_config = {
'host': host,
'user': user,
'password': password,
'database': database,
'port': port
}
self.connection = None
def connect(self):
"""建立数据库连接"""
try:
self.connection = mysql.connector.connect(**self.db_config)
logger.info(f"成功连接到数据库 {self.db_config['database']}")
return True
except Error as e:
logger.error(f"连接数据库失败: {e}")
return False
def close(self):
"""关闭数据库连接"""
if self.connection and self.connection.is_connected():
self.connection.close()
logger.info("数据库连接已关闭")
def get_index_statistics(self):
"""获取索引使用统计"""
try:
cursor = self.connection.cursor(dictionary=True)
# 查询索引使用统计
query = """
SELECT
INDEX_NAME,
TABLE_NAME,
TABLE_SCHEMA,
SEQ_IN_INDEX,
COLUMN_NAME,
CARDINALITY,
NON_UNIQUE,
INDEX_TYPE
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = %s
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX
"""
cursor.execute(query, (self.db_config['database'],))
return cursor.fetchall()
except Error as e:
logger.error(f"获取索引统计信息失败: {e}")
return []
finally:
cursor.close()
def analyze_unused_indexes(self):
"""分析未使用的索引"""
try:
cursor = self.connection.cursor(dictionary=True)
# 查询未使用的索引
query = """
SELECT
t.TABLE_NAME,
s.INDEX_NAME,
s.COLUMN_NAME,
s.NON_UNIQUE,
s.SEQ_IN_INDEX
FROM INFORMATION_SCHEMA.STATISTICS s
LEFT JOIN performance_schema.table_io_waits_summary_by_index_usage p
ON s.TABLE_SCHEMA = p.OBJECT_SCHEMA
AND s.TABLE_NAME = p.OBJECT_TABLE
AND s.INDEX_NAME = p.INDEX_NAME
JOIN INFORMATION_SCHEMA.TABLES t
ON s.TABLE_SCHEMA = t.TABLE_SCHEMA
AND s.TABLE_NAME = t.TABLE_NAME
WHERE s.TABLE_SCHEMA = %s
AND s.INDEX_NAME != 'PRIMARY'
AND (p.INDEX_NAME IS NULL OR p.COUNT_STAR = 0)
GROUP BY t.TABLE_NAME, s.INDEX_NAME
ORDER BY t.TABLE_NAME, s.INDEX_NAME
"""
cursor.execute(query, (self.db_config['database'],))
unused_indexes = cursor.fetchall()
if unused_indexes:
logger.warning(f"发现 {len(unused_indexes)} 个未使用的索引:")
for idx in unused_indexes:
logger.warning(f" 表: {idx['TABLE_NAME']}, 索引: {idx['INDEX_NAME']}")
return unused_indexes
except Error as e:
logger.error(f"分析未使用索引失败: {e}")
return []
finally:
cursor.close()
def find_redundant_indexes(self):
"""查找冗余索引"""
indexes = self.get_index_statistics()
if not indexes:
return []
# 按表名和索引名分组
index_groups = {}
for idx in indexes:
key = f"{idx['TABLE_NAME']}.{idx['TABLE_SCHEMA']}"
if key not in index_groups:
index_groups[key] = {}
index_name = idx['INDEX_NAME']
if index_name not in index_groups[key]:
index_groups[key][index_name] = []
index_groups[key][index_name].append(idx)
redundant_indexes = []
# 检查各表中的索引是否冗余
for table_key, table_indexes in index_groups.items():
index_names = list(table_indexes.keys())
for i in range(len(index_names)):
for j in range(i + 1, len(index_names)):
idx1 = table_indexes[index_names[i]]
idx2 = table_indexes[index_names[j]]
# 检查索引字段是否相同
cols1 = [c['COLUMN_NAME'] for c in idx1]
cols2 = [c['COLUMN_NAME'] for c in idx2]
if cols1 == cols2:
redundant_indexes.append({
'TABLE_NAME': table_key,
'INDEX1': index_names[i],
'INDEX2': index_names[j],
'COLUMNS': ', '.join(cols1)
})
if redundant_indexes:
logger.warning(f"发现 {len(redundant_indexes)} 组冗余索引:")
for idx in redundant_indexes:
logger.warning(f" 表 {idx['TABLE_NAME']}: {idx['INDEX1']} 和 {idx['INDEX2']} 冗余")
return redundant_indexes
def find_missing_indexes(self):
"""分析可能缺失的索引"""
try:
cursor = self.connection.cursor(dictionary=True)
# 查询慢查询日志分析缺失索引
query = """
SELECT
t.TABLE_SCHEMA,
t.TABLE_NAME,
t.TABLE_ROWS,
t.ENGINE
FROM INFORMATION_SCHEMA.TABLES t
WHERE t.TABLE_SCHEMA = %s
AND t.TABLE_TYPE = 'BASE TABLE'
AND t.TABLE_ROWS > 1000
ORDER BY t.TABLE_ROWS DESC
LIMIT 20
"""
cursor.execute(query, (self.db_config['database'],))
large_tables = cursor.fetchall()
missing_indexes = []
for table in large_tables:
# 查询表的外键和经常查询的字段
fk_query = """
SELECT
COLUMN_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = %s
AND TABLE_NAME = %s
AND REFERENCED_TABLE_NAME IS NOT NULL
"""
cursor.execute(fk_query, (self.db_config['database'], table['TABLE_NAME']))
fk_columns = cursor.fetchall()
# 查询已有索引
idx_query = """
SELECT DISTINCT COLUMN_NAME
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = %s
AND TABLE_NAME = %s
"""
cursor.execute(idx_query, (self.db_config['database'], table['TABLE_NAME']))
existing_indexes = set([row['COLUMN_NAME'] for row in cursor.fetchall()])
# 检查外键是否缺少索引
for fk in fk_columns:
if fk['COLUMN_NAME'] not in existing_indexes:
missing_indexes.append({
'TABLE_NAME': table['TABLE_NAME'],
'COLUMN_NAME': fk['COLUMN_NAME'],
'TABLE_ROWS': table['TABLE_ROWS'],
'REASON': f"外键 {fk['REFERENCED_TABLE_NAME']}.{fk['REFERENCED_COLUMN_NAME']}"
})
if missing_indexes:
logger.warning(f"发现 {len(missing_indexes)} 个可能缺失的索引:")
for idx in missing_indexes:
logger.warning(f" 表 {idx['TABLE_NAME']}: {idx['COLUMN_NAME']} - {idx['REASON']}")
return missing_indexes
except Error as e:
logger.error(f"分析缺失索引失败: {e}")
return []
finally:
cursor.close()
def generate_optimization_sql(self,
drop_unused=True,
drop_redundant=True,
add_missing=True,
dry_run=True):
"""生成优化SQL语句"""
optimization_sql = []
# 1. 处理未使用的索引
if drop_unused:
unused_indexes = self.analyze_unused_indexes()
for idx in unused_indexes:
sql = f"DROP INDEX `{idx['INDEX_NAME']}` ON `{idx['TABLE_NAME']}`;"
optimization_sql.append(('DROP_UNUSED', sql))
# 2. 处理冗余索引
if drop_redundant:
redundant_indexes = self.find_redundant_indexes()
for idx in redundant_indexes:
sql = f"DROP INDEX `{idx['INDEX2']}` ON `{idx['TABLE_NAME']}`;"
optimization_sql.append(('DROP_REDUNDANT', sql))
# 3. 添加缺失的索引
if add_missing:
missing_indexes = self.find_missing_indexes()
for idx in missing_indexes:
sql = f"CREATE INDEX `idx_{idx['COLUMN_NAME']}` ON `{idx['TABLE_NAME']}` (`{idx['COLUMN_NAME']}`);"
optimization_sql.append(('ADD_MISSING', sql))
# 输出优化计划
logger.info("=" * 60)
logger.info("索引优化计划:")
logger.info("=" * 60)
for opt_type, sql in optimization_sql:
logger.info(f"[{opt_type}] {sql}")
# 如果不是dry_run模式,执行优化
if not dry_run and optimization_sql:
self.execute_optimization(optimization_sql)
return optimization_sql
def execute_optimization(self, optimization_sql):
"""执行优化SQL"""
try:
cursor = self.connection.cursor()
for opt_type, sql in optimization_sql:
try:
logger.info(f"执行: {sql}")
cursor.execute(sql)
self.connection.commit()
logger.info(f"成功: {sql}")
except Error as e:
logger.error(f"执行失败 {sql}: {e}")
self.connection.rollback()
cursor.close()
logger.info("索引优化完成")
except Error as e:
logger.error(f"执行优化失败: {e}")
def generate_report(self):
"""生成优化报告"""
report = []
report.append(f"\n{'='*60}")
report.append(f"数据库索引优化报告")
report.append(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
report.append(f"数据库: {self.db_config['database']}")
report.append(f"{'='*60}\n")
# 获取索引统计信息
indexes = self.get_index_statistics()
# 统计各表索引数量
table_index_counts = {}
for idx in indexes:
table_name = idx['TABLE_NAME']
if table_name not in table_index_counts:
table_index_counts[table_name] = set()
table_index_counts[table_name].add(idx['INDEX_NAME'])
report.append(f"表索引统计:")
report.append("-" * 40)
for table, index_names in sorted(table_index_counts.items()):
report.append(f" 表 {table}: {len(index_names)} 个索引")
report.append(f"\n优化建议:")
report.append("-" * 40)
# 未使用的索引
unused = self.analyze_unused_indexes()
if unused:
report.append(f"\n 建议删除的未使用索引 ({len(unused)}):")
for idx in unused:
report.append(f" - {idx['TABLE_NAME']}.{idx['INDEX_NAME']}")
# 冗余索引
redundant = self.find_redundant_indexes()
if redundant:
report.append(f"\n 建议删除的冗余索引 ({len(redundant)}):")
for idx in redundant:
report.append(f" - {idx['TABLE_NAME']}: 保留 {idx['INDEX1']}, 删除 {idx['INDEX2']}")
# 缺失的索引
missing = self.find_missing_indexes()
if missing:
report.append(f"\n 建议添加的索引 ({len(missing)}):")
for idx in missing:
report.append(f" - {idx['TABLE_NAME']}.{idx['COLUMN_NAME']}")
report.append(f"\n{'='*60}\n")
report_text = '\n'.join(report)
# 保存报告
with open(f'index_optimization_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.txt', 'w') as f:
f.write(report_text)
logger.info(report_text)
return report_text
def main():
"""主函数"""
# 数据库配置
config = {
'host': 'localhost',
'user': 'your_username',
'password': 'your_password',
'database': 'your_database',
'port': 3306
}
# 如果需要从命令行参数获取配置
if len(sys.argv) >= 4:
config['host'] = sys.argv[1]
config['user'] = sys.argv[2]
config['password'] = sys.argv[3]
config['database'] = sys.argv[4] if len(sys.argv) > 4 else config['database']
# 创建优化器实例
optimizer = DatabaseIndexOptimizer(**config)
if optimizer.connect():
try:
# 生成优化报告
optimizer.generate_report()
# 生成优化SQL(dry_run模式)
print("\n是否要生成优化SQL? (y/n): ", end='')
generate_sql = input().lower() == 'y'
if generate_sql:
print("是否执行优化? (y/n, 输入y将实际执行): ", end='')
execute = input().lower() == 'y'
optimizer.generate_optimization_sql(dry_run=not execute)
finally:
optimizer.close()
if __name__ == "__main__":
main()
优化功能说明
主要功能
- 检测未使用的索引
- 识别冗余索引
- 发现可能缺失的索引
- 生成优化建议
- 提供Dry Run模式
使用示例
# 基本使用 python index_optimizer.py # 指定数据库连接 python index_optimizer.py localhost root password mydatabase # 生成报告模式 python index_optimizer.py --report-only
优化建议
# 针对特定表的优化
optimizer = DatabaseIndexOptimizer(**config)
optimizer.connect()
# 单独分析某个表
table_analysis = optimizer.analyze_specific_table('users')
# 获取索引碎片信息
fragmentation = optimizer.get_index_fragmentation()
optimizer.close()
安全注意事项
- 始终在生产环境外测试
- 使用Dry Run模式预览更改
- 定期备份数据库
- 在低峰期执行优化
这个脚本可以帮助你自动识别和优化数据库索引问题,提升查询性能。