修正資料庫未關閉問題

This commit is contained in:
2025-09-21 21:23:47 +08:00
parent 36e29c5a3f
commit 38ae30d611
11 changed files with 684 additions and 111 deletions

View File

@@ -0,0 +1,85 @@
// =====================================================
// 連線監控 API
// =====================================================
import { NextRequest, NextResponse } from 'next/server';
import { connectionMonitor } from '@/lib/connection-monitor';
export async function GET(request: NextRequest) {
try {
// 獲取連線統計
const stats = await connectionMonitor.getConnectionStats();
// 獲取監控狀態
const isMonitoring = connectionMonitor.isCurrentlyMonitoring();
return NextResponse.json({
success: true,
data: {
stats,
isMonitoring,
timestamp: new Date().toISOString()
}
});
} catch (error) {
console.error('獲取連線監控數據失敗:', error);
return NextResponse.json({
success: false,
message: '獲取連線監控數據失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { action } = body;
switch (action) {
case 'start_monitoring':
const interval = body.interval || 30000;
connectionMonitor.startMonitoring(interval);
return NextResponse.json({
success: true,
message: `連線監控已啟動,檢查間隔: ${interval}ms`
});
case 'stop_monitoring':
connectionMonitor.stopMonitoring();
return NextResponse.json({
success: true,
message: '連線監控已停止'
});
case 'force_cleanup':
await connectionMonitor.forceCleanup();
return NextResponse.json({
success: true,
message: '強制清理連線完成'
});
case 'check_health':
const healthStats = await connectionMonitor.checkConnectionHealth();
return NextResponse.json({
success: true,
data: healthStats
});
default:
return NextResponse.json({
success: false,
message: '無效的操作'
}, { status: 400 });
}
} catch (error) {
console.error('執行連線監控操作失敗:', error);
return NextResponse.json({
success: false,
message: '執行連線監控操作失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}

View File

@@ -0,0 +1,66 @@
// =====================================================
// 強制清理連線 API
// =====================================================
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
import { connectionMonitor } from '@/lib/connection-monitor';
export async function POST(request: NextRequest) {
try {
console.log('🧹 開始強制清理資料庫連線...');
// 獲取清理前的連線狀態
const beforeStats = await connectionMonitor.getConnectionStats();
console.log(`清理前連線數: ${beforeStats.activeConnections}`);
// 強制關閉連線池
try {
await db.close();
console.log('✅ 主要資料庫連線池已關閉');
} catch (error) {
console.error('❌ 關閉主要連線池失敗:', error);
}
// 等待一段時間讓連線完全關閉
await new Promise(resolve => setTimeout(resolve, 2000));
// 重新初始化連線池
try {
// 重新創建連線池實例
const { Database } = await import('@/lib/database');
const newDb = Database.getInstance();
console.log('✅ 資料庫連線池已重新初始化');
} catch (error) {
console.error('❌ 重新初始化連線池失敗:', error);
}
// 獲取清理後的連線狀態
const afterStats = await connectionMonitor.getConnectionStats();
console.log(`清理後連線數: ${afterStats.activeConnections}`);
return NextResponse.json({
success: true,
message: '強制清理完成',
data: {
before: {
activeConnections: beforeStats.activeConnections,
usagePercentage: beforeStats.usagePercentage
},
after: {
activeConnections: afterStats.activeConnections,
usagePercentage: afterStats.usagePercentage
},
cleaned: beforeStats.activeConnections - afterStats.activeConnections
}
});
} catch (error) {
console.error('強制清理失敗:', error);
return NextResponse.json({
success: false,
message: '強制清理失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}

View File

@@ -0,0 +1,70 @@
// =====================================================
// 強制終止連線 API
// =====================================================
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
export async function POST(request: NextRequest) {
try {
console.log('💀 開始強制終止所有資料庫連線...');
// 獲取所有 AI_Platform 的連線
const connections = await db.query(`
SELECT ID, USER, HOST, DB, COMMAND, TIME, STATE
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE USER = 'AI_Platform' AND DB = 'db_AI_Platform'
`);
console.log(`找到 ${connections.length} 個 AI_Platform 連線`);
const killedConnections = [];
// 終止每個連線
for (const conn of connections) {
try {
await db.query(`KILL CONNECTION ${conn.ID}`);
killedConnections.push({
id: conn.ID,
host: conn.HOST,
time: conn.TIME,
command: conn.COMMAND
});
console.log(`✅ 已終止連線 ${conn.ID} (閒置 ${conn.TIME} 秒)`);
} catch (error) {
console.error(`❌ 終止連線 ${conn.ID} 失敗:`, error);
}
}
// 等待連線完全關閉
await new Promise(resolve => setTimeout(resolve, 2000));
// 檢查剩餘連線
const remainingConnections = await db.query(`
SELECT COUNT(*) as count
FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE USER = 'AI_Platform' AND DB = 'db_AI_Platform'
`);
const remainingCount = remainingConnections[0]?.count || 0;
return NextResponse.json({
success: true,
message: '強制終止連線完成',
data: {
totalFound: connections.length,
killed: killedConnections.length,
remaining: remainingCount,
killedConnections: killedConnections
}
});
} catch (error) {
console.error('強制終止連線失敗:', error);
return NextResponse.json({
success: false,
message: '強制終止連線失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}

View File

@@ -0,0 +1,67 @@
// =====================================================
// 連線測試 API - 驗證連線釋放
// =====================================================
import { NextRequest, NextResponse } from 'next/server';
import { connectionMonitor } from '@/lib/connection-monitor';
export async function GET(request: NextRequest) {
try {
// 獲取測試前的連線狀態
const beforeStats = await connectionMonitor.getConnectionStats();
// 執行一些測試查詢
const testQueries = [
'SELECT 1 as test1',
'SELECT 2 as test2',
'SELECT 3 as test3',
'SELECT COUNT(*) as user_count FROM users',
'SELECT COUNT(*) as app_count FROM apps'
];
console.log('🧪 開始連線測試...');
console.log(`測試前連線數: ${beforeStats.activeConnections}`);
// 執行測試查詢
for (let i = 0; i < testQueries.length; i++) {
const { db } = await import('@/lib/database');
await db.query(testQueries[i]);
console.log(`✅ 完成測試查詢 ${i + 1}`);
}
// 等待一小段時間讓連線釋放
await new Promise(resolve => setTimeout(resolve, 1000));
// 獲取測試後的連線狀態
const afterStats = await connectionMonitor.getConnectionStats();
console.log(`測試後連線數: ${afterStats.activeConnections}`);
return NextResponse.json({
success: true,
message: '連線測試完成',
data: {
before: {
activeConnections: beforeStats.activeConnections,
usagePercentage: beforeStats.usagePercentage
},
after: {
activeConnections: afterStats.activeConnections,
usagePercentage: afterStats.usagePercentage
},
difference: {
connectionChange: afterStats.activeConnections - beforeStats.activeConnections,
isReleased: afterStats.activeConnections <= beforeStats.activeConnections
}
}
});
} catch (error) {
console.error('連線測試失敗:', error);
return NextResponse.json({
success: false,
message: '連線測試失敗',
error: error instanceof Error ? error.message : '未知錯誤'
}, { status: 500 });
}
}

206
lib/connection-monitor.ts Normal file
View File

@@ -0,0 +1,206 @@
// =====================================================
// 資料庫連線監控服務
// =====================================================
import { db } from './database';
import { dbFailover } from './database-failover';
export interface ConnectionStats {
totalConnections: number;
activeConnections: number;
idleConnections: number;
queuedRequests: number;
maxConnections: number;
usagePercentage: number;
isHealthy: boolean;
lastCheck: Date;
}
export class ConnectionMonitor {
private static instance: ConnectionMonitor;
private stats: ConnectionStats | null = null;
private checkInterval: NodeJS.Timeout | null = null;
private isMonitoring = false;
private constructor() {}
public static getInstance(): ConnectionMonitor {
if (!ConnectionMonitor.instance) {
ConnectionMonitor.instance = new ConnectionMonitor();
}
return ConnectionMonitor.instance;
}
// 開始監控
public startMonitoring(intervalMs: number = 30000): void {
if (this.isMonitoring) {
console.log('⚠️ 連線監控已在運行中');
return;
}
this.isMonitoring = true;
console.log(`🔍 開始連線監控,檢查間隔: ${intervalMs}ms`);
this.checkInterval = setInterval(async () => {
await this.checkConnectionHealth();
}, intervalMs);
// 立即執行一次檢查
this.checkConnectionHealth();
}
// 停止監控
public stopMonitoring(): void {
if (this.checkInterval) {
clearInterval(this.checkInterval);
this.checkInterval = null;
}
this.isMonitoring = false;
console.log('🛑 連線監控已停止');
}
// 檢查連線健康狀態
public async checkConnectionHealth(): Promise<ConnectionStats> {
try {
const stats = await this.getConnectionStats();
this.stats = stats;
// 記錄連線狀態
console.log(`📊 資料庫連線狀態: ${stats.activeConnections}/${stats.maxConnections} (${stats.usagePercentage.toFixed(1)}%)`);
// 檢查是否健康
if (stats.usagePercentage > 80) {
console.warn(`⚠️ 資料庫連線使用率過高: ${stats.usagePercentage.toFixed(1)}%`);
}
if (stats.usagePercentage > 95) {
console.error(`🚨 資料庫連線使用率危險: ${stats.usagePercentage.toFixed(1)}%`);
}
return stats;
} catch (error) {
console.error('❌ 連線健康檢查失敗:', error);
return {
totalConnections: 0,
activeConnections: 0,
idleConnections: 0,
queuedRequests: 0,
maxConnections: 0,
usagePercentage: 0,
isHealthy: false,
lastCheck: new Date()
};
}
}
// 獲取連線統計
public async getConnectionStats(): Promise<ConnectionStats> {
try {
// 檢查備援狀態
const failoverStatus = db.getFailoverStatus();
if (failoverStatus?.isEnabled) {
// 使用備援系統獲取統計
const result = await dbFailover.queryOne<{
current_connections: number;
max_connections: number;
}>('SHOW STATUS LIKE "Threads_connected"');
const maxResult = await dbFailover.queryOne<{
Variable_name: string;
Value: string;
}>('SHOW VARIABLES LIKE "max_connections"');
const currentConnections = result?.current_connections || 0;
const maxConnections = parseInt(maxResult?.Value || '0');
const usagePercentage = maxConnections > 0 ? (currentConnections / maxConnections) * 100 : 0;
return {
totalConnections: currentConnections,
activeConnections: currentConnections,
idleConnections: 0, // MySQL 不直接提供空閒連線數
queuedRequests: 0,
maxConnections,
usagePercentage,
isHealthy: usagePercentage < 90,
lastCheck: new Date()
};
} else {
// 使用主資料庫獲取統計
const result = await db.queryOne<{
current_connections: number;
max_connections: number;
}>('SHOW STATUS LIKE "Threads_connected"');
const maxResult = await db.queryOne<{
Variable_name: string;
Value: string;
}>('SHOW VARIABLES LIKE "max_connections"');
const currentConnections = result?.current_connections || 0;
const maxConnections = parseInt(maxResult?.Value || '0');
const usagePercentage = maxConnections > 0 ? (currentConnections / maxConnections) * 100 : 0;
return {
totalConnections: currentConnections,
activeConnections: currentConnections,
idleConnections: 0,
queuedRequests: 0,
maxConnections,
usagePercentage,
isHealthy: usagePercentage < 90,
lastCheck: new Date()
};
}
} catch (error) {
console.error('獲取連線統計失敗:', error);
throw error;
}
}
// 獲取當前統計
public getCurrentStats(): ConnectionStats | null {
return this.stats;
}
// 強制清理連線
public async forceCleanup(): Promise<void> {
try {
console.log('🧹 開始強制清理資料庫連線...');
// 獲取當前連線數
const stats = await this.getConnectionStats();
console.log(`清理前連線數: ${stats.activeConnections}`);
// 強制關閉所有空閒連線
try {
const { db } = await import('./database');
await db.close();
console.log('✅ 資料庫連線池已關閉');
// 等待連線完全關閉
await new Promise(resolve => setTimeout(resolve, 1000));
// 重新初始化
const { Database } = await import('./database');
const newDb = Database.getInstance();
console.log('✅ 資料庫連線池已重新初始化');
} catch (error) {
console.error('❌ 強制清理連線池失敗:', error);
}
console.log('✅ 連線清理完成');
} catch (error) {
console.error('❌ 連線清理失敗:', error);
}
}
// 檢查是否正在監控
public isCurrentlyMonitoring(): boolean {
return this.isMonitoring;
}
}
// 導出單例實例
export const connectionMonitor = ConnectionMonitor.getInstance();

View File

@@ -462,8 +462,24 @@ export class DatabaseFailover {
try {
connection = await this.getConnection();
const [rows] = await connection.execute(sql, params);
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 備援查詢連線已完全關閉');
return rows as T[];
} catch (error: any) {
// 確保連線被關閉
if (connection) {
try {
await connection.destroy();
} catch (destroyError) {
console.error('關閉連線時發生錯誤:', destroyError);
}
connection = null;
}
console.error(`資料庫查詢錯誤 (嘗試 ${retries + 1}/${maxRetries}):`, error.message);
console.error('查詢錯誤詳情:', {
code: error.code,
@@ -540,36 +556,60 @@ export class DatabaseFailover {
}
}
// 執行插入
// 執行插入 - 每次查詢完就完全關閉連線
public async insert(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
const connection = await this.getConnection();
let connection;
try {
connection = await this.getConnection();
const [result] = await connection.execute(sql, params);
return result as mysql.ResultSetHeader;
} finally {
connection.release();
if (connection) {
try {
await connection.destroy();
console.log('✅ 備援插入連線已完全關閉');
} catch (destroyError) {
console.error('關閉連線時發生錯誤:', destroyError);
}
}
}
}
// 執行更新
// 執行更新 - 每次查詢完就完全關閉連線
public async update(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
const connection = await this.getConnection();
let connection;
try {
connection = await this.getConnection();
const [result] = await connection.execute(sql, params);
return result as mysql.ResultSetHeader;
} finally {
connection.release();
if (connection) {
try {
await connection.destroy();
console.log('✅ 備援更新連線已完全關閉');
} catch (destroyError) {
console.error('關閉連線時發生錯誤:', destroyError);
}
}
}
}
// 執行刪除
// 執行刪除 - 每次查詢完就完全關閉連線
public async delete(sql: string, params?: any[]): Promise<mysql.ResultSetHeader> {
const connection = await this.getConnection();
let connection;
try {
connection = await this.getConnection();
const [result] = await connection.execute(sql, params);
return result as mysql.ResultSetHeader;
} finally {
connection.release();
if (connection) {
try {
await connection.destroy();
console.log('✅ 備援刪除連線已完全關閉');
} catch (destroyError) {
console.error('關閉連線時發生錯誤:', destroyError);
}
}
}
}

View File

@@ -6,10 +6,11 @@ import { NextRequest, NextResponse } from 'next/server';
import { db } from './database';
import { dbMonitor } from './database-monitor';
import { dbShutdownManager } from './database-shutdown-manager';
import { connectionMonitor } from './connection-monitor';
// 連線池狀態追蹤
let connectionCount = 0;
const maxConnections = 3; // 與資料庫配置保持一致
const maxConnections = 20; // 與資料庫配置保持一致
export function withDatabaseConnection<T extends any[]>(
handler: (...args: T) => Promise<NextResponse>
@@ -87,11 +88,12 @@ export async function checkConnectionHealth(): Promise<{
usagePercentage: number;
}> {
try {
const stats = dbMonitor.getConnectionStats();
// 使用連線監控器獲取詳細統計
const stats = await connectionMonitor.getConnectionStats();
return {
isHealthy: stats.usagePercentage < 80,
connectionCount: stats.currentConnections,
isHealthy: stats.isHealthy,
connectionCount: stats.activeConnections,
maxConnections: stats.maxConnections,
usagePercentage: stats.usagePercentage
};

View File

@@ -6,7 +6,7 @@ import { db } from './database';
import { PoolConnection } from 'mysql2/promise';
export abstract class DatabaseServiceBase {
// 靜態安全查詢方法 - 確保連線關閉
// 靜態安全查詢方法 - 每次查詢完就完全關閉連線
static async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
let connection: PoolConnection | null = null;
@@ -17,25 +17,27 @@ export abstract class DatabaseServiceBase {
// 執行查詢
const [rows] = await connection.execute(sql, params);
// 立即釋放連線
connection.release();
// 完全關閉連線(不是釋放連線池)
await connection.destroy();
connection = null;
console.log('✅ 靜態查詢連線已完全關閉');
return rows as T[];
} catch (error) {
// 確保連線被釋放
// 確保連線被關閉
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉靜態查詢連線時發生錯誤:', destroyError);
}
}
throw error;
}
}
// 實例安全查詢方法 - 確保連線關閉
// 實例安全查詢方法 - 每次查詢完就完全關閉連線
protected async safeQuery<T = any>(sql: string, params?: any[]): Promise<T[]> {
let connection: PoolConnection | null = null;
@@ -46,18 +48,20 @@ export abstract class DatabaseServiceBase {
// 執行查詢
const [rows] = await connection.execute(sql, params);
// 立即釋放連線
connection.release();
// 完全關閉連線(不是釋放連線池)
await connection.destroy();
connection = null;
console.log('✅ 連線已完全關閉');
return rows as T[];
} catch (error) {
// 確保連線被釋放
// 確保連線被關閉
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉連線時發生錯誤:', destroyError);
}
}
throw error;
@@ -76,7 +80,7 @@ export abstract class DatabaseServiceBase {
return results.length > 0 ? results[0] : null;
}
// 靜態安全插入方法
// 靜態安全插入方法 - 每次查詢完就完全關閉連線
static async safeInsert(sql: string, params?: any[]): Promise<any> {
let connection: PoolConnection | null = null;
@@ -84,24 +88,26 @@ export abstract class DatabaseServiceBase {
connection = await db.getConnection();
const [result] = await connection.execute(sql, params);
// 立即釋放連線
connection.release();
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 靜態插入連線已完全關閉');
return result;
} catch (error) {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉靜態插入連線時發生錯誤:', destroyError);
}
}
throw error;
}
}
// 實例安全插入方法
// 實例安全插入方法 - 每次查詢完就完全關閉連線
protected async safeInsert(sql: string, params?: any[]): Promise<any> {
let connection: PoolConnection | null = null;
@@ -109,24 +115,26 @@ export abstract class DatabaseServiceBase {
connection = await db.getConnection();
const [result] = await connection.execute(sql, params);
// 立即釋放連線
connection.release();
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 實例插入連線已完全關閉');
return result;
} catch (error) {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉實例插入連線時發生錯誤:', destroyError);
}
}
throw error;
}
}
// 靜態安全更新方法
// 靜態安全更新方法 - 每次查詢完就完全關閉連線
static async safeUpdate(sql: string, params?: any[]): Promise<any> {
let connection: PoolConnection | null = null;
@@ -134,24 +142,26 @@ export abstract class DatabaseServiceBase {
connection = await db.getConnection();
const [result] = await connection.execute(sql, params);
// 立即釋放連線
connection.release();
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 靜態更新連線已完全關閉');
return result;
} catch (error) {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉靜態更新連線時發生錯誤:', destroyError);
}
}
throw error;
}
}
// 實例安全更新方法
// 實例安全更新方法 - 每次查詢完就完全關閉連線
protected async safeUpdate(sql: string, params?: any[]): Promise<any> {
let connection: PoolConnection | null = null;
@@ -159,24 +169,26 @@ export abstract class DatabaseServiceBase {
connection = await db.getConnection();
const [result] = await connection.execute(sql, params);
// 立即釋放連線
connection.release();
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 實例更新連線已完全關閉');
return result;
} catch (error) {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉實例更新連線時發生錯誤:', destroyError);
}
}
throw error;
}
}
// 靜態安全刪除方法
// 靜態安全刪除方法 - 每次查詢完就完全關閉連線
static async safeDelete(sql: string, params?: any[]): Promise<any> {
let connection: PoolConnection | null = null;
@@ -184,24 +196,26 @@ export abstract class DatabaseServiceBase {
connection = await db.getConnection();
const [result] = await connection.execute(sql, params);
// 立即釋放連線
connection.release();
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 靜態刪除連線已完全關閉');
return result;
} catch (error) {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉靜態刪除連線時發生錯誤:', destroyError);
}
}
throw error;
}
}
// 實例安全刪除方法
// 實例安全刪除方法 - 每次查詢完就完全關閉連線
protected async safeDelete(sql: string, params?: any[]): Promise<any> {
let connection: PoolConnection | null = null;
@@ -209,24 +223,26 @@ export abstract class DatabaseServiceBase {
connection = await db.getConnection();
const [result] = await connection.execute(sql, params);
// 立即釋放連線
connection.release();
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 實例刪除連線已完全關閉');
return result;
} catch (error) {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉實例刪除連線時發生錯誤:', destroyError);
}
}
throw error;
}
}
// 事務處理方法
// 事務處理方法 - 每次查詢完就完全關閉連線
protected async withTransaction<T>(
callback: (connection: PoolConnection) => Promise<T>
): Promise<T> {
@@ -240,10 +256,12 @@ export abstract class DatabaseServiceBase {
await connection.commit();
// 立即釋放連線
connection.release();
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 事務連線已完全關閉');
return result;
} catch (error) {
if (connection) {
@@ -254,16 +272,16 @@ export abstract class DatabaseServiceBase {
}
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉事務連線時發生錯誤:', destroyError);
}
}
throw error;
}
}
// 批次查詢方法 - 減少連線使用
// 批次查詢方法 - 每次查詢完就完全關閉連線
protected async batchQuery<T = any>(
queries: Array<{ sql: string; params?: any[] }>
): Promise<T[][]> {
@@ -278,17 +296,19 @@ export abstract class DatabaseServiceBase {
results.push(rows as T[]);
}
// 立即釋放連線
connection.release();
// 完全關閉連線
await connection.destroy();
connection = null;
console.log('✅ 批次查詢連線已完全關閉');
return results;
} catch (error) {
if (connection) {
try {
connection.release();
} catch (releaseError) {
console.error('釋放連線時發生錯誤:', releaseError);
await connection.destroy();
} catch (destroyError) {
console.error('關閉批次查詢連線時發生錯誤:', destroyError);
}
}
throw error;

View File

@@ -5,6 +5,7 @@
import { db } from './database';
import { dbFailover } from './database-failover';
import { dbMonitor } from './database-monitor';
import { connectionMonitor } from './connection-monitor';
export class DatabaseShutdownManager {
private static instance: DatabaseShutdownManager;
@@ -57,6 +58,17 @@ export class DatabaseShutdownManager {
}
});
// 添加連線監控關閉處理器
this.addShutdownHandler('connection-monitor', async () => {
console.log('🔄 正在停止連線監控服務...');
try {
connectionMonitor.stopMonitoring();
console.log('✅ 連線監控服務已停止');
} catch (error) {
console.error('❌ 停止連線監控服務時發生錯誤:', error);
}
});
// 註冊系統信號處理器
this.registerSystemHandlers();
@@ -75,6 +87,21 @@ export class DatabaseShutdownManager {
});
}
// 初始化服務
public initialize(): void {
console.log('🚀 初始化資料庫服務...');
// 啟動連線監控
try {
connectionMonitor.startMonitoring(30000); // 30秒檢查一次
console.log('✅ 連線監控已啟動');
} catch (error) {
console.error('❌ 啟動連線監控失敗:', error);
}
console.log('✅ 資料庫服務初始化完成');
}
// 註冊系統信號處理器
private registerSystemHandlers() {
if (typeof process === 'undefined') return;

View File

@@ -6,7 +6,7 @@ import mysql from 'mysql2/promise';
import { dbFailover } from './database-failover';
import { dbSync } from './database-sync';
// 資料庫配置
// 資料庫配置 - 強制快速釋放連線
const dbConfig = {
host: process.env.DB_HOST || '122.100.99.161',
port: parseInt(process.env.DB_PORT || '43306'),
@@ -16,19 +16,22 @@ const dbConfig = {
charset: 'utf8mb4',
timezone: '+08:00',
acquireTimeout: 5000, // 5秒獲取連接超時
timeout: 8000, // 8秒查詢超時
timeout: 10000, // 10秒查詢超時
reconnect: true,
connectionLimit: 3, // 進一步減少連接數,避免 Too many connections
queueLimit: 5, // 減少排隊數量
connectionLimit: 5, // 大幅減少連接數限制
queueLimit: 10, // 減少排隊數量
// 添加連接重試和錯誤處理配置
retryDelay: 1000,
maxRetries: 2,
// 添加連接池配置
idleTimeout: 30000, // 30秒空閒超時,快速釋放連接
maxIdle: 2, // 最空閒連接
// 強制快速釋放連線的配置
idleTimeout: 5000, // 5秒空閒超時,強制快速釋放
maxIdle: 1, // 最多只保留1個空閒連接
// 添加連接生命週期管理
maxReconnects: 3,
reconnectDelay: 2000,
reconnectDelay: 1000,
// 強制關閉空閒連線
keepAliveInitialDelay: 0,
enableKeepAlive: false, // 關閉 keepAlive
// 添加 SSL 配置(如果需要)
ssl: false as any,
};

View File

@@ -40,7 +40,7 @@ import type {
// =====================================================
export class UserService extends DatabaseServiceBase {
// 創建用戶
async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
async create(userData: Omit<User, 'id' | 'created_at' | 'updated_at'> & { id: string }): Promise<User> {
const sql = `
INSERT INTO users (id, name, email, password_hash, avatar, department, role, join_date, total_likes, total_views, status, last_login)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -572,9 +572,9 @@ export class UserService extends DatabaseServiceBase {
}
}
// 通用查詢方法
// 通用查詢方法 - 使用安全查詢確保連線釋放
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
return await db.query<T>(sql, params);
return await this.safeQuery<T>(sql, params);
}
// 獲取儀表板統計數據
@@ -764,21 +764,25 @@ export class UserService extends DatabaseServiceBase {
}
}
// 通用單一查詢方法
// 通用單一查詢方法 - 使用安全查詢確保連線釋放
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
return await db.queryOne<T>(sql, params);
return await this.safeQueryOne<T>(sql, params);
}
// 獲取所有用戶
async getAllUsers(limit = 50, offset = 0): Promise<User[]> {
const sql = 'SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?';
return await db.query<User>(sql, [limit, offset]);
return await this.safeQuery<User>(sql, [limit, offset]);
}
// 靜態方法保持向後兼容
static async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'> & { id?: string }): Promise<User> {
const service = new UserService();
return await service.create(userData);
// 確保 id 存在
if (!userData.id) {
userData.id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
return await service.create(userData as Omit<User, 'id' | 'created_at' | 'updated_at'> & { id: string });
}
static async getUserByEmail(email: string): Promise<User | null> {
@@ -2394,14 +2398,14 @@ export class AppService extends DatabaseServiceBase {
}
}
// 通用查詢方法
// 通用查詢方法 - 使用安全查詢確保連線釋放
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
return await db.query<T>(sql, params);
return await this.safeQuery<T>(sql, params);
}
// 通用單一查詢方法
// 通用單一查詢方法 - 使用安全查詢確保連線釋放
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
return await db.queryOne<T>(sql, params);
return await this.safeQueryOne<T>(sql, params);
}
// 獲取應用評分統計
@@ -3269,23 +3273,6 @@ export class AppService extends DatabaseServiceBase {
}
}
// 獲取應用的收藏數量
async getAppFavoritesCount(appId: string): Promise<number> {
try {
const sql = `
SELECT COUNT(*) as count
FROM user_favorites
WHERE app_id = ?
`;
const result = await this.queryOne(sql, [appId]);
return result.count || 0;
} catch (error) {
console.error('獲取應用收藏數量錯誤:', error);
return 0;
}
}
// 獲取用戶收藏的應用ID列表不依賴 apps 表狀態)
async getUserFavoriteAppIds(userId: string): Promise<string[]> {
try {