246 lines
6.3 KiB
JavaScript
246 lines
6.3 KiB
JavaScript
|
|
/**
|
|||
|
|
* 数据库查询优化器
|
|||
|
|
* @file query-optimizer.js
|
|||
|
|
* @description 监控和优化SQL查询性能
|
|||
|
|
*/
|
|||
|
|
const { sequelize } = require('./database-pool');
|
|||
|
|
const { QueryTypes } = require('sequelize');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 记录查询性能
|
|||
|
|
* @param {string} query SQL查询语句
|
|||
|
|
* @param {number} executionTime 执行时间(毫秒)
|
|||
|
|
* @returns {Promise<void>}
|
|||
|
|
*/
|
|||
|
|
async function logQueryPerformance(query, executionTime) {
|
|||
|
|
try {
|
|||
|
|
// 简化查询语句(移除参数值)
|
|||
|
|
const simplifiedQuery = query.replace(/('([^']*)'|"([^"]*)"|`([^`]*)`)/g, '?');
|
|||
|
|
|
|||
|
|
// 记录到性能日志表
|
|||
|
|
await sequelize.query(
|
|||
|
|
'INSERT INTO query_performance_logs (query, execution_time, timestamp) VALUES (?, ?, NOW())',
|
|||
|
|
{
|
|||
|
|
replacements: [simplifiedQuery, executionTime],
|
|||
|
|
type: QueryTypes.INSERT
|
|||
|
|
}
|
|||
|
|
).catch(() => {
|
|||
|
|
// 如果表不存在,创建表
|
|||
|
|
return sequelize.query(
|
|||
|
|
`CREATE TABLE IF NOT EXISTS query_performance_logs (
|
|||
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|||
|
|
query TEXT NOT NULL,
|
|||
|
|
execution_time FLOAT NOT NULL,
|
|||
|
|
timestamp DATETIME NOT NULL,
|
|||
|
|
INDEX (timestamp),
|
|||
|
|
INDEX (execution_time)
|
|||
|
|
)`,
|
|||
|
|
{ type: QueryTypes.RAW }
|
|||
|
|
).then(() => {
|
|||
|
|
// 重新尝试插入
|
|||
|
|
return sequelize.query(
|
|||
|
|
'INSERT INTO query_performance_logs (query, execution_time, timestamp) VALUES (?, ?, NOW())',
|
|||
|
|
{
|
|||
|
|
replacements: [simplifiedQuery, executionTime],
|
|||
|
|
type: QueryTypes.INSERT
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('记录查询性能失败:', error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 识别慢查询
|
|||
|
|
* @param {number} threshold 慢查询阈值(毫秒),默认为500ms
|
|||
|
|
* @returns {Promise<Array>} 慢查询列表
|
|||
|
|
*/
|
|||
|
|
async function identifySlowQueries(threshold = 500) {
|
|||
|
|
try {
|
|||
|
|
// 查询性能日志表中的慢查询
|
|||
|
|
const slowQueries = await sequelize.query(
|
|||
|
|
'SELECT query, AVG(execution_time) as avg_time, COUNT(*) as count, MAX(timestamp) as last_seen ' +
|
|||
|
|
'FROM query_performance_logs ' +
|
|||
|
|
'WHERE execution_time > ? ' +
|
|||
|
|
'GROUP BY query ' +
|
|||
|
|
'ORDER BY avg_time DESC',
|
|||
|
|
{
|
|||
|
|
replacements: [threshold],
|
|||
|
|
type: QueryTypes.SELECT
|
|||
|
|
}
|
|||
|
|
).catch(() => {
|
|||
|
|
// 如果表不存在,返回空数组
|
|||
|
|
return [];
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return slowQueries;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('识别慢查询失败:', error);
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 分析和优化表
|
|||
|
|
* @param {string} tableName 表名
|
|||
|
|
* @returns {Promise<Object>} 优化结果
|
|||
|
|
*/
|
|||
|
|
async function analyzeAndOptimizeTable(tableName) {
|
|||
|
|
try {
|
|||
|
|
// 分析表
|
|||
|
|
await sequelize.query(`ANALYZE TABLE ${tableName}`, { type: QueryTypes.RAW });
|
|||
|
|
|
|||
|
|
// 优化表
|
|||
|
|
const optimizeResult = await sequelize.query(`OPTIMIZE TABLE ${tableName}`, { type: QueryTypes.RAW });
|
|||
|
|
|
|||
|
|
return optimizeResult[0];
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`分析和优化表 ${tableName} 失败:`, error);
|
|||
|
|
return { error: error.message };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取表的索引信息
|
|||
|
|
* @param {string} tableName 表名
|
|||
|
|
* @returns {Promise<Array>} 索引信息
|
|||
|
|
*/
|
|||
|
|
async function getIndexInfo(tableName) {
|
|||
|
|
try {
|
|||
|
|
const indexInfo = await sequelize.query(
|
|||
|
|
'SHOW INDEX FROM ??',
|
|||
|
|
{
|
|||
|
|
replacements: [tableName],
|
|||
|
|
type: QueryTypes.SELECT
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
return indexInfo;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`获取表 ${tableName} 的索引信息失败:`, error);
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取表信息
|
|||
|
|
* @param {string} tableName 表名
|
|||
|
|
* @returns {Promise<Object>} 表信息
|
|||
|
|
*/
|
|||
|
|
async function getTableInfo(tableName) {
|
|||
|
|
try {
|
|||
|
|
// 获取表状态
|
|||
|
|
const tableStatus = await sequelize.query(
|
|||
|
|
'SHOW TABLE STATUS LIKE ?',
|
|||
|
|
{
|
|||
|
|
replacements: [tableName],
|
|||
|
|
type: QueryTypes.SELECT
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 获取表结构
|
|||
|
|
const tableStructure = await sequelize.query(
|
|||
|
|
'DESCRIBE ??',
|
|||
|
|
{
|
|||
|
|
replacements: [tableName],
|
|||
|
|
type: QueryTypes.SELECT
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
status: tableStatus[0] || {},
|
|||
|
|
structure: tableStructure
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`获取表 ${tableName} 信息失败:`, error);
|
|||
|
|
return { error: error.message };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解释查询计划
|
|||
|
|
* @param {Object} query Sequelize查询对象
|
|||
|
|
* @returns {Promise<Array>} 查询计划
|
|||
|
|
*/
|
|||
|
|
async function explainQuery(query) {
|
|||
|
|
try {
|
|||
|
|
// 获取SQL语句
|
|||
|
|
const sql = query.getQueryString();
|
|||
|
|
|
|||
|
|
// 执行EXPLAIN
|
|||
|
|
const explainResult = await sequelize.query(
|
|||
|
|
`EXPLAIN ${sql}`,
|
|||
|
|
{
|
|||
|
|
type: QueryTypes.SELECT
|
|||
|
|
}
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
return explainResult;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('解释查询计划失败:', error);
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取数据库状态
|
|||
|
|
* @returns {Promise<Object>} 数据库状态
|
|||
|
|
*/
|
|||
|
|
async function getDatabaseStatus() {
|
|||
|
|
try {
|
|||
|
|
// 获取全局状态
|
|||
|
|
const globalStatus = await 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
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取数据库状态失败:', error);
|
|||
|
|
return { error: error.message };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
logQueryPerformance,
|
|||
|
|
identifySlowQueries,
|
|||
|
|
analyzeAndOptimizeTable,
|
|||
|
|
getIndexInfo,
|
|||
|
|
getTableInfo,
|
|||
|
|
explainQuery,
|
|||
|
|
getDatabaseStatus
|
|||
|
|
};
|