查询分析
概述
查询分析是数据库性能优化的核心环节,通过深入分析SQL查询的执行计划、性能指标和资源消耗,识别性能瓶颈并制定优化策略。本文详细介绍如何在Node.js应用中实现全面的查询分析系统。
查询性能监控
1. 查询执行时间监控
javascript
// 查询性能监控器
class QueryPerformanceMonitor {
constructor() {
this.queryStats = new Map();
this.slowQueryThreshold = 1000; // 1秒慢查询阈值
this.slowQueries = [];
this.totalQueries = 0;
this.totalExecutionTime = 0;
}
// 开始查询监控
startQuery(sql, params) {
const queryId = this.generateQueryId();
const startTime = process.hrtime.bigint();
return {
queryId: queryId,
sql: sql,
params: params,
startTime: startTime,
// 结束查询监控
end: (result, error) => {
this.endQuery(queryId, sql, params, startTime, result, error);
}
};
}
// 结束查询监控
endQuery(queryId, sql, params, startTime, result, error) {
const endTime = process.hrtime.bigint();
const executionTime = Number(endTime - startTime) / 1000000; // 转换为毫秒
this.totalQueries++;
this.totalExecutionTime += executionTime;
// 记录查询统计
const normalizedSQL = this.normalizeSQL(sql);
const stats = this.queryStats.get(normalizedSQL) || {
sql: normalizedSQL,
count: 0,
totalTime: 0,
avgTime: 0,
minTime: Infinity,
maxTime: 0,
errors: 0,
lastExecuted: null
};
stats.count++;
stats.totalTime += executionTime;
stats.avgTime = stats.totalTime / stats.count;
stats.minTime = Math.min(stats.minTime, executionTime);
stats.maxTime = Math.max(stats.maxTime, executionTime);
stats.lastExecuted = new Date();
if (error) {
stats.errors++;
}
this.queryStats.set(normalizedSQL, stats);
// 记录慢查询
if (executionTime > this.slowQueryThreshold) {
this.recordSlowQuery({
queryId: queryId,
sql: sql,
params: params,
executionTime: executionTime,
error: error,
timestamp: new Date(),
result: result ? {
rowCount: Array.isArray(result) ? result.length : (result.affectedRows || 0)
} : null
});
}
// 实时日志输出
this.logQueryExecution(sql, params, executionTime, error);
}
// 标准化SQL语句
normalizeSQL(sql) {
return sql
.replace(/\s+/g, ' ')
.replace(/\?/g, '?')
.replace(/\$\d+/g, '$N')
.replace(/=\s*'[^']*'/g, "= 'VALUE'")
.replace(/=\s*\d+/g, '= NUMBER')
.trim()
.toLowerCase();
}
// 生成查询ID
generateQueryId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 记录慢查询
recordSlowQuery(slowQuery) {
this.slowQueries.push(slowQuery);
// 保持最近1000条慢查询记录
if (this.slowQueries.length > 1000) {
this.slowQueries.shift();
}
console.warn('慢查询检测:', {
sql: slowQuery.sql.substring(0, 100) + '...',
executionTime: slowQuery.executionTime + 'ms',
timestamp: slowQuery.timestamp
});
}
// 日志查询执行
logQueryExecution(sql, params, executionTime, error) {
const logLevel = executionTime > this.slowQueryThreshold ? 'warn' : 'debug';
const message = `查询执行 [${executionTime.toFixed(2)}ms]: ${sql.substring(0, 100)}${sql.length > 100 ? '...' : ''}`;
if (logLevel === 'warn') {
console.warn(message);
} else if (process.env.NODE_ENV === 'development') {
console.debug(message);
}
}
// 获取查询统计报告
getQueryStats() {
const sortedQueries = Array.from(this.queryStats.values())
.sort((a, b) => b.totalTime - a.totalTime);
return {
summary: {
totalQueries: this.totalQueries,
totalExecutionTime: this.totalExecutionTime,
averageExecutionTime: this.totalQueries > 0 ? this.totalExecutionTime / this.totalQueries : 0,
slowQueriesCount: this.slowQueries.length,
uniqueQueries: this.queryStats.size
},
topQueriesByTime: sortedQueries.slice(0, 10),
topQueriesByCount: Array.from(this.queryStats.values())
.sort((a, b) => b.count - a.count)
.slice(0, 10),
recentSlowQueries: this.slowQueries.slice(-20),
timestamp: new Date()
};
}
// 获取查询性能趋势
getPerformanceTrends() {
const now = Date.now();
const oneHourAgo = now - 3600000;
const recentSlowQueries = this.slowQueries.filter(q =>
q.timestamp.getTime() > oneHourAgo
);
// 按时间分组统计
const hourlyStats = {};
recentSlowQueries.forEach(q => {
const hour = new Date(q.timestamp).getHours();
if (!hourlyStats[hour]) {
hourlyStats[hour] = { count: 0, totalTime: 0 };
}
hourlyStats[hour].count++;
hourlyStats[hour].totalTime += q.executionTime;
});
return {
hourlySlowQueries: hourlyStats,
recentTrend: this.calculateTrend(recentSlowQueries),
peakHour: this.findPeakHour(hourlyStats)
};
}
// 计算趋势
calculateTrend(queries) {
if (queries.length < 2) return 'stable';
const midPoint = Math.floor(queries.length / 2);
const firstHalf = queries.slice(0, midPoint);
const secondHalf = queries.slice(midPoint);
const firstAvg = firstHalf.reduce((sum, q) => sum + q.executionTime, 0) / firstHalf.length;
const secondAvg = secondHalf.reduce((sum, q) => sum + q.executionTime, 0) / secondHalf.length;
const change = (secondAvg - firstAvg) / firstAvg;
if (change > 0.2) return 'deteriorating';
if (change < -0.2) return 'improving';
return 'stable';
}
// 找到峰值时间
findPeakHour(hourlyStats) {
let peakHour = 0;
let maxCount = 0;
Object.entries(hourlyStats).forEach(([hour, stats]) => {
if (stats.count > maxCount) {
maxCount = stats.count;
peakHour = parseInt(hour);
}
});
return { hour: peakHour, count: maxCount };
}
}
2. 查询执行计划分析
javascript
// 查询执行计划分析器
class QueryPlanAnalyzer {
constructor(pool) {
this.pool = pool;
this.planCache = new Map();
}
// 分析MySQL查询执行计划
async analyzeMySQLPlan(sql, params) {
try {
const connection = await this.pool.getConnection();
// 获取执行计划
const [explainResult] = await connection.execute(`EXPLAIN FORMAT=JSON ${sql}`, params);
const plan = JSON.parse(explainResult[0]['EXPLAIN']);
connection.release();
// 分析执行计划
const analysis = this.analyzeMySQLExplain(plan);
// 缓存执行计划
const normalizedSQL = this.normalizeSQL(sql);
this.planCache.set(normalizedSQL, {
plan: plan,
analysis: analysis,
timestamp: new Date()
});
return analysis;
} catch (error) {
console.error('执行计划分析失败:', error);
return null;
}
}
// 分析MySQL EXPLAIN结果
analyzeMySQLExplain(plan) {
const analysis = {
cost: 0,
rowsExamined: 0,
issues: [],
recommendations: [],
tableScans: 0,
indexUsage: [],
joinTypes: []
};
this.traverseMySQLPlan(plan.query_block, analysis);
// 生成建议
this.generateMySQLRecommendations(analysis);
return analysis;
}
// 遍历MySQL执行计划
traverseMySQLPlan(block, analysis) {
if (block.cost_info) {
analysis.cost += parseFloat(block.cost_info.query_cost || 0);
}
// 分析表访问
if (block.table) {
this.analyzeMySQLTable(block.table, analysis);
}
// 分析嵌套循环连接
if (block.nested_loop) {
block.nested_loop.forEach(item => {
if (item.table) {
this.analyzeMySQLTable(item.table, analysis);
}
});
}
// 分析子查询
if (block.subqueries) {
block.subqueries.forEach(subquery => {
this.traverseMySQLPlan(subquery, analysis);
});
}
}
// 分析MySQL表访问
analyzeMySQLTable(table, analysis) {
analysis.rowsExamined += parseInt(table.rows_examined_per_scan || 0);
// 检查访问类型
if (table.access_type === 'ALL') {
analysis.tableScans++;
analysis.issues.push(`表 ${table.table_name} 进行全表扫描`);
}
// 检查索引使用
if (table.key) {
analysis.indexUsage.push({
table: table.table_name,
index: table.key,
keyLength: table.key_length
});
} else if (table.access_type !== 'system' && table.access_type !== 'const') {
analysis.issues.push(`表 ${table.table_name} 未使用索引`);
}
// 检查连接类型
if (table.access_type) {
analysis.joinTypes.push(table.access_type);
}
// 检查额外信息
if (table.extra) {
if (table.extra.includes('Using filesort')) {
analysis.issues.push(`表 ${table.table_name} 使用文件排序`);
}
if (table.extra.includes('Using temporary')) {
analysis.issues.push(`表 ${table.table_name} 使用临时表`);
}
}
}
// 生成MySQL优化建议
generateMySQLRecommendations(analysis) {
if (analysis.tableScans > 0) {
analysis.recommendations.push('考虑为全表扫描的表添加适当的索引');
}
if (analysis.cost > 1000) {
analysis.recommendations.push('查询成本较高,考虑优化查询逻辑或添加索引');
}
if (analysis.rowsExamined > 10000) {
analysis.recommendations.push('检查的行数过多,考虑添加WHERE条件限制结果集');
}
if (analysis.issues.some(issue => issue.includes('文件排序'))) {
analysis.recommendations.push('考虑添加覆盖索引避免文件排序');
}
if (analysis.issues.some(issue => issue.includes('临时表'))) {
analysis.recommendations.push('优化GROUP BY或ORDER BY子句避免使用临时表');
}
}
// 分析PostgreSQL查询执行计划
async analyzePostgreSQLPlan(sql, params) {
try {
const connection = await this.pool.getConnection();
// 获取执行计划
const explainResult = await connection.query(`EXPLAIN (FORMAT JSON, ANALYZE, BUFFERS) ${sql}`, params);
const plan = explainResult.rows[0]['QUERY PLAN'][0];
connection.release();
// 分析执行计划
const analysis = this.analyzePostgreSQLExplain(plan);
return analysis;
} catch (error) {
console.error('PostgreSQL执行计划分析失败:', error);
return null;
}
}
// 分析PostgreSQL EXPLAIN结果
analyzePostgreSQLExplain(plan) {
const analysis = {
totalCost: plan['Total Cost'],
actualTime: plan['Actual Total Time'],
planningTime: plan['Planning Time'],
executionTime: plan['Execution Time'],
rowsReturned: plan['Actual Rows'],
issues: [],
recommendations: [],
bufferStats: plan['Shared Hit Blocks'] ? {
sharedHit: plan['Shared Hit Blocks'],
sharedRead: plan['Shared Read Blocks'],
sharedWritten: plan['Shared Written Blocks']
} : null
};
this.traversePostgreSQLPlan(plan.Plan, analysis);
this.generatePostgreSQLRecommendations(analysis);
return analysis;
}
// 遍历PostgreSQL执行计划
traversePostgreSQLPlan(node, analysis) {
// 检查节点类型
if (node['Node Type'] === 'Seq Scan') {
analysis.issues.push(`表 ${node['Relation Name']} 进行顺序扫描`);
}
if (node['Node Type'] === 'Sort' && node['Sort Method'] === 'external merge') {
analysis.issues.push('排序操作使用磁盘,考虑增加work_mem');
}
// 检查索引使用
if (node['Node Type'] === 'Index Scan' || node['Node Type'] === 'Index Only Scan') {
// 索引使用良好
} else if (node['Relation Name'] && node['Node Type'] !== 'Seq Scan') {
// 可能需要索引优化
}
// 递归检查子节点
if (node.Plans) {
node.Plans.forEach(childNode => {
this.traversePostgreSQLPlan(childNode, analysis);
});
}
}
// 生成PostgreSQL优化建议
generatePostgreSQLRecommendations(analysis) {
if (analysis.issues.some(issue => issue.includes('顺序扫描'))) {
analysis.recommendations.push('考虑为顺序扫描的表添加适当的索引');
}
if (analysis.totalCost > 10000) {
analysis.recommendations.push('查询成本很高,考虑重写查询或优化索引');
}
if (analysis.actualTime > 1000) {
analysis.recommendations.push('查询执行时间较长,检查是否有锁等待或I/O瓶颈');
}
if (analysis.bufferStats && analysis.bufferStats.sharedRead > analysis.bufferStats.sharedHit) {
analysis.recommendations.push('缓存命中率较低,考虑增加shared_buffers');
}
}
// 标准化SQL
normalizeSQL(sql) {
return sql
.replace(/\s+/g, ' ')
.replace(/\?/g, '?')
.replace(/\$\d+/g, '$N')
.trim()
.toLowerCase();
}
// 获取执行计划缓存
getCachedPlan(sql) {
const normalizedSQL = this.normalizeSQL(sql);
return this.planCache.get(normalizedSQL);
}
// 清理过期的执行计划缓存
cleanupPlanCache() {
const oneHourAgo = Date.now() - 3600000;
for (const [sql, cache] of this.planCache) {
if (cache.timestamp.getTime() < oneHourAgo) {
this.planCache.delete(sql);
}
}
}
}
慢查询分析
1. 慢查询检测和记录
javascript
// 慢查询检测器
class SlowQueryDetector {
constructor(options = {}) {
this.slowThreshold = options.slowThreshold || 1000; // 1秒
this.verySlowThreshold = options.verySlowThreshold || 5000; // 5秒
this.criticalThreshold = options.criticalThreshold || 10000; // 10秒
this.slowQueries = [];
this.queryPatterns = new Map();
this.alertCallbacks = [];
this.maxSlowQueries = options.maxSlowQueries || 1000;
this.enablePatternAnalysis = options.enablePatternAnalysis !== false;
}
// 检测慢查询
detectSlowQuery(sql, params, executionTime, metadata = {}) {
if (executionTime < this.slowThreshold) {
return null; // 不是慢查询
}
const severity = this.getSeverity(executionTime);
const slowQuery = {
id: this.generateId(),
sql: sql,
params: params,
executionTime: executionTime,
severity: severity,
timestamp: new Date(),
metadata: metadata,
stackTrace: this.captureStackTrace(),
fingerprint: this.generateFingerprint(sql)
};
// 记录慢查询
this.recordSlowQuery(slowQuery);
// 分析查询模式
if (this.enablePatternAnalysis) {
this.analyzeQueryPattern(slowQuery);
}
// 触发告警
this.triggerAlerts(slowQuery);
return slowQuery;
}
// 获取严重程度
getSeverity(executionTime) {
if (executionTime >= this.criticalThreshold) return 'critical';
if (executionTime >= this.verySlowThreshold) return 'high';
return 'medium';
}
// 记录慢查询
recordSlowQuery(slowQuery) {
this.slowQueries.push(slowQuery);
// 保持最大记录数限制
if (this.slowQueries.length > this.maxSlowQueries) {
this.slowQueries.shift();
}
// 输出日志
console.warn('慢查询检测:', {
id: slowQuery.id,
executionTime: slowQuery.executionTime + 'ms',
severity: slowQuery.severity,
sql: slowQuery.sql.substring(0, 100) + (slowQuery.sql.length > 100 ? '...' : ''),
timestamp: slowQuery.timestamp
});
}
// 分析查询模式
analyzeQueryPattern(slowQuery) {
const pattern = this.queryPatterns.get(slowQuery.fingerprint) || {
fingerprint: slowQuery.fingerprint,
count: 0,
totalTime: 0,
avgTime: 0,
minTime: Infinity,
maxTime: 0,
firstSeen: slowQuery.timestamp,
lastSeen: slowQuery.timestamp,
examples: []
};
pattern.count++;
pattern.totalTime += slowQuery.executionTime;
pattern.avgTime = pattern.totalTime / pattern.count;
pattern.minTime = Math.min(pattern.minTime, slowQuery.executionTime);
pattern.maxTime = Math.max(pattern.maxTime, slowQuery.executionTime);
pattern.lastSeen = slowQuery.timestamp;
// 保存示例查询
if (pattern.examples.length < 3) {
pattern.examples.push({
sql: slowQuery.sql,
params: slowQuery.params,
executionTime: slowQuery.executionTime,
timestamp: slowQuery.timestamp
});
}
this.queryPatterns.set(slowQuery.fingerprint, pattern);
}
// 生成查询指纹
generateFingerprint(sql) {
return sql
.replace(/\s+/g, ' ')
.replace(/\?/g, '?')
.replace(/\$\d+/g, '$N')
.replace(/=\s*'[^']*'/g, "= 'VALUE'")
.replace(/=\s*\d+/g, '= NUMBER')
.replace(/IN\s*\([^)]+\)/gi, 'IN (VALUES)')
.trim()
.toLowerCase();
}
// 捕获堆栈跟踪
captureStackTrace() {
const error = new Error();
const stack = error.stack?.split('\n').slice(2, 8); // 前几层调用栈
return stack || [];
}
// 生成唯一ID
generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 触发告警
triggerAlerts(slowQuery) {
this.alertCallbacks.forEach(callback => {
try {
callback(slowQuery);
} catch (error) {
console.error('告警回调执行失败:', error);
}
});
}
// 添加告警回调
addAlertCallback(callback) {
this.alertCallbacks.push(callback);
}
// 获取慢查询统计
getSlowQueryStats() {
const now = Date.now();
const oneHourAgo = now - 3600000;
const oneDayAgo = now - 86400000;
const recentQueries = this.slowQueries.filter(q =>
q.timestamp.getTime() > oneHourAgo
);
const dailyQueries = this.slowQueries.filter(q =>
q.timestamp.getTime() > oneDayAgo
);
return {
total: this.slowQueries.length,
recentCount: recentQueries.length,
dailyCount: dailyQueries.length,
severityDistribution: this.getSeverityDistribution(this.slowQueries),
topPatterns: this.getTopPatterns(10),
averageExecutionTime: this.getAverageExecutionTime(this.slowQueries),
peakHours: this.getPeakHours(dailyQueries)
};
}
// 获取严重程度分布
getSeverityDistribution(queries) {
const distribution = { critical: 0, high: 0, medium: 0 };
queries.forEach(query => {
distribution[query.severity]++;
});
return distribution;
}
// 获取热点查询模式
getTopPatterns(limit = 10) {
return Array.from(this.queryPatterns.values())
.sort((a, b) => b.count - a.count)
.slice(0, limit);
}
// 获取平均执行时间
getAverageExecutionTime(queries) {
if (queries.length === 0) return 0;
const totalTime = queries.reduce((sum, query) => sum + query.executionTime, 0);
return totalTime / queries.length;
}
// 获取峰值时段
getPeakHours(queries) {
const hourlyCount = {};
queries.forEach(query => {
const hour = query.timestamp.getHours();
hourlyCount[hour] = (hourlyCount[hour] || 0) + 1;
});
return Object.entries(hourlyCount)
.sort(([, a], [, b]) => b - a)
.slice(0, 3)
.map(([hour, count]) => ({ hour: parseInt(hour), count }));
}
// 获取慢查询详情
getSlowQueryDetails(queryId) {
return this.slowQueries.find(query => query.id === queryId);
}
// 获取查询模式详情
getPatternDetails(fingerprint) {
return this.queryPatterns.get(fingerprint);
}
}
2. 慢查询优化建议
javascript
// 慢查询优化建议生成器
class SlowQueryOptimizer {
constructor(planAnalyzer) {
this.planAnalyzer = planAnalyzer;
this.optimizationRules = this.initializeRules();
}
// 初始化优化规则
initializeRules() {
return [
{
name: 'missing_index',
pattern: /SELECT.*FROM.*WHERE/i,
check: (query, plan) => {
return plan?.issues?.some(issue => issue.includes('全表扫描') || issue.includes('顺序扫描'));
},
suggestion: '考虑为WHERE条件中的字段添加索引'
},
{
name: 'no_limit',
pattern: /SELECT.*FROM(?!.*LIMIT)/i,
check: (query) => {
return !query.sql.toLowerCase().includes('limit') &&
query.sql.toLowerCase().includes('select');
},
suggestion: '考虑添加LIMIT子句限制结果集大小'
},
{
name: 'select_star',
pattern: /SELECT\s+\*/i,
check: (query) => {
return query.sql.toLowerCase().includes('select *');
},
suggestion: '避免使用SELECT *,只查询需要的字段'
},
{
name: 'inefficient_join',
pattern: /JOIN/i,
check: (query, plan) => {
return plan?.issues?.some(issue => issue.includes('嵌套循环'));
},
suggestion: '优化JOIN查询,确保连接字段有索引'
},
{
name: 'unnecessary_sorting',
pattern: /ORDER BY/i,
check: (query, plan) => {
return plan?.issues?.some(issue => issue.includes('文件排序'));
},
suggestion: '考虑添加覆盖索引避免排序操作'
},
{
name: 'subquery_optimization',
pattern: /EXISTS\s*\(|IN\s*\(/i,
check: (query) => {
return /EXISTS\s*\(|IN\s*\(/.test(query.sql);
},
suggestion: '考虑将子查询重写为JOIN操作'
}
];
}
// 生成优化建议
async generateOptimizationSuggestions(slowQuery) {
const suggestions = {
queryId: slowQuery.id,
sql: slowQuery.sql,
executionTime: slowQuery.executionTime,
severity: slowQuery.severity,
suggestions: [],
estimatedImprovement: 0
};
try {
// 获取执行计划
const plan = await this.planAnalyzer.analyzeMySQLPlan(slowQuery.sql, slowQuery.params);
// 应用优化规则
for (const rule of this.optimizationRules) {
if (rule.pattern.test(slowQuery.sql) && rule.check(slowQuery, plan)) {
suggestions.suggestions.push({
type: rule.name,
description: rule.suggestion,
priority: this.calculatePriority(rule.name, slowQuery),
examples: this.getOptimizationExamples(rule.name, slowQuery.sql)
});
}
}
// 基于执行计划的建议
if (plan) {
suggestions.suggestions.push(...this.generatePlanBasedSuggestions(plan));
}
// 估算改进效果
suggestions.estimatedImprovement = this.estimateImprovement(suggestions.suggestions, slowQuery);
} catch (error) {
console.error('生成优化建议失败:', error);
suggestions.suggestions.push({
type: 'analysis_error',
description: '无法分析查询执行计划,请手动检查查询语句',
priority: 'low'
});
}
return suggestions;
}
// 计算优化优先级
calculatePriority(ruleType, slowQuery) {
const severityWeight = {
critical: 3,
high: 2,
medium: 1
};
const ruleWeight = {
missing_index: 3,
inefficient_join: 3,
unnecessary_sorting: 2,
select_star: 1,
no_limit: 2,
subquery_optimization: 2
};
const score = (severityWeight[slowQuery.severity] || 1) * (ruleWeight[ruleType] || 1);
if (score >= 6) return 'high';
if (score >= 3) return 'medium';
return 'low';
}
// 获取优化示例
getOptimizationExamples(ruleType, originalSQL) {
const examples = {
missing_index: {
before: 'SELECT * FROM users WHERE email = ?',
after: '-- 添加索引: CREATE INDEX idx_users_email ON users(email)\nSELECT * FROM users WHERE email = ?'
},
select_star: {
before: 'SELECT * FROM users WHERE id = ?',
after: 'SELECT id, name, email FROM users WHERE id = ?'
},
no_limit: {
before: 'SELECT * FROM posts ORDER BY created_at DESC',
after: 'SELECT * FROM posts ORDER BY created_at DESC LIMIT 20'
},
inefficient_join: {
before: 'SELECT * FROM users u, orders o WHERE u.id = o.user_id',
after: 'SELECT * FROM users u INNER JOIN orders o ON u.id = o.user_id'
},
unnecessary_sorting: {
before: 'SELECT * FROM users ORDER BY created_at',
after: '-- 添加索引: CREATE INDEX idx_users_created_at ON users(created_at)\nSELECT * FROM users ORDER BY created_at'
},
subquery_optimization: {
before: 'SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE status = "completed")',
after: 'SELECT DISTINCT u.* FROM users u INNER JOIN orders o ON u.id = o.user_id WHERE o.status = "completed"'
}
};
return examples[ruleType] || null;
}
// 基于执行计划生成建议
generatePlanBasedSuggestions(plan) {
const suggestions = [];
if (plan.issues) {
plan.issues.forEach(issue => {
if (issue.includes('全表扫描')) {
suggestions.push({
type: 'full_table_scan',
description: '检测到全表扫描,建议添加适当的索引',
priority: 'high'
});
}
if (issue.includes('临时表')) {
suggestions.push({
type: 'temporary_table',
description: '查询使用了临时表,考虑优化GROUP BY或DISTINCT操作',
priority: 'medium'
});
}
if (issue.includes('文件排序')) {
suggestions.push({
type: 'filesort',
description: '查询使用了文件排序,考虑添加覆盖索引',
priority: 'medium'
});
}
});
}
if (plan.cost > 1000) {
suggestions.push({
type: 'high_cost',
description: '查询成本很高,建议重新设计查询逻辑',
priority: 'high'
});
}
return suggestions;
}
// 估算改进效果
estimateImprovement(suggestions, slowQuery) {
let improvement = 0;
suggestions.forEach(suggestion => {
const improvementMap = {
missing_index: 0.7, // 70%改进
inefficient_join: 0.5, // 50%改进
unnecessary_sorting: 0.3, // 30%改进
select_star: 0.2, // 20%改进
no_limit: 0.4, // 40%改进
subquery_optimization: 0.6, // 60%改进
full_table_scan: 0.8, // 80%改进
temporary_table: 0.4, // 40%改进
filesort: 0.3 // 30%改进
};
improvement = Math.max(improvement, improvementMap[suggestion.type] || 0);
});
return Math.round(improvement * 100); // 返回百分比
}
// 生成优化报告
generateOptimizationReport(slowQueries) {
const report = {
summary: {
totalSlowQueries: slowQueries.length,
avgExecutionTime: slowQueries.reduce((sum, q) => sum + q.executionTime, 0) / slowQueries.length,
optimizationOpportunities: 0
},
topOptimizationTargets: [],
commonIssues: {},
recommendations: []
};
// 统计常见问题
slowQueries.forEach(query => {
this.optimizationRules.forEach(rule => {
if (rule.pattern.test(query.sql)) {
report.commonIssues[rule.name] = (report.commonIssues[rule.name] || 0) + 1;
}
});
});
// 生成通用建议
Object.entries(report.commonIssues)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.forEach(([issue, count]) => {
const rule = this.optimizationRules.find(r => r.name === issue);
if (rule) {
report.recommendations.push({
issue: issue,
affectedQueries: count,
suggestion: rule.suggestion,
priority: count > slowQueries.length * 0.3 ? 'high' : 'medium'
});
}
});
return report;
}
}
查询模式分析
1. 查询模式识别
javascript
// 查询模式分析器
class QueryPatternAnalyzer {
constructor() {
this.patterns = new Map();
this.antiPatterns = this.initializeAntiPatterns();
}
// 初始化反模式
initializeAntiPatterns() {
return [
{
name: 'n_plus_one',
description: 'N+1查询问题',
detector: (queries) => this.detectNPlusOne(queries),
severity: 'high'
},
{
name: 'cartesian_product',
description: '笛卡尔积查询',
detector: (queries) => this.detectCartesianProduct(queries),
severity: 'critical'
},
{
name: 'unnecessary_distinct',
description: '不必要的DISTINCT',
detector: (queries) => this.detectUnnecessaryDistinct(queries),
severity: 'medium'
},
{
name: 'inefficient_pagination',
description: '低效分页查询',
detector: (queries) => this.detectInefficientPagination(queries),
severity: 'medium'
},
{
name: 'missing_where_clause',
description: '缺少WHERE条件',
detector: (queries) => this.detectMissingWhereClause(queries),
severity: 'high'
}
];
}
// 分析查询模式
analyzePatterns(queries) {
const analysis = {
totalQueries: queries.length,
uniquePatterns: 0,
antiPatternsFound: [],
patternDistribution: {},
recommendations: []
};
// 识别查询模式
queries.forEach(query => {
const fingerprint = this.generateFingerprint(query.sql);
const pattern = this.patterns.get(fingerprint) || {
fingerprint: fingerprint,
count: 0,
avgExecutionTime: 0,
totalTime: 0,
examples: []
};
pattern.count++;
pattern.totalTime += query.executionTime || 0;
pattern.avgExecutionTime = pattern.totalTime / pattern.count;
if (pattern.examples.length < 3) {
pattern.examples.push(query);
}
this.patterns.set(fingerprint, pattern);
});
analysis.uniquePatterns = this.patterns.size;
// 检测反模式
this.antiPatterns.forEach(antiPattern => {
const detected = antiPattern.detector(queries);
if (detected.length > 0) {
analysis.antiPatternsFound.push({
name: antiPattern.name,
description: antiPattern.description,
severity: antiPattern.severity,
occurrences: detected.length,
examples: detected.slice(0, 3)
});
}
});
// 生成模式分布
const sortedPatterns = Array.from(this.patterns.values())
.sort((a, b) => b.count - a.count);
sortedPatterns.slice(0, 10).forEach((pattern, index) => {
analysis.patternDistribution[`pattern_${index + 1}`] = {
fingerprint: pattern.fingerprint,
count: pattern.count,
percentage: (pattern.count / queries.length) * 100,
avgExecutionTime: pattern.avgExecutionTime
};
});
// 生成建议
analysis.recommendations = this.generatePatternRecommendations(analysis);
return analysis;
}
// 检测N+1查询问题
detectNPlusOne(queries) {
const nPlusOneQueries = [];
const queryGroups = {};
// 按时间窗口分组查询
queries.forEach(query => {
const timeWindow = Math.floor(query.timestamp?.getTime() / 1000 / 5) * 5; // 5秒窗口
const fingerprint = this.generateFingerprint(query.sql);
if (!queryGroups[timeWindow]) {
queryGroups[timeWindow] = {};
}
if (!queryGroups[timeWindow][fingerprint]) {
queryGroups[timeWindow][fingerprint] = [];
}
queryGroups[timeWindow][fingerprint].push(query);
});
// 检测重复查询
Object.values(queryGroups).forEach(window => {
Object.entries(window).forEach(([fingerprint, queries]) => {
if (queries.length > 10) { // 在短时间内执行超过10次相同查询
nPlusOneQueries.push({
fingerprint: fingerprint,
count: queries.length,
examples: queries.slice(0, 3),
timeWindow: queries[0].timestamp
});
}
});
});
return nPlusOneQueries;
}
// 检测笛卡尔积查询
detectCartesianProduct(queries) {
const cartesianQueries = [];
queries.forEach(query => {
const sql = query.sql.toLowerCase();
// 检测没有JOIN条件的多表查询
if (sql.includes('from') && sql.includes(',')) {
const fromClause = sql.substring(sql.indexOf('from'), sql.indexOf('where') > -1 ? sql.indexOf('where') : sql.length);
const tableCount = (fromClause.match(/,/g) || []).length + 1;
if (tableCount > 1 && !sql.includes('join') && !sql.includes('where')) {
cartesianQueries.push(query);
}
}
// 检测JOIN但没有ON条件
if (sql.includes('join') && !sql.includes(' on ')) {
cartesianQueries.push(query);
}
});
return cartesianQueries;
}
// 检测不必要的DISTINCT
detectUnnecessaryDistinct(queries) {
const unnecessaryDistinct = [];
queries.forEach(query => {
const sql = query.sql.toLowerCase();
if (sql.includes('distinct')) {
// 如果查询包含主键或唯一键,DISTINCT可能是不必要的
if (sql.includes('id =') || sql.includes('primary key')) {
unnecessaryDistinct.push(query);
}
// 如果只查询一个表且没有JOIN,DISTINCT可能不必要
if (!sql.includes('join') && !sql.includes(',') && sql.includes('from')) {
const fromMatch = sql.match(/from\s+(\w+)/);
if (fromMatch && !sql.includes('group by')) {
unnecessaryDistinct.push(query);
}
}
}
});
return unnecessaryDistinct;
}
// 检测低效分页查询
detectInefficientPagination(queries) {
const inefficientPagination = [];
queries.forEach(query => {
const sql = query.sql.toLowerCase();
if (sql.includes('limit') && sql.includes('offset')) {
const offsetMatch = sql.match(/offset\s+(\d+)/);
if (offsetMatch) {
const offset = parseInt(offsetMatch[1]);
if (offset > 1000) { // 大偏移量分页
inefficientPagination.push({
...query,
reason: '大偏移量分页效率低下',
offset: offset
});
}
}
}
// 检测没有ORDER BY的LIMIT查询
if (sql.includes('limit') && !sql.includes('order by')) {
inefficientPagination.push({
...query,
reason: '分页查询缺少ORDER BY子句'
});
}
});
return inefficientPagination;
}
// 检测缺少WHERE条件的查询
detectMissingWhereClause(queries) {
const missingWhere = [];
queries.forEach(query => {
const sql = query.sql.toLowerCase();
if (sql.includes('select') && sql.includes('from') && !sql.includes('where')) {
// 排除一些合理的情况
if (!sql.includes('count(*)') && !sql.includes('limit 1') && !sql.includes('order by')) {
missingWhere.push(query);
}
}
});
return missingWhere;
}
// 生成查询指纹
generateFingerprint(sql) {
return sql
.replace(/\s+/g, ' ')
.replace(/\?/g, '?')
.replace(/\$\d+/g, '$N')
.replace(/=\s*'[^']*'/g, "= 'VALUE'")
.replace(/=\s*\d+/g, '= NUMBER')
.replace(/IN\s*\([^)]+\)/gi, 'IN (VALUES)')
.trim()
.toLowerCase();
}
// 生成模式建议
generatePatternRecommendations(analysis) {
const recommendations = [];
// 基于反模式生成建议
analysis.antiPatternsFound.forEach(antiPattern => {
switch (antiPattern.name) {
case 'n_plus_one':
recommendations.push({
type: 'optimization',
priority: 'high',
description: '检测到N+1查询问题,建议使用JOIN或批量查询优化',
impact: '可显著提升性能,减少数据库往返次数'
});
break;
case 'cartesian_product':
recommendations.push({
type: 'bug_fix',
priority: 'critical',
description: '检测到笛卡尔积查询,请添加适当的JOIN条件',
impact: '修复后可避免返回大量无用数据和性能问题'
});
break;
case 'inefficient_pagination':
recommendations.push({
type: 'optimization',
priority: 'medium',
description: '优化分页查询,考虑使用游标分页或索引优化',
impact: '改善大数据集分页的性能'
});
break;
}
});
// 基于查询频率生成建议
const topPatterns = Object.values(analysis.patternDistribution)
.sort((a, b) => b.count - a.count)
.slice(0, 3);
topPatterns.forEach(pattern => {
if (pattern.percentage > 20) {
recommendations.push({
type: 'caching',
priority: 'medium',
description: `查询模式 "${pattern.fingerprint.substring(0, 50)}..." 占总查询的${pattern.percentage.toFixed(1)}%,建议考虑缓存`,
impact: '通过缓存热点查询可以显著减少数据库负载'
});
}
});
return recommendations;
}
// 获取查询模式报告
getPatternReport() {
const sortedPatterns = Array.from(this.patterns.values())
.sort((a, b) => b.count - a.count);
return {
totalPatterns: this.patterns.size,
topPatterns: sortedPatterns.slice(0, 20),
summary: {
mostFrequentPattern: sortedPatterns[0],
slowestPattern: sortedPatterns.sort((a, b) => b.avgExecutionTime - a.avgExecutionTime)[0],
totalQueries: sortedPatterns.reduce((sum, p) => sum + p.count, 0)
}
};
}
}
总结
查询分析是数据库性能优化的基础:
- 全面监控:监控查询执行时间、资源消耗和错误率
- 执行计划分析:深入分析查询执行计划,识别性能瓶颈
- 慢查询检测:自动检测和记录慢查询,提供优化建议
- 模式识别:识别查询模式和反模式,预防性能问题
- 智能建议:基于分析结果生成具体的优化建议
- 持续优化:建立持续的查询分析和优化流程
通过系统化的查询分析,可以及时发现性能问题,指导数据库优化工作,提升应用整体性能。