You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS14+MySQL2遭遇Too many connections错误求助

解决MySQL "Too many connections" 与Sleep连接堆积问题

问题分析

从show processlist输出可以看到,大量处于Sleep状态的连接长时间存在(远超配置的60秒空闲超时),且每次刷新页面都会新增连接。结合你的Next.js代码(App Router架构),核心问题是连接池实例泄漏:开发环境下的热重载会导致db.js模块被反复编译加载,每次加载都会创建新的连接池,而旧的连接池未被主动关闭,其持有的连接会一直保持Sleep状态,无法被MySQL回收,最终耗尽连接数。

你的connection.release()写法本身符合连接池设计,但旧连接池的泄漏导致连接无法被复用和回收,才会出现堆积。

解决方案

1. 修复连接池泄漏(核心)

修改db.js,添加热重载和进程终止时的连接池清理逻辑,确保旧实例被正确关闭:

// app\test\db.js

import mysql from 'mysql2/promise'

let pool

function createPool() {
  try {
    if (!pool) {
      pool = mysql.createPool({
        host: process.env.DB_HOST,
        user: process.env.DB_USER,
        password: process.env.DB_PASSWORD,
        database: process.env.DB_NAME,
        connectionLimit: 10,
        waitForConnections: true,
        queueLimit: 0,
        enableKeepAlive: true,
        keepAliveInitialDelay: 0,
        idleTimeout: 60000, // 空闲60秒后自动回收连接
      })
    }
    return pool
  } catch (error) {
    console.error(`Could not connect - ${error.message}`)
    throw new Error('Database connection failed')
  }
}

// 处理开发环境热重载与进程终止时的连接池清理
if (process.env.NODE_ENV === 'development') {
  // Webpack热重载模块卸载时,关闭旧连接池
  if (module.hot) {
    module.hot.dispose(async () => {
      await closePool()
      pool = null
    })
  }
}

// 捕获进程终止信号,确保生产环境也能清理连接池
process.on('SIGTERM', async () => {
  await closePool()
  process.exit(0)
})

const db = createPool()

export async function executeQuery(sql, values = []) {
  const connection = await db.getConnection()
  try {
    const [results] = await connection.execute(sql, values)
    return results
  } catch (error) {
    console.error('Query execution failed:', error)
    throw error
  } finally {
    connection.release() // 保持连接放回池,复用连接
  }
}

export async function closePool() {
  if (pool) {
    await pool.end()
    pool = null
  }
}

2. 同步MySQL服务器端超时配置

检查MySQL的wait_timeout和interactive_timeout参数,确保它们的值不小于连接池的idleTimeout(否则MySQL会主动断开空闲连接,导致连接池中的连接失效)。

执行以下SQL查看当前配置:

SHOW VARIABLES LIKE 'wait_timeout';
SHOW VARIABLES LIKE 'interactive_timeout';

建议设置为比idleTimeout稍大的值(比如70秒),可修改MySQL配置文件(my.cnf或my.ini)后重启服务:

wait_timeout = 70
interactive_timeout = 70

3. 优化查询执行(可选)

在Next.js App Router中,服务器组件的顶层await合法,但可以通过缓存减少重复查询,降低连接池压力:

// app\test\page.js

import testQuery from "./actions"
import { cache } from 'react'

// 缓存查询结果,避免相同请求重复执行数据库查询
const cachedTestQuery = cache(testQuery)

export default async function DBAccess() {
  const res = await cachedTestQuery()
  return (
    <div className="flex w-screen h-screen justify-center items-center">
      <p className="">RESULT: {res[0]}</p>
    </div>
  )
}

为什么connection.destroy()能临时解决?

destroy()会直接关闭当前连接,不会放回连接池,因此每次请求创建的连接用完就被销毁,不会堆积。但这完全放弃了连接池的复用优势,会导致每次请求都重新建立数据库连接,性能大幅下降,不是长期解决方案。

内容的提问来源于stack exchange,提问作者hikari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 05:45:00