我最近都在用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();
接着再來逐行解釋如下:
import mysql from "mysql2/promise";
用的是 mysql2 套件的 Promise 版本 (npm install mysql2)。
傳統的 mysql2 是使用「回呼函式 (Callback)」,寫久了會變成 Callback Hell。引入 promise 版本後,我們就可以在 Route Handler 中優雅地使用 async/await 來處理非同步的資料庫操作。
const globalForMysql = globalThis as unknown as {
__mysqlPool: mysql.Pool | undefined;
};
globalThis 是一個 JavaScript 內建的全域物件(在 Node.js 環境指的就是 global,在瀏覽器指的就是 window)。
as unknown as { __mysqlPool: ... } 使用「雙重型別斷言(Type Assertion)」( 先轉成最寬鬆的 unknown,再強制轉型成「含有 __mysqlPool 屬性(其型別可能是 mysql.Pool 或 undefined)的物件」,好讓 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;
}
定義一個名為 getRequiredEnv 的防禦型函式,接收一個字串參數 name(環境變數的名稱),並保證回傳的一定是字串
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,
});
}
建立「連線池(Pool)」,而非「單一連線(Connection)」。
連線池會幫你管理多個連線,重複利用,不會每次查詢都重新開關 TCP 連線(三向交握),效能好很多。
waitForConnections: true:當連線池裡的 5 個連線都被拿光了,下一個請求進來時,要「排隊等待」有人釋放連線,而不是直接報錯拒絕。
queueLimit: 0:當連線滿了,排隊等待的請求數量「沒有上限」(0 代表不限制)。
enableKeepAlive: true 與 keepAliveInitialDelay: 10000:
這是為了解決 Serverless 環境 或 防火牆 的閒置斷線問題。每隔 10000 毫秒(10 秒),連線池會自動向 MySQL 發送一個「心跳包(TCP Keep-Alive)」,告訴資料庫「我還活著,別把我斷線」,避免發生 ETIMEDOUT 或 PROTOCOL_CONNECTION_LOST 的錯誤。
export const pool =
process.env.NODE_ENV !== "production"
? globalForMysql.__mysqlPool || (globalForMysql.__mysqlPool = createNewPool())
: createNewPool();
檢查如果不是非生產環境(也就是 development 開發環境或 test 測試環境)。