Next.js – 連接MySQL

建議了解以下文章內容後再往下讀:

https://hyc.eshachem.com/program/js-callback-hell/

https://hyc.eshachem.com/program/js-defensive-function/

https://hyc.eshachem.com/program/tcp-keep-alive/

我最近都在用Next.js進行全端開發,想當然爾第一個遇到的問題就是:怎麼接DB?因此這裏提供解法並詳細說明我後來的寫法~!

這裏先show出程式碼:

import mysql from "mysql2/promise";

const globalForMysql = globalThis as unknown as {
  __mysqlPool: mysql.Pool | undefined;
};

function getRequiredEnv(name: string): string {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required env: ${name}`);
  }
  return value;
}

function createNewPool() {
  return mysql.createPool({
    host: getRequiredEnv("DB_HOST"),
    port: Number(process.env.DB_PORT || 3306),
    user: getRequiredEnv("DB_USER"),
    password: getRequiredEnv("DB_PASSWORD"),
    database: getRequiredEnv("DB_NAME"),
    waitForConnections: true,
    connectionLimit: 5,
    queueLimit: 0,
    enableKeepAlive: true,
    keepAliveInitialDelay: 10000,
  });
}

export const pool =
  process.env.NODE_ENV !== "production"
    ? globalForMysql.__mysqlPool || (globalForMysql.__mysqlPool = createNewPool())
    : createNewPool();Code language: JavaScript (javascript)

基礎設定

接着再來逐行解釋如下:

import mysql from "mysql2/promise";Code language: JavaScript (javascript)
  • 用的是 mysql2 套件的 Promise 版本
  • 傳統的 mysql2 是使用「回呼函式 (Callback)」,寫久了會變成 Callback Hell。引入 promise 版本後,我們就可以在 Route Handler 中優雅地使用 async/await 來處理非同步的資料庫操作。

const globalForMysql = globalThis as unknown as {
  __mysqlPool: mysql.Pool | undefined;
};Code language: JavaScript (javascript)
  • globalThis 是一個 JavaScript 內建的全域物件(在 Node.js 環境指的就是 global,在瀏覽器指的就是 window)。
  • as unknown as { __mysqlPool: ... } 使用「雙重型別斷言(Type Assertion)」( 先轉成最寬鬆的 unknown,再強制轉型成「含有 __mysqlPool 屬性(其型別可能是 mysql.Poolundefined)的物件」,好讓 TypeScript 閉嘴)因為 JavaScript 的 globalThis 預設並沒有 __mysqlPool 這個屬性,如果你直接寫 globalThis.__mysqlPool,TypeScript 會噴錯。

function getRequiredEnv(name: string): string {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required env: ${name}`);
  }
  return value;
}Code language: JavaScript (javascript)

定義一個名為 getRequiredEnv 的防禦型函式,接收一個字串參數 name(環境變數的名稱),並保證回傳的一定是字串


建立連線池(Connection Pool)


function createNewPool() {
  return mysql.createPool({
    host: getRequiredEnv("DB_HOST"),
    port: Number(process.env.DB_PORT || 3306),
    user: getRequiredEnv("DB_USER"),
    password: getRequiredEnv("DB_PASSWORD"),
    database: getRequiredEnv("DB_NAME"),
    waitForConnections: true,
    connectionLimit: 5, //設定連線最多數量
    queueLimit: 0,
    enableKeepAlive: true,
    keepAliveInitialDelay: 10000,
  });
}Code language: JavaScript (javascript)

建立「連線池(Pool)」,而非「單一連線(Connection)」。

連線池會幫你管理多個連線,重複利用,不會每次查詢都重新開關 TCP 連線(三向交握),效能好很多。

  • waitForConnections: true:當連線池裡的 5 個連線都被拿光了,下一個請求進來時,要「排隊等待」有人釋放連線,而不是直接報錯拒絕。
  • queueLimit: 0:當連線滿了,排隊等待的請求數量「沒有上限」(0 代表不限制)。
  • enableKeepAlive: truekeepAliveInitialDelay: 10000
  • 這是為了解決 Serverless 環境防火牆 的閒置斷線問題。每隔 10000 毫秒(10 秒),連線池會自動向 MySQL 發送一個「心跳包(TCP Keep-Alive)」,告訴資料庫「我還活著,別把我斷線」,避免發生 ETIMEDOUTPROTOCOL_CONNECTION_LOST 的錯誤。

單例模式與環境分流

export const pool =
  process.env.NODE_ENV !== "production"
    ? globalForMysql.__mysqlPool || (globalForMysql.__mysqlPool = createNewPool())
    : createNewPool();Code language: JavaScript (javascript)

檢查如果不是非生產環境(也就是 development 開發環境或 test 測試環境)。