Initial commit: 宁夏智慧养殖监管平台
This commit is contained in:
495
backend/utils/db-monitor.js
Normal file
495
backend/utils/db-monitor.js
Normal file
@@ -0,0 +1,495 @@
|
||||
/**
|
||||
* 数据库连接监控工具
|
||||
* @file db-monitor.js
|
||||
* @description 实时监控数据库连接状态和性能
|
||||
*/
|
||||
const { monitorPool, testConnection, resetPool, optimizePool } = require('../config/database-pool');
|
||||
const queryOptimizer = require('./query-optimizer');
|
||||
const EventEmitter = require('events');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 创建事件发射器
|
||||
class DatabaseEventEmitter extends EventEmitter {}
|
||||
const dbEvents = new DatabaseEventEmitter();
|
||||
|
||||
// 数据库监控类
|
||||
class DatabaseMonitor {
|
||||
constructor() {
|
||||
this.isMonitoring = false;
|
||||
this.monitorInterval = null;
|
||||
this.monitorIntervalTime = 60000; // 默认每分钟监控一次
|
||||
this.listeners = [];
|
||||
this.status = {
|
||||
lastCheck: null,
|
||||
isConnected: false,
|
||||
poolStats: null,
|
||||
slowQueries: [],
|
||||
errors: [],
|
||||
metrics: {}
|
||||
};
|
||||
this.logPath = path.join(process.cwd(), 'logs', 'db-monitor');
|
||||
this.alertThresholds = {
|
||||
connectionErrors: 3, // 连续连接错误次数阈值
|
||||
slowQueryPercentage: 10, // 慢查询百分比阈值
|
||||
connectionPoolUsage: 80, // 连接池使用率阈值(百分比)
|
||||
queryResponseTime: 1000 // 查询响应时间阈值(毫秒)
|
||||
};
|
||||
this.metricsHistory = {
|
||||
connectionPool: [],
|
||||
queryPerformance: [],
|
||||
errors: []
|
||||
};
|
||||
this.maxMetricsHistory = 100; // 保存最近100条历史记录
|
||||
|
||||
// 确保日志目录存在
|
||||
this.ensureLogDirectory();
|
||||
}
|
||||
|
||||
// 确保日志目录存在
|
||||
ensureLogDirectory() {
|
||||
try {
|
||||
if (!fs.existsSync(path.join(process.cwd(), 'logs'))) {
|
||||
fs.mkdirSync(path.join(process.cwd(), 'logs'));
|
||||
}
|
||||
if (!fs.existsSync(this.logPath)) {
|
||||
fs.mkdirSync(this.logPath);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建日志目录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 开始监控
|
||||
startMonitoring(intervalTime = this.monitorIntervalTime) {
|
||||
if (this.isMonitoring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.monitorIntervalTime = intervalTime;
|
||||
this.isMonitoring = true;
|
||||
|
||||
// 立即执行一次监控
|
||||
this.checkStatus();
|
||||
|
||||
// 设置定时监控
|
||||
this.monitorInterval = setInterval(() => {
|
||||
this.checkStatus();
|
||||
}, this.monitorIntervalTime);
|
||||
|
||||
console.log(`数据库监控已启动,监控间隔: ${this.monitorIntervalTime}ms`);
|
||||
dbEvents.emit('monitoring_started', { interval: this.monitorIntervalTime });
|
||||
return true;
|
||||
}
|
||||
|
||||
// 停止监控
|
||||
stopMonitoring() {
|
||||
if (!this.isMonitoring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
clearInterval(this.monitorInterval);
|
||||
this.monitorInterval = null;
|
||||
this.isMonitoring = false;
|
||||
|
||||
console.log('数据库监控已停止');
|
||||
dbEvents.emit('monitoring_stopped');
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查数据库状态
|
||||
async checkStatus() {
|
||||
try {
|
||||
// 检查连接
|
||||
const isConnected = await testConnection();
|
||||
|
||||
// 获取连接池状态
|
||||
const poolStats = monitorPool();
|
||||
|
||||
// 获取慢查询
|
||||
const slowQueries = await queryOptimizer.identifySlowQueries(this.alertThresholds.queryResponseTime);
|
||||
|
||||
// 获取数据库状态
|
||||
const dbStatus = await queryOptimizer.getDatabaseStatus();
|
||||
|
||||
// 计算指标
|
||||
const metrics = this.calculateMetrics(isConnected, poolStats, slowQueries, dbStatus);
|
||||
|
||||
// 更新状态
|
||||
this.status = {
|
||||
lastCheck: new Date(),
|
||||
isConnected,
|
||||
poolStats,
|
||||
slowQueries,
|
||||
errors: isConnected ? [] : [{ time: new Date(), message: '数据库连接失败' }],
|
||||
metrics
|
||||
};
|
||||
|
||||
// 更新历史记录
|
||||
this.updateMetricsHistory(metrics);
|
||||
|
||||
// 检查是否需要发出警报
|
||||
this.checkAlerts();
|
||||
|
||||
// 记录状态日志
|
||||
this.logStatus();
|
||||
|
||||
// 通知所有监听器
|
||||
this.notifyListeners();
|
||||
|
||||
// 如果连接失败,记录错误
|
||||
if (!isConnected) {
|
||||
console.error('数据库连接检查失败');
|
||||
this.logError('数据库连接检查失败');
|
||||
dbEvents.emit('connection_error', { time: new Date(), message: '数据库连接失败' });
|
||||
}
|
||||
|
||||
return this.status;
|
||||
} catch (error) {
|
||||
const errorStatus = {
|
||||
lastCheck: new Date(),
|
||||
isConnected: false,
|
||||
poolStats: null,
|
||||
slowQueries: [],
|
||||
errors: [{ time: new Date(), message: error.message }],
|
||||
metrics: {}
|
||||
};
|
||||
|
||||
this.status = errorStatus;
|
||||
this.logError(error.message);
|
||||
this.notifyListeners();
|
||||
|
||||
console.error('数据库状态检查失败:', error);
|
||||
dbEvents.emit('status_check_error', { time: new Date(), error: error.message });
|
||||
return errorStatus;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算监控指标
|
||||
calculateMetrics(isConnected, poolStats, slowQueries, dbStatus) {
|
||||
// 连接池使用率
|
||||
const poolUsage = poolStats && poolStats.total > 0
|
||||
? (poolStats.borrowed / poolStats.total) * 100
|
||||
: 0;
|
||||
|
||||
// 慢查询百分比
|
||||
const totalQueries = dbStatus && dbStatus.queries ? parseInt(dbStatus.queries.total) || 0 : 0;
|
||||
const slowQueryCount = slowQueries ? slowQueries.length : 0;
|
||||
const slowQueryPercentage = totalQueries > 0
|
||||
? (slowQueryCount / totalQueries) * 100
|
||||
: 0;
|
||||
|
||||
// 缓冲池命中率
|
||||
const bufferPoolHitRate = dbStatus && dbStatus.buffer_pool ? dbStatus.buffer_pool.hit_rate : 0;
|
||||
|
||||
return {
|
||||
connectionStatus: isConnected ? 'connected' : 'disconnected',
|
||||
poolUsage: parseFloat(poolUsage.toFixed(2)),
|
||||
slowQueryPercentage: parseFloat(slowQueryPercentage.toFixed(2)),
|
||||
bufferPoolHitRate: parseFloat(bufferPoolHitRate.toFixed(2)),
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
// 更新指标历史记录
|
||||
updateMetricsHistory(metrics) {
|
||||
// 更新连接池历史
|
||||
this.metricsHistory.connectionPool.push({
|
||||
timestamp: new Date(),
|
||||
poolUsage: metrics.poolUsage,
|
||||
connectionStatus: metrics.connectionStatus
|
||||
});
|
||||
|
||||
// 更新查询性能历史
|
||||
this.metricsHistory.queryPerformance.push({
|
||||
timestamp: new Date(),
|
||||
slowQueryPercentage: metrics.slowQueryPercentage,
|
||||
bufferPoolHitRate: metrics.bufferPoolHitRate
|
||||
});
|
||||
|
||||
// 限制历史记录数量
|
||||
if (this.metricsHistory.connectionPool.length > this.maxMetricsHistory) {
|
||||
this.metricsHistory.connectionPool.shift();
|
||||
}
|
||||
|
||||
if (this.metricsHistory.queryPerformance.length > this.maxMetricsHistory) {
|
||||
this.metricsHistory.queryPerformance.shift();
|
||||
}
|
||||
|
||||
if (this.metricsHistory.errors.length > this.maxMetricsHistory) {
|
||||
this.metricsHistory.errors.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要发出警报
|
||||
checkAlerts() {
|
||||
const { metrics, slowQueries, poolStats } = this.status;
|
||||
|
||||
// 检查连接池使用率
|
||||
if (metrics.poolUsage > this.alertThresholds.connectionPoolUsage) {
|
||||
const alert = {
|
||||
type: 'high_pool_usage',
|
||||
message: `连接池使用率过高: ${metrics.poolUsage}%`,
|
||||
level: 'warning',
|
||||
timestamp: new Date()
|
||||
};
|
||||
dbEvents.emit('alert', alert);
|
||||
console.warn(alert.message);
|
||||
}
|
||||
|
||||
// 检查慢查询百分比
|
||||
if (metrics.slowQueryPercentage > this.alertThresholds.slowQueryPercentage) {
|
||||
const alert = {
|
||||
type: 'high_slow_query_percentage',
|
||||
message: `慢查询百分比过高: ${metrics.slowQueryPercentage}%`,
|
||||
level: 'warning',
|
||||
timestamp: new Date(),
|
||||
details: slowQueries
|
||||
};
|
||||
dbEvents.emit('alert', alert);
|
||||
console.warn(alert.message);
|
||||
}
|
||||
|
||||
// 检查连接错误
|
||||
const recentErrors = this.metricsHistory.errors.slice(-this.alertThresholds.connectionErrors);
|
||||
if (recentErrors.length >= this.alertThresholds.connectionErrors) {
|
||||
const alert = {
|
||||
type: 'connection_errors',
|
||||
message: `连续出现${recentErrors.length}次连接错误`,
|
||||
level: 'error',
|
||||
timestamp: new Date(),
|
||||
details: recentErrors
|
||||
};
|
||||
dbEvents.emit('alert', alert);
|
||||
console.error(alert.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录状态日志
|
||||
logStatus() {
|
||||
try {
|
||||
const logFile = path.join(this.logPath, `status-${new Date().toISOString().split('T')[0]}.log`);
|
||||
const logData = JSON.stringify({
|
||||
timestamp: new Date(),
|
||||
status: this.status
|
||||
}) + '\n';
|
||||
|
||||
fs.appendFileSync(logFile, logData);
|
||||
} catch (error) {
|
||||
console.error('记录状态日志失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录错误日志
|
||||
logError(message) {
|
||||
try {
|
||||
const errorLog = {
|
||||
timestamp: new Date(),
|
||||
message
|
||||
};
|
||||
|
||||
// 添加到错误历史
|
||||
this.metricsHistory.errors.push(errorLog);
|
||||
|
||||
// 写入错误日志文件
|
||||
const logFile = path.join(this.logPath, `error-${new Date().toISOString().split('T')[0]}.log`);
|
||||
const logData = JSON.stringify(errorLog) + '\n';
|
||||
|
||||
fs.appendFileSync(logFile, logData);
|
||||
} catch (error) {
|
||||
console.error('记录错误日志失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前状态
|
||||
getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
// 获取历史指标
|
||||
getMetricsHistory() {
|
||||
return this.metricsHistory;
|
||||
}
|
||||
|
||||
// 添加状态变化监听器
|
||||
addListener(listener) {
|
||||
if (typeof listener === 'function' && !this.listeners.includes(listener)) {
|
||||
this.listeners.push(listener);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 移除监听器
|
||||
removeListener(listener) {
|
||||
const index = this.listeners.indexOf(listener);
|
||||
if (index !== -1) {
|
||||
this.listeners.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 通知所有监听器
|
||||
notifyListeners() {
|
||||
this.listeners.forEach(listener => {
|
||||
try {
|
||||
listener(this.status);
|
||||
} catch (error) {
|
||||
console.error('监听器执行失败:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 设置监控间隔
|
||||
setMonitorInterval(intervalTime) {
|
||||
if (intervalTime < 1000) {
|
||||
console.warn('监控间隔不能小于1000ms,已设置为1000ms');
|
||||
intervalTime = 1000;
|
||||
}
|
||||
|
||||
this.monitorIntervalTime = intervalTime;
|
||||
|
||||
// 如果正在监控,重新启动监控
|
||||
if (this.isMonitoring) {
|
||||
this.stopMonitoring();
|
||||
this.startMonitoring();
|
||||
}
|
||||
|
||||
return this.monitorIntervalTime;
|
||||
}
|
||||
|
||||
// 设置警报阈值
|
||||
setAlertThresholds(thresholds = {}) {
|
||||
this.alertThresholds = {
|
||||
...this.alertThresholds,
|
||||
...thresholds
|
||||
};
|
||||
return this.alertThresholds;
|
||||
}
|
||||
|
||||
// 获取警报阈值
|
||||
getAlertThresholds() {
|
||||
return this.alertThresholds;
|
||||
}
|
||||
|
||||
// 优化连接池
|
||||
async optimizeConnectionPool() {
|
||||
try {
|
||||
// 获取当前连接池状态
|
||||
const poolStats = monitorPool();
|
||||
|
||||
// 根据当前使用情况计算优化配置
|
||||
const newConfig = {};
|
||||
|
||||
// 如果连接池使用率超过80%,增加最大连接数
|
||||
if (poolStats.borrowed / poolStats.total > 0.8) {
|
||||
newConfig.max = Math.min(poolStats.max + 5, 30); // 增加5个连接,但不超过30
|
||||
}
|
||||
|
||||
// 如果连接池使用率低于20%,减少最大连接数
|
||||
if (poolStats.borrowed / poolStats.total < 0.2 && poolStats.max > 10) {
|
||||
newConfig.max = Math.max(poolStats.max - 5, 10); // 减少5个连接,但不低于10
|
||||
}
|
||||
|
||||
// 应用优化配置
|
||||
if (Object.keys(newConfig).length > 0) {
|
||||
const result = await optimizePool(newConfig);
|
||||
console.log('连接池已优化:', result);
|
||||
dbEvents.emit('pool_optimized', result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return poolStats;
|
||||
} catch (error) {
|
||||
console.error('优化连接池失败:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 重置连接池
|
||||
async resetConnectionPool() {
|
||||
try {
|
||||
const result = await resetPool();
|
||||
console.log('连接池已重置:', result);
|
||||
dbEvents.emit('pool_reset', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('重置连接池失败:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取详细的数据库信息
|
||||
async getDatabaseInfo() {
|
||||
try {
|
||||
// 获取数据库状态
|
||||
const dbStatus = await queryOptimizer.getDatabaseStatus();
|
||||
|
||||
// 获取当前连接池状态
|
||||
const poolStats = monitorPool();
|
||||
|
||||
// 获取慢查询
|
||||
const slowQueries = await queryOptimizer.identifySlowQueries();
|
||||
|
||||
// 获取表信息
|
||||
const tables = await this.getTablesList();
|
||||
const tableInfoPromises = tables.map(table => queryOptimizer.getTableInfo(table));
|
||||
const tablesInfo = await Promise.all(tableInfoPromises);
|
||||
|
||||
return {
|
||||
timestamp: new Date(),
|
||||
connection: {
|
||||
isConnected: await testConnection(),
|
||||
pool: poolStats
|
||||
},
|
||||
performance: {
|
||||
slowQueries,
|
||||
metrics: this.status.metrics,
|
||||
history: this.metricsHistory
|
||||
},
|
||||
database: dbStatus,
|
||||
tables: tablesInfo.reduce((acc, info, index) => {
|
||||
acc[tables[index]] = info;
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取数据库信息失败:', error);
|
||||
return {
|
||||
timestamp: new Date(),
|
||||
error: error.message,
|
||||
connection: { isConnected: false, pool: null },
|
||||
performance: { slowQueries: [], metrics: {}, history: {} },
|
||||
database: { variables: {}, status: {} },
|
||||
tables: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取表列表
|
||||
async getTablesList() {
|
||||
try {
|
||||
const result = await sequelize.query(
|
||||
'SHOW TABLES',
|
||||
{ type: sequelize.QueryTypes.SHOWTABLES }
|
||||
);
|
||||
|
||||
// 结果格式可能因数据库类型而异
|
||||
return Array.isArray(result)
|
||||
? result.flat().filter(Boolean)
|
||||
: [];
|
||||
} catch (error) {
|
||||
console.error('获取表列表失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建数据库监控实例
|
||||
const dbMonitor = new DatabaseMonitor();
|
||||
|
||||
// 导出事件发射器,允许外部监听事件
|
||||
dbMonitor.events = dbEvents;
|
||||
|
||||
module.exports = dbMonitor;
|
||||
44
backend/utils/logger.js
Normal file
44
backend/utils/logger.js
Normal file
@@ -0,0 +1,44 @@
|
||||
const winston = require('winston');
|
||||
|
||||
// 创建日志格式
|
||||
const logFormat = winston.format.combine(
|
||||
winston.format.timestamp({
|
||||
format: 'YYYY-MM-DD HH:mm:ss'
|
||||
}),
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.json()
|
||||
);
|
||||
|
||||
// 创建logger实例
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: logFormat,
|
||||
defaultMeta: { service: 'nxxmdata-backend' },
|
||||
transports: [
|
||||
// 错误日志文件
|
||||
new winston.transports.File({
|
||||
filename: 'logs/error.log',
|
||||
level: 'error',
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5
|
||||
}),
|
||||
// 所有日志文件
|
||||
new winston.transports.File({
|
||||
filename: 'logs/combined.log',
|
||||
maxsize: 5242880, // 5MB
|
||||
maxFiles: 5
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
// 开发环境下添加控制台输出
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
logger.add(new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
module.exports = logger;
|
||||
570
backend/utils/performance-monitor.js
Normal file
570
backend/utils/performance-monitor.js
Normal file
@@ -0,0 +1,570 @@
|
||||
/**
|
||||
* 性能监控系统
|
||||
* @file performance-monitor.js
|
||||
* @description 全面的系统性能监控工具,包括数据库、API和系统资源监控
|
||||
*/
|
||||
const { EventEmitter } = require('events');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const logger = require('./logger');
|
||||
const dbMonitor = require('./db-monitor');
|
||||
const queryOptimizer = require('./query-optimizer');
|
||||
const { getPoolStatus } = require('../config/database-pool');
|
||||
|
||||
// 性能监控事件发射器
|
||||
class PerformanceEventEmitter extends EventEmitter {}
|
||||
const perfEvents = new PerformanceEventEmitter();
|
||||
|
||||
// 性能监控系统
|
||||
class PerformanceMonitor {
|
||||
constructor() {
|
||||
this.isMonitoring = false;
|
||||
this.monitoringInterval = 60000; // 默认监控间隔为1分钟
|
||||
this.metricsHistory = {
|
||||
database: [],
|
||||
api: [],
|
||||
system: [],
|
||||
memory: []
|
||||
};
|
||||
this.historyLimit = 100; // 历史记录限制
|
||||
this.alertThresholds = {
|
||||
database: {
|
||||
connectionPoolUtilization: 80, // 连接池利用率阈值(百分比)
|
||||
slowQueryCount: 5, // 慢查询数量阈值
|
||||
errorRate: 5 // 错误率阈值(百分比)
|
||||
},
|
||||
api: {
|
||||
responseTime: 500, // API响应时间阈值(毫秒)
|
||||
errorRate: 5, // API错误率阈值(百分比)
|
||||
requestRate: 1000 // 每分钟请求数阈值
|
||||
},
|
||||
system: {
|
||||
cpuUsage: 80, // CPU使用率阈值(百分比)
|
||||
memoryUsage: 80, // 内存使用率阈值(百分比)
|
||||
diskUsage: 80 // 磁盘使用率阈值(百分比)
|
||||
}
|
||||
};
|
||||
|
||||
// 日志配置
|
||||
this.logPath = path.join(process.cwd(), 'logs', 'performance');
|
||||
this.ensureLogDirectory();
|
||||
|
||||
// API请求统计
|
||||
this.apiStats = {
|
||||
totalRequests: 0,
|
||||
totalErrors: 0,
|
||||
endpoints: new Map(), // 按端点统计
|
||||
responseTimes: [],
|
||||
lastReset: new Date()
|
||||
};
|
||||
|
||||
// 初始化数据库监控
|
||||
this.initDatabaseMonitoring();
|
||||
}
|
||||
|
||||
// 确保日志目录存在
|
||||
ensureLogDirectory() {
|
||||
if (!fs.existsSync(this.logPath)) {
|
||||
fs.mkdirSync(this.logPath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据库监控
|
||||
initDatabaseMonitoring() {
|
||||
// 监听数据库事件
|
||||
dbMonitor.on('statusChange', (status) => {
|
||||
this.recordDatabaseMetrics(status);
|
||||
perfEvents.emit('databaseStatus', status);
|
||||
});
|
||||
|
||||
dbMonitor.on('error', (error) => {
|
||||
this.logError('database', error);
|
||||
perfEvents.emit('databaseError', error);
|
||||
});
|
||||
|
||||
dbMonitor.on('slowQuery', (query) => {
|
||||
perfEvents.emit('slowQuery', query);
|
||||
});
|
||||
}
|
||||
|
||||
// 开始监控
|
||||
startMonitoring(interval = this.monitoringInterval) {
|
||||
if (this.isMonitoring) {
|
||||
return { success: false, message: '监控已经在运行中' };
|
||||
}
|
||||
|
||||
this.monitoringInterval = interval;
|
||||
this.isMonitoring = true;
|
||||
|
||||
// 启动数据库监控
|
||||
dbMonitor.startMonitoring(interval);
|
||||
|
||||
// 启动系统资源监控
|
||||
this.monitoringTimer = setInterval(() => {
|
||||
this.collectSystemMetrics();
|
||||
}, interval);
|
||||
|
||||
logger.info(`性能监控已启动,监控间隔: ${interval}ms`);
|
||||
perfEvents.emit('monitoringStarted', { interval });
|
||||
|
||||
return { success: true, message: `性能监控已启动,监控间隔: ${interval}ms` };
|
||||
}
|
||||
|
||||
// 停止监控
|
||||
stopMonitoring() {
|
||||
if (!this.isMonitoring) {
|
||||
return { success: false, message: '监控未在运行' };
|
||||
}
|
||||
|
||||
// 停止数据库监控
|
||||
dbMonitor.stopMonitoring();
|
||||
|
||||
// 停止系统资源监控
|
||||
clearInterval(this.monitoringTimer);
|
||||
|
||||
this.isMonitoring = false;
|
||||
logger.info('性能监控已停止');
|
||||
perfEvents.emit('monitoringStopped');
|
||||
|
||||
return { success: true, message: '性能监控已停止' };
|
||||
}
|
||||
|
||||
// 收集系统指标
|
||||
collectSystemMetrics() {
|
||||
try {
|
||||
// 获取CPU使用情况
|
||||
const cpuUsage = this.getCpuUsage();
|
||||
|
||||
// 获取内存使用情况
|
||||
const memoryUsage = this.getMemoryUsage();
|
||||
|
||||
// 获取磁盘使用情况
|
||||
const diskUsage = this.getDiskUsage();
|
||||
|
||||
// 记录系统指标
|
||||
this.recordSystemMetrics({ cpuUsage, memoryUsage, diskUsage });
|
||||
|
||||
// 检查是否需要发出警报
|
||||
this.checkSystemAlerts({ cpuUsage, memoryUsage, diskUsage });
|
||||
|
||||
return { cpuUsage, memoryUsage, diskUsage };
|
||||
} catch (error) {
|
||||
this.logError('system', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取CPU使用情况
|
||||
getCpuUsage() {
|
||||
try {
|
||||
const cpus = os.cpus();
|
||||
let totalIdle = 0;
|
||||
let totalTick = 0;
|
||||
|
||||
cpus.forEach(cpu => {
|
||||
for (const type in cpu.times) {
|
||||
totalTick += cpu.times[type];
|
||||
}
|
||||
totalIdle += cpu.times.idle;
|
||||
});
|
||||
|
||||
const idle = totalIdle / cpus.length;
|
||||
const total = totalTick / cpus.length;
|
||||
const usage = 100 - (idle / total * 100);
|
||||
|
||||
return {
|
||||
usage: parseFloat(usage.toFixed(2)),
|
||||
cores: cpus.length,
|
||||
model: cpus[0].model,
|
||||
speed: cpus[0].speed
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('获取CPU使用情况失败:', error);
|
||||
return { usage: 0, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取内存使用情况
|
||||
getMemoryUsage() {
|
||||
try {
|
||||
const totalMemory = os.totalmem();
|
||||
const freeMemory = os.freemem();
|
||||
const usedMemory = totalMemory - freeMemory;
|
||||
const usage = (usedMemory / totalMemory) * 100;
|
||||
|
||||
return {
|
||||
total: totalMemory,
|
||||
free: freeMemory,
|
||||
used: usedMemory,
|
||||
usage: parseFloat(usage.toFixed(2))
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('获取内存使用情况失败:', error);
|
||||
return { usage: 0, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取磁盘使用情况(简化版,实际生产环境可能需要使用第三方库)
|
||||
getDiskUsage() {
|
||||
// 注意:这是一个简化的实现,实际生产环境可能需要使用第三方库如diskusage
|
||||
try {
|
||||
// 在实际生产环境中,这里应该使用适当的库来获取磁盘使用情况
|
||||
// 这里返回一个模拟值
|
||||
return {
|
||||
total: 1000000000, // 1GB
|
||||
free: 500000000, // 500MB
|
||||
used: 500000000, // 500MB
|
||||
usage: 50.0 // 50%
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('获取磁盘使用情况失败:', error);
|
||||
return { usage: 0, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 记录API请求
|
||||
recordApiRequest(req, res, startTime) {
|
||||
const duration = Date.now() - startTime;
|
||||
const endpoint = `${req.method} ${req.path}`;
|
||||
const statusCode = res.statusCode;
|
||||
const isError = statusCode >= 400;
|
||||
|
||||
// 更新总体统计
|
||||
this.apiStats.totalRequests++;
|
||||
if (isError) this.apiStats.totalErrors++;
|
||||
this.apiStats.responseTimes.push(duration);
|
||||
|
||||
// 限制响应时间数组大小
|
||||
if (this.apiStats.responseTimes.length > 1000) {
|
||||
this.apiStats.responseTimes.shift();
|
||||
}
|
||||
|
||||
// 更新端点统计
|
||||
if (!this.apiStats.endpoints.has(endpoint)) {
|
||||
this.apiStats.endpoints.set(endpoint, {
|
||||
count: 0,
|
||||
errors: 0,
|
||||
totalDuration: 0,
|
||||
avgDuration: 0,
|
||||
minDuration: duration,
|
||||
maxDuration: duration
|
||||
});
|
||||
}
|
||||
|
||||
const endpointStats = this.apiStats.endpoints.get(endpoint);
|
||||
endpointStats.count++;
|
||||
if (isError) endpointStats.errors++;
|
||||
endpointStats.totalDuration += duration;
|
||||
endpointStats.avgDuration = endpointStats.totalDuration / endpointStats.count;
|
||||
endpointStats.minDuration = Math.min(endpointStats.minDuration, duration);
|
||||
endpointStats.maxDuration = Math.max(endpointStats.maxDuration, duration);
|
||||
|
||||
// 记录API指标
|
||||
this.recordApiMetrics({
|
||||
endpoint,
|
||||
statusCode,
|
||||
duration,
|
||||
timestamp: new Date(),
|
||||
isError
|
||||
});
|
||||
|
||||
// 检查是否需要发出警报
|
||||
if (duration > this.alertThresholds.api.responseTime) {
|
||||
perfEvents.emit('slowApiRequest', {
|
||||
endpoint,
|
||||
duration,
|
||||
threshold: this.alertThresholds.api.responseTime
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint,
|
||||
statusCode,
|
||||
duration,
|
||||
isError
|
||||
};
|
||||
}
|
||||
|
||||
// 记录数据库指标
|
||||
recordDatabaseMetrics(metrics) {
|
||||
this.metricsHistory.database.push({
|
||||
...metrics,
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
// 限制历史记录大小
|
||||
if (this.metricsHistory.database.length > this.historyLimit) {
|
||||
this.metricsHistory.database.shift();
|
||||
}
|
||||
|
||||
// 记录到文件
|
||||
this.logStatus('database', metrics);
|
||||
}
|
||||
|
||||
// 记录API指标
|
||||
recordApiMetrics(metrics) {
|
||||
this.metricsHistory.api.push(metrics);
|
||||
|
||||
// 限制历史记录大小
|
||||
if (this.metricsHistory.api.length > this.historyLimit) {
|
||||
this.metricsHistory.api.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// 记录系统指标
|
||||
recordSystemMetrics(metrics) {
|
||||
this.metricsHistory.system.push({
|
||||
...metrics,
|
||||
timestamp: new Date()
|
||||
});
|
||||
|
||||
// 限制历史记录大小
|
||||
if (this.metricsHistory.system.length > this.historyLimit) {
|
||||
this.metricsHistory.system.shift();
|
||||
}
|
||||
|
||||
// 记录到文件
|
||||
this.logStatus('system', metrics);
|
||||
}
|
||||
|
||||
// 检查系统警报
|
||||
checkSystemAlerts(metrics) {
|
||||
// 检查CPU使用率
|
||||
if (metrics.cpuUsage && metrics.cpuUsage.usage > this.alertThresholds.system.cpuUsage) {
|
||||
perfEvents.emit('highCpuUsage', {
|
||||
usage: metrics.cpuUsage.usage,
|
||||
threshold: this.alertThresholds.system.cpuUsage
|
||||
});
|
||||
}
|
||||
|
||||
// 检查内存使用率
|
||||
if (metrics.memoryUsage && metrics.memoryUsage.usage > this.alertThresholds.system.memoryUsage) {
|
||||
perfEvents.emit('highMemoryUsage', {
|
||||
usage: metrics.memoryUsage.usage,
|
||||
threshold: this.alertThresholds.system.memoryUsage
|
||||
});
|
||||
}
|
||||
|
||||
// 检查磁盘使用率
|
||||
if (metrics.diskUsage && metrics.diskUsage.usage > this.alertThresholds.system.diskUsage) {
|
||||
perfEvents.emit('highDiskUsage', {
|
||||
usage: metrics.diskUsage.usage,
|
||||
threshold: this.alertThresholds.system.diskUsage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 记录状态日志
|
||||
logStatus(type, data) {
|
||||
try {
|
||||
const logFile = path.join(this.logPath, `${type}-${new Date().toISOString().split('T')[0]}.log`);
|
||||
const logData = JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
data
|
||||
});
|
||||
|
||||
fs.appendFileSync(logFile, logData + '\n');
|
||||
} catch (error) {
|
||||
logger.error(`记录${type}状态日志失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// 记录错误日志
|
||||
logError(type, error) {
|
||||
try {
|
||||
const logFile = path.join(this.logPath, `${type}-error-${new Date().toISOString().split('T')[0]}.log`);
|
||||
const logData = JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
fs.appendFileSync(logFile, logData + '\n');
|
||||
logger.error(`${type}错误:`, error);
|
||||
} catch (logError) {
|
||||
logger.error(`记录${type}错误日志失败:`, logError);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取API统计信息
|
||||
getApiStats() {
|
||||
const responseTimes = this.apiStats.responseTimes;
|
||||
const avgResponseTime = responseTimes.length > 0
|
||||
? responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length
|
||||
: 0;
|
||||
|
||||
// 计算错误率
|
||||
const errorRate = this.apiStats.totalRequests > 0
|
||||
? (this.apiStats.totalErrors / this.apiStats.totalRequests) * 100
|
||||
: 0;
|
||||
|
||||
// 计算每分钟请求数
|
||||
const minutesSinceReset = (new Date() - this.apiStats.lastReset) / (1000 * 60);
|
||||
const requestsPerMinute = minutesSinceReset > 0
|
||||
? this.apiStats.totalRequests / minutesSinceReset
|
||||
: 0;
|
||||
|
||||
return {
|
||||
totalRequests: this.apiStats.totalRequests,
|
||||
totalErrors: this.apiStats.totalErrors,
|
||||
errorRate: parseFloat(errorRate.toFixed(2)),
|
||||
avgResponseTime: parseFloat(avgResponseTime.toFixed(2)),
|
||||
requestsPerMinute: parseFloat(requestsPerMinute.toFixed(2)),
|
||||
endpoints: Array.from(this.apiStats.endpoints.entries()).map(([endpoint, stats]) => ({
|
||||
endpoint,
|
||||
...stats,
|
||||
errorRate: stats.count > 0 ? (stats.errors / stats.count) * 100 : 0
|
||||
})),
|
||||
since: this.apiStats.lastReset
|
||||
};
|
||||
}
|
||||
|
||||
// 重置API统计信息
|
||||
resetApiStats() {
|
||||
this.apiStats = {
|
||||
totalRequests: 0,
|
||||
totalErrors: 0,
|
||||
endpoints: new Map(),
|
||||
responseTimes: [],
|
||||
lastReset: new Date()
|
||||
};
|
||||
|
||||
return { success: true, message: 'API统计信息已重置' };
|
||||
}
|
||||
|
||||
// 获取数据库性能指标
|
||||
async getDatabaseMetrics() {
|
||||
try {
|
||||
// 获取连接池状态
|
||||
const poolStatus = await getPoolStatus();
|
||||
|
||||
// 获取慢查询
|
||||
const slowQueries = queryOptimizer.getSlowQueries();
|
||||
|
||||
// 获取查询模式统计
|
||||
const queryPatterns = queryOptimizer.getQueryPatternStats();
|
||||
|
||||
return {
|
||||
poolStatus,
|
||||
slowQueries,
|
||||
queryPatterns,
|
||||
history: this.metricsHistory.database
|
||||
};
|
||||
} catch (error) {
|
||||
this.logError('database', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取系统性能指标
|
||||
getSystemMetrics() {
|
||||
try {
|
||||
return {
|
||||
current: {
|
||||
cpu: this.getCpuUsage(),
|
||||
memory: this.getMemoryUsage(),
|
||||
disk: this.getDiskUsage()
|
||||
},
|
||||
history: this.metricsHistory.system
|
||||
};
|
||||
} catch (error) {
|
||||
this.logError('system', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有性能指标
|
||||
async getAllMetrics() {
|
||||
try {
|
||||
const [databaseMetrics, apiStats, systemMetrics] = await Promise.all([
|
||||
this.getDatabaseMetrics(),
|
||||
this.getApiStats(),
|
||||
this.getSystemMetrics()
|
||||
]);
|
||||
|
||||
return {
|
||||
database: databaseMetrics,
|
||||
api: apiStats,
|
||||
system: systemMetrics,
|
||||
timestamp: new Date()
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('获取所有性能指标失败:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 设置警报阈值
|
||||
setAlertThresholds(thresholds) {
|
||||
if (thresholds.database) {
|
||||
this.alertThresholds.database = {
|
||||
...this.alertThresholds.database,
|
||||
...thresholds.database
|
||||
};
|
||||
}
|
||||
|
||||
if (thresholds.api) {
|
||||
this.alertThresholds.api = {
|
||||
...this.alertThresholds.api,
|
||||
...thresholds.api
|
||||
};
|
||||
}
|
||||
|
||||
if (thresholds.system) {
|
||||
this.alertThresholds.system = {
|
||||
...this.alertThresholds.system,
|
||||
...thresholds.system
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, thresholds: this.alertThresholds };
|
||||
}
|
||||
|
||||
// 获取警报阈值
|
||||
getAlertThresholds() {
|
||||
return this.alertThresholds;
|
||||
}
|
||||
|
||||
// 设置监控间隔
|
||||
setMonitoringInterval(interval) {
|
||||
if (this.isMonitoring) {
|
||||
this.stopMonitoring();
|
||||
this.startMonitoring(interval);
|
||||
} else {
|
||||
this.monitoringInterval = interval;
|
||||
}
|
||||
|
||||
return { success: true, interval };
|
||||
}
|
||||
|
||||
// 获取监控状态
|
||||
getMonitoringStatus() {
|
||||
return {
|
||||
isMonitoring: this.isMonitoring,
|
||||
interval: this.monitoringInterval,
|
||||
startedAt: this.startedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 创建性能监控实例
|
||||
const performanceMonitor = new PerformanceMonitor();
|
||||
|
||||
// 创建Express中间件
|
||||
function performanceMonitorMiddleware(req, res, next) {
|
||||
const startTime = Date.now();
|
||||
|
||||
// 在请求结束时记录性能指标
|
||||
res.on('finish', () => {
|
||||
performanceMonitor.recordApiRequest(req, res, startTime);
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
performanceMonitor,
|
||||
performanceMonitorMiddleware,
|
||||
events: perfEvents
|
||||
};
|
||||
517
backend/utils/query-optimizer.js
Normal file
517
backend/utils/query-optimizer.js
Normal file
@@ -0,0 +1,517 @@
|
||||
/**
|
||||
* 数据库查询优化器
|
||||
* @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;
|
||||
Reference in New Issue
Block a user