Files
openclaw-master-skills/skills/database-admin/scripts/query_optimizer.js
T

197 lines
5.7 KiB
JavaScript

#!/usr/bin/env node
/**
* 查询优化工具 Query Optimizer
*
* 分析查询性能、生成执行计划、提供优化建议
*/
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT) || 5432,
database: process.env.DB_NAME || 'postgres',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || '',
});
/**
* 获取查询执行计划并分析性能瓶颈
*/
async function analyzeQuery(query) {
const startTime = Date.now();
try {
// 执行 EXPLAIN (ANALYZE, BUFFERS) 获取详细执行计划
const explainQuery = `EXPLAIN (ANALYZE, BUFFERS, VERBOSE) ${query}`;
const result = await pool.query(explainQuery);
const plan = JSON.parse(result.rows[0].plan);
const duration = Date.now() - startTime;
console.log(`\n🔍 查询执行计划分析\n`);
console.log('==================');
console.log('\n查询耗时:', plan.total_plan_time + 'ms');
console.log('实际耗时:', plan.actual_total_time + 'ms (包括查询本身)');
console.log('行数扫描:', plan.rows_scanned || plan.total_rows);
// 输出执行计划树形结构
console.log('\n执行计划树:');
printExecutionPlan(plan.plan_tree, 0);
// 生成优化建议
const suggestions = generateSuggestions(plan, duration);
console.log('\n🔧 优化建议:');
suggestions.forEach((s, i) => {
console.log(`${i + 1}. ${s}`);
});
return { plan, analysis: { duration, suggestions } };
} catch (err) {
console.error(`❌ 分析失败:${err.message}`);
throw err;
}
}
/**
* 打印执行计划树形结构(递归)
*/
function printExecutionPlan(node, depth = 0) {
const indent = ' '.repeat(depth);
if (node && typeof node === 'object') {
console.log(`${indent}└── ${JSON.stringify(node)}`); // 简单打印,生产环境需要更复杂的解析
for (const child of Object.values(node)) {
printExecutionPlan(child, depth + 1);
}
}
}
/**
* 生成优化建议
*/
function generateSuggestions(plan, duration) {
const suggestions = [];
if (duration > 1000) {
suggestions.push('⚠️ 查询耗时超过 1 秒,需要优化');
}
// 检查是否使用了 Sequential Scan 而不是 Index Scan
if (plan.plan_type === 'Seq Scan' && plan.rows_scanned > 10000) {
suggestions.push(`⚠️ 在大表上进行了顺序扫描(${plan.rows_scanned} 行),考虑为 WHERE 条件字段创建索引`);
}
// 检查嵌套循环连接
if (plan.join_type === 'Nested Loop' && plan.rows_scanned > 100) {
suggestions.push('⚠️ 使用了 Nested Loop Join,在大表上可能效率低,考虑改为 Hash Join');
}
// 检查是否选择了不必要的列
if (plan.columns_count && plan.columns_count > 5) {
suggestions.push(`💡 只选择需要的列,避免 SELECT *(当前选择了 ${plan.columns_count} 列)`);
}
// 检查 WHERE 条件
if (plan.where_clause && !plan.index_name) {
const field = Object.keys(plan.where_condition)[0];
suggestions.push(`💡 为 ${field} 字段创建索引可能提升查询性能`);
}
return suggestions;
}
/**
* 生成索引推荐报告
*/
async function generateIndexReport(tableName) {
try {
const query = `
SELECT
indexname,
indexdef,
pg_size_pretty(pg_relation_size(indexrelid)) as index_size
FROM pg_indexes
WHERE schemaname = 'public' AND tablename = '${tableName}'
ORDER BY pg_relation_size(indexrelid) DESC;
`;
const result = await pool.query(query);
console.log(`\n📊 表 ${tableName} 的现有索引:`);
if (result.rows.length === 0) {
console.log('⚠️ 暂无索引');
} else {
result.rows.forEach(row => {
console.log(`├── ${row.indexname}: ${row.indexdef}`);
console.log(` 大小:${row.index_size}`);
});
}
// 推荐新索引
console.log('\n💡 建议:');
console.log('- 为经常用于 WHERE 条件的列创建索引');
console.log('- 为外键列创建索引(如果还没创建)');
console.log('- 避免在低基数列上创建索引');
return { existing: result.rows };
} catch (err) {
console.error(`❌ 分析失败:${err.message}`);
throw err;
}
}
/**
* 批量查询性能测试
*/
async function benchmarkQuery(query, iterations = 10) {
const results = [];
for (let i = 0; i < iterations; i++) {
const startTime = Date.now();
await pool.query(query); // 执行一次,忽略结果
const duration = Date.now() - startTime;
results.push(duration);
}
const totalDuration = results.reduce((a, b) => a + b, 0);
const avgDuration = totalDuration / iterations;
const maxDuration = Math.max(...results);
console.log(`\n📈 ${iterations} 次性能测试结果:`);
console.log(`平均耗时:${avgDuration.toFixed(2)}ms`);
console.log(`最大耗时:${maxDuration}ms`);
console.log(`最小耗时:${Math.min(...results)}ms`);
return { results, avg: avgDuration, max: maxDuration };
}
// CLI 使用示例
async function main() {
const args = process.argv.slice(2);
if (args.includes('--help')) {
console.log(`
查询优化工具 v1.0
用法:
node query_optimizer.js --analyze "SELECT * FROM users WHERE status='active'"
node query_optimizer.js --index users
node query_optimizer.js --benchmark "SELECT count(*) FROM orders"
选项:
--analyze <sql> 分析查询性能并给出建议
--index <table> 生成表的索引报告和推荐
--benchmark <sql> 多次执行查询并测试性能
--help 显示此帮助信息
`);
process.exit(0);
}
}
main().catch(console.error);