Files
nxxmdata/backend/utils/query-optimizer.js
2025-08-25 15:00:46 +08:00

517 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 数据库查询优化器
* @file query-optimizer.js
* @description 监控和优化SQL查询性能
*/
const { sequelize } = require('../config/database-pool');
const { QueryTypes } = require('sequelize');
// 查询性能日志
class QueryPerformanceLog {
constructor() {
this.logs = [];
this.maxLogs = 200; // 最多保存200条日志
this.slowQueryThreshold = 500; // 慢查询阈值(毫秒)
this.queryPatterns = new Map(); // 存储查询模式及其统计信息
}
// 添加日志
add(query, duration, params = {}) {
const log = {
query,
duration,
params,
timestamp: new Date(),
isSlow: duration > this.slowQueryThreshold
};
this.logs.unshift(log); // 添加到开头
// 保持日志数量不超过最大值
if (this.logs.length > this.maxLogs) {
this.logs.pop();
}
// 如果是慢查询,输出警告
if (log.isSlow) {
console.warn(`慢查询 (${duration}ms): ${query}`);
console.warn('参数:', params);
}
// 更新查询模式统计
this.updateQueryPatternStats(query, duration);
return log;
}
// 更新查询模式统计
updateQueryPatternStats(query, duration) {
// 简化查询,移除具体参数值,保留查询结构
const patternQuery = this.simplifyQuery(query);
if (!this.queryPatterns.has(patternQuery)) {
this.queryPatterns.set(patternQuery, {
count: 0,
totalDuration: 0,
avgDuration: 0,
minDuration: duration,
maxDuration: duration,
lastSeen: new Date()
});
}
const stats = this.queryPatterns.get(patternQuery);
stats.count++;
stats.totalDuration += duration;
stats.avgDuration = stats.totalDuration / stats.count;
stats.minDuration = Math.min(stats.minDuration, duration);
stats.maxDuration = Math.max(stats.maxDuration, duration);
stats.lastSeen = new Date();
}
// 简化查询,移除具体参数值
simplifyQuery(query) {
return query
.replace(/('([^']*)'|"([^"]*)")/g, '?') // 替换字符串
.replace(/\b\d+\b/g, '?') // 替换数字
.replace(/\s+/g, ' ') // 规范化空白字符
.trim();
}
// 获取所有日志
getLogs() {
return this.logs;
}
// 获取慢查询日志
getSlowLogs() {
return this.logs.filter(log => log.isSlow);
}
// 获取查询模式统计
getQueryPatternStats() {
return Array.from(this.queryPatterns.entries()).map(([pattern, stats]) => ({
pattern,
...stats
}));
}
// 获取最常见的查询模式
getMostFrequentQueries(limit = 10) {
return this.getQueryPatternStats()
.sort((a, b) => b.count - a.count)
.slice(0, limit);
}
// 获取平均执行时间最长的查询模式
getSlowestQueries(limit = 10) {
return this.getQueryPatternStats()
.sort((a, b) => b.avgDuration - a.avgDuration)
.slice(0, limit);
}
// 清除日志
clear() {
this.logs = [];
this.queryPatterns.clear();
}
// 设置慢查询阈值
setSlowQueryThreshold(threshold) {
this.slowQueryThreshold = threshold;
return this.slowQueryThreshold;
}
// 获取慢查询阈值
getSlowQueryThreshold() {
return this.slowQueryThreshold;
}
}
// 创建性能日志实例
const performanceLog = new QueryPerformanceLog();
// 查询优化器
class QueryOptimizer {
constructor(sequelize, performanceLog) {
this.sequelize = sequelize;
this.performanceLog = performanceLog;
this.indexSuggestions = new Map(); // 存储索引建议
this.setupLogging();
}
// 设置查询日志记录
setupLogging() {
// 如果已经在开发环境中启用了benchmark则不需要额外设置
if (!this.sequelize.options.benchmark) {
const originalQuery = this.sequelize.query.bind(this.sequelize);
this.sequelize.query = async function (...args) {
const start = Date.now();
try {
const result = await originalQuery(...args);
const duration = Date.now() - start;
// 记录查询性能
performanceLog.add(args[0], duration, args[1]);
return result;
} catch (error) {
const duration = Date.now() - start;
performanceLog.add(`ERROR: ${args[0]}`, duration, { ...args[1], error: error.message });
throw error;
}
};
}
}
// 分析表结构并提供优化建议
async analyzeTableStructure(tableName) {
try {
// 获取表结构
const tableInfo = await this.getTableInfo(tableName);
// 获取索引信息
const indexInfo = await this.getIndexInfo(tableName);
// 获取表数据统计
const tableStats = await this.getTableStats(tableName);
// 分析并生成建议
const suggestions = this.generateOptimizationSuggestions(
tableName,
tableInfo,
indexInfo,
tableStats
);
return {
tableName,
structure: tableInfo,
indexes: indexInfo,
stats: tableStats,
suggestions
};
} catch (error) {
console.error(`分析表 ${tableName} 结构失败:`, error);
return { error: error.message };
}
}
// 生成优化建议
generateOptimizationSuggestions(tableName, tableInfo, indexInfo, tableStats) {
const suggestions = [];
// 检查是否有主键
const hasPrimaryKey = indexInfo.some(idx => idx.Key_name === 'PRIMARY');
if (!hasPrimaryKey) {
suggestions.push({
type: 'missing_primary_key',
importance: 'high',
message: `${tableName} 缺少主键,建议添加主键以提高性能`
});
}
// 检查大型TEXT/BLOB字段
const largeTextFields = tableInfo.structure.filter(
field => field.Type.includes('text') || field.Type.includes('blob')
);
if (largeTextFields.length > 0) {
suggestions.push({
type: 'large_text_fields',
importance: 'medium',
message: `${tableName} 包含 ${largeTextFields.length} 个大型TEXT/BLOB字段考虑将不常用的大型字段移至单独的表中`
});
}
// 检查表大小
if (tableStats && tableStats.data_length > 100 * 1024 * 1024) { // 大于100MB
suggestions.push({
type: 'large_table',
importance: 'medium',
message: `${tableName} 较大 (${Math.round(tableStats.data_length / (1024 * 1024))}MB),考虑分区或归档旧数据`
});
}
// 从查询日志中分析可能需要的索引
const suggestedIndexes = this.suggestIndexesFromQueries(tableName);
suggestedIndexes.forEach(suggestion => {
suggestions.push({
type: 'suggested_index',
importance: 'high',
message: `建议在表 ${tableName}${suggestion.columns.join(', ')} 列上创建索引,可能提高查询性能`,
details: suggestion
});
});
return suggestions;
}
// 从查询日志中分析可能需要的索引
suggestIndexesFromQueries(tableName) {
// 获取与该表相关的慢查询
const tableQueries = this.performanceLog.getSlowLogs()
.filter(log => log.query.includes(tableName));
// 简单的索引建议逻辑 - 实际实现会更复杂
const suggestions = [];
// 检查WHERE子句中频繁使用的列
const wherePattern = new RegExp(`${tableName}\\s+WHERE\\s+([\\w\\s,=<>!]+)`, 'i');
tableQueries.forEach(log => {
const match = log.query.match(wherePattern);
if (match && match[1]) {
const whereClause = match[1];
// 提取列名 - 这是一个简化的实现
const columnMatches = whereClause.match(/\b(\w+)\b\s*[=<>!]/g);
if (columnMatches) {
const columns = columnMatches.map(col => col.trim().replace(/[=<>!\s]/g, ''));
// 检查这些列是否已经在建议中
const key = columns.sort().join(',');
if (!this.indexSuggestions.has(key)) {
this.indexSuggestions.set(key, {
tableName,
columns,
queryCount: 1,
avgDuration: log.duration
});
} else {
const suggestion = this.indexSuggestions.get(key);
suggestion.queryCount++;
suggestion.avgDuration = (suggestion.avgDuration * (suggestion.queryCount - 1) + log.duration) / suggestion.queryCount;
}
}
}
});
// 返回针对该表的索引建议
return Array.from(this.indexSuggestions.values())
.filter(suggestion => suggestion.tableName === tableName)
.sort((a, b) => b.queryCount - a.queryCount);
}
// 获取表统计信息
async getTableStats(tableName) {
try {
const stats = await this.sequelize.query(
'SHOW TABLE STATUS LIKE ?',
{
replacements: [tableName],
type: QueryTypes.SELECT
}
);
return stats[0] || null;
} catch (error) {
console.error(`获取表 ${tableName} 统计信息失败:`, error);
return null;
}
}
// 分析和优化表
async analyzeAndOptimizeTable(tableName) {
try {
// 分析表
await this.sequelize.query(`ANALYZE TABLE ${tableName}`, { type: QueryTypes.RAW });
// 优化表
const optimizeResult = await this.sequelize.query(`OPTIMIZE TABLE ${tableName}`, { type: QueryTypes.RAW });
return optimizeResult[0];
} catch (error) {
console.error(`分析和优化表 ${tableName} 失败:`, error);
return { error: error.message };
}
}
// 获取表的索引信息
async getIndexInfo(tableName) {
try {
const indexInfo = await this.sequelize.query(
'SHOW INDEX FROM ??',
{
replacements: [tableName],
type: QueryTypes.SELECT
}
);
return indexInfo;
} catch (error) {
console.error(`获取表 ${tableName} 的索引信息失败:`, error);
return [];
}
}
// 获取表信息
async getTableInfo(tableName) {
try {
// 获取表状态
const tableStatus = await this.sequelize.query(
'SHOW TABLE STATUS LIKE ?',
{
replacements: [tableName],
type: QueryTypes.SELECT
}
);
// 获取表结构
const tableStructure = await this.sequelize.query(
'DESCRIBE ??',
{
replacements: [tableName],
type: QueryTypes.SELECT
}
);
return {
status: tableStatus[0] || {},
structure: tableStructure
};
} catch (error) {
console.error(`获取表 ${tableName} 信息失败:`, error);
return { error: error.message };
}
}
// 解释查询计划
async explainQuery(query, params = {}) {
try {
// 执行EXPLAIN
const explainResult = await this.sequelize.query(
`EXPLAIN ${query}`,
{
replacements: params.replacements || [],
type: QueryTypes.SELECT
}
);
return explainResult;
} catch (error) {
console.error('解释查询计划失败:', error);
return [];
}
}
// 识别慢查询
async identifySlowQueries(threshold = this.performanceLog.slowQueryThreshold) {
try {
// 从性能日志中获取慢查询
const slowLogs = this.performanceLog.getSlowLogs();
// 如果性能日志中没有足够的数据,则从数据库中查询
if (slowLogs.length < 5) {
const dbSlowQueries = await this.sequelize.query(
'SELECT * FROM information_schema.PROCESSLIST WHERE TIME > ?',
{
replacements: [threshold / 1000], // 转换为秒
type: QueryTypes.SELECT
}
);
return dbSlowQueries;
}
return slowLogs;
} catch (error) {
console.error('识别慢查询失败:', error);
return [];
}
}
// 获取数据库状态
async getDatabaseStatus() {
try {
// 获取全局状态
const globalStatus = await this.sequelize.query(
'SHOW GLOBAL STATUS',
{ type: QueryTypes.SELECT }
);
// 转换为对象格式
const status = {};
globalStatus.forEach(item => {
if (item.Variable_name && item.Value) {
status[item.Variable_name] = item.Value;
}
});
// 提取关键指标
return {
connections: {
max_used: status.Max_used_connections,
current: status.Threads_connected,
running: status.Threads_running,
created: status.Threads_created,
cached: status.Threads_cached
},
queries: {
total: status.Questions,
slow: status.Slow_queries,
qps: status.Queries
},
buffer_pool: {
size: status.Innodb_buffer_pool_pages_total,
free: status.Innodb_buffer_pool_pages_free,
dirty: status.Innodb_buffer_pool_pages_dirty,
reads: status.Innodb_buffer_pool_reads,
read_requests: status.Innodb_buffer_pool_read_requests,
hit_rate: status.Innodb_buffer_pool_read_requests && status.Innodb_buffer_pool_reads
? (1 - parseInt(status.Innodb_buffer_pool_reads) / parseInt(status.Innodb_buffer_pool_read_requests)) * 100
: 0
},
performance: {
slow_queries_count: this.performanceLog.getSlowLogs().length,
total_queries_logged: this.performanceLog.getLogs().length,
query_patterns: this.performanceLog.getQueryPatternStats().length
}
};
} catch (error) {
console.error('获取数据库状态失败:', error);
return { error: error.message };
}
}
// 获取所有查询日志
getAllQueries() {
return this.performanceLog.getLogs();
}
// 获取慢查询
getSlowQueries() {
return this.performanceLog.getSlowLogs();
}
// 获取查询模式统计
getQueryPatternStats() {
return this.performanceLog.getQueryPatternStats();
}
// 获取最常见的查询
getMostFrequentQueries(limit = 10) {
return this.performanceLog.getMostFrequentQueries(limit);
}
// 获取最慢的查询
getSlowestQueries(limit = 10) {
return this.performanceLog.getSlowestQueries(limit);
}
// 设置慢查询阈值
setSlowQueryThreshold(threshold) {
return this.performanceLog.setSlowQueryThreshold(threshold);
}
// 清除性能日志
clearPerformanceLogs() {
this.performanceLog.clear();
this.indexSuggestions.clear();
return { success: true, message: '性能日志已清除' };
}
}
// 创建查询优化器实例
const queryOptimizer = new QueryOptimizer(sequelize, performanceLog);
module.exports = queryOptimizer;