api-database-mysql
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMySQL Patterns (mysql2)
MySQL 模式(mysql2)
Quick Guide: Use mysql2/promise for all new code -- it provides async/await support over the mysql2 callback API. Always use(nevercreatePool()in production) withcreateConnection()for parameterized queries (prepared statements, LRU-cached). Type query results withexecute()generics for SELECTs andRowDataPacketfor INSERT/UPDATE/DELETE. For transactions, acquire a dedicated connection withResultSetHeader, wrap in try/finally to guaranteepool.getConnection(). Never interpolate user input into SQL strings -- always useconnection.release()placeholders. Handle?andER_DUP_ENTRYexplicitly in catch blocks.ER_LOCK_DEADLOCK
<critical_requirements>
快速指南: 所有新代码请使用 mysql2/promise——它在mysql2回调API之上提供了async/await支持。生产环境中始终使用(绝不要使用createPool()),配合createConnection()执行参数化查询(预编译语句,LRU缓存)。SELECT查询使用execute()泛型为结果添加类型,INSERT/UPDATE/DELETE使用RowDataPacket。处理事务时,通过ResultSetHeader获取专用连接,用try/finally包裹以确保pool.getConnection()。绝不要将用户输入直接插入SQL字符串——始终使用connection.release()占位符。在catch块中显式处理?和ER_DUP_ENTRY错误。ER_LOCK_DEADLOCK
<critical_requirements>
CRITICAL: Before Using This Skill
重要提示:使用本技能前须知
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST use with placeholders for ALL queries containing user input -- NEVER interpolate values into SQL strings with template literals or string concatenation)
execute()?(You MUST use for transactions and release the connection in a block -- pool convenience methods () use a different connection per call and cannot maintain transaction state)
pool.getConnection()finallypool.execute()(You MUST always import from for async/await code -- the base module returns callback-based objects that do not support )
mysql2/promisemysql2await(You MUST handle the pool event -- unhandled connection errors crash the Node.js process)
error</critical_requirements>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名、命名导出、导入顺序、、命名常量)import type
(所有包含用户输入的查询必须使用和占位符——绝不要用模板字符串或字符串拼接将值插入SQL字符串)
execute()?(事务必须使用获取连接,并在块中释放连接——池便捷方法()每次调用使用不同连接,无法维护事务状态)
pool.getConnection()finallypool.execute()(异步代码必须始终从导入——基础模块返回基于回调的对象,不支持)
mysql2/promisemysql2await(必须处理池的事件——未处理的连接错误会导致Node.js进程崩溃)
error</critical_requirements>
Examples
示例
- Core Patterns -- Pool setup, typed queries, prepared statements, connection lifecycle
- Transactions -- Manual transactions, savepoints, deadlock retry, nested operations
- Streaming & Batch -- Streaming large result sets, batch inserts, multiple statements
- Error Handling -- MySQL error codes, connection errors, retry strategies, graceful degradation
- Configuration -- SSL/TLS, named placeholders, pool tuning, monitoring events
Additional resources:
- reference.md -- Type cheat sheet, pool options, error codes, production checklist
Auto-detection: MySQL, mysql2, mysql2/promise, createPool, createConnection, RowDataPacket, ResultSetHeader, execute, prepared statement, pool.getConnection, beginTransaction, commit, rollback, ER_DUP_ENTRY, ER_LOCK_DEADLOCK, connectionLimit, SHOW TABLES, mysqldump, InnoDB, MariaDB
When to use:
- Direct SQL queries against MySQL or MariaDB databases
- Connection pool management for server applications
- Transactions requiring atomicity across multiple queries
- Streaming large result sets without loading all rows into memory
- Typed query results with TypeScript generics
- Batch inserts or multi-statement operations
Key patterns covered:
- Pool creation with and proper configuration
mysql2/promise - Prepared statements via with
execute()placeholders? - TypeScript generics with and
RowDataPacketResultSetHeader - Transaction lifecycle: ->
getConnection->beginTransaction/commit->rollbackrelease - Streaming with on the non-promise API
connection.query().stream() - Error handling for ,
ER_DUP_ENTRY, connection failuresER_LOCK_DEADLOCK - Pool events (,
acquire,release) for monitoringenqueue - SSL/TLS and named placeholders configuration
When NOT to use:
- When your project already uses an ORM or query builder for MySQL -- use that tool's skill instead
- For in-memory caching or key-value storage (use a dedicated caching solution)
- For document databases or graph queries (wrong database type)
- For one-off CLI scripts where a single connection suffices and pool overhead is unnecessary
<philosophy>
- 核心模式 —— 池配置、类型化查询、预编译语句、连接生命周期
- 事务 —— 手动事务、保存点、死锁重试、嵌套操作
- 流式处理与批量操作 —— 流式处理大型结果集、批量插入、多语句执行
- 错误处理 —— MySQL错误码、连接错误、重试策略、优雅降级
- 配置 —— SSL/TLS、命名占位符、池调优、监控事件
额外资源:
- reference.md —— 类型速查表、池选项、错误码、生产环境检查清单
自动检测关键词: MySQL, mysql2, mysql2/promise, createPool, createConnection, RowDataPacket, ResultSetHeader, execute, prepared statement, pool.getConnection, beginTransaction, commit, rollback, ER_DUP_ENTRY, ER_LOCK_DEADLOCK, connectionLimit, SHOW TABLES, mysqldump, InnoDB, MariaDB
适用场景:
- 直接对MySQL或MariaDB数据库执行SQL查询
- 服务器应用的连接池管理
- 需要跨多个查询保证原子性的事务
- 流式处理大型结果集,无需将所有行加载到内存
- 使用TypeScript泛型实现类型化查询结果
- 批量插入或多语句操作
涵盖的关键模式:
- 使用创建连接池并进行合理配置
mysql2/promise - 通过和
execute()占位符实现预编译语句? - 结合TypeScript泛型与、
RowDataPacketResultSetHeader - 事务生命周期:->
getConnection->beginTransaction/commit->rollbackrelease - 在非Promise API上使用实现流式处理
connection.query().stream() - 处理、
ER_DUP_ENTRY、连接失败等错误ER_LOCK_DEADLOCK - 池事件(、
acquire、release)监控enqueue - SSL/TLS和命名占位符配置
不适用场景:
- 项目已使用ORM或查询构建器操作MySQL——请使用对应工具的技能
- 内存缓存或键值存储场景(使用专用缓存解决方案)
- 文档数据库或图查询场景(数据库类型不匹配)
- 一次性CLI脚本,单连接足够且池开销不必要的场景
<philosophy>
Philosophy
设计理念
mysql2 is a low-level MySQL driver -- it sends SQL to MySQL and returns typed results. It does not generate SQL, manage migrations, or handle schema changes.
Core principles:
- Pools, not connections -- Production applications should always use . Pools manage connection lifecycle, handle reconnection, and prevent connection exhaustion.
createPool()is only appropriate for one-off scripts.createConnection() - Prepared statements always -- sends parameterized queries to MySQL's prepared statement protocol. The driver caches prepared statements in an LRU cache, so repeated queries skip the preparation step. Never use
execute()with string interpolation.query() - Type your results -- MySQL2's TypeScript generics (,
RowDataPacket) eliminateResultSetHeaderfrom query results. Define interfaces extendinganyfor each table shape.RowDataPacket - Transactions need dedicated connections -- Pool convenience methods (,
pool.execute()) may use different connections for each call. Transactions requirepool.query()to pin a single connection, withpool.getConnection()in aconnection.release()block.finally - Fail explicitly -- MySQL errors carry structured fields (
code,ER_DUP_ENTRY). CheckER_LOCK_DEADLOCKin catch blocks rather than parsing message strings.error.code
<patterns>
mysql2是一个底层MySQL驱动——它将SQL发送到MySQL并返回类型化结果。它不生成SQL、不管理迁移、不处理模式变更。
核心原则:
- 使用连接池,而非单连接——生产应用应始终使用。连接池管理连接生命周期、处理重连、防止连接耗尽。
createPool()仅适用于一次性脚本。createConnection() - 始终使用预编译语句——将参数化查询发送到MySQL的预编译语句协议。驱动在LRU缓存中缓存预编译语句,重复查询可跳过准备步骤。绝不要使用
execute()进行字符串插值。query() - 为结果添加类型——MySQL2的TypeScript泛型(、
RowDataPacket)消除查询结果中的ResultSetHeader类型。为每个表结构定义继承any的接口。RowDataPacket - 事务需要专用连接——池便捷方法(、
pool.execute())每次调用可能使用不同连接。事务需要pool.query()固定单个连接,并在pool.getConnection()块中调用finally。connection.release() - 显式处理失败——MySQL错误包含结构化的字段(
code、ER_DUP_ENTRY)。在catch块中检查ER_LOCK_DEADLOCK,而非解析错误消息字符串。error.code
<patterns>
Core Patterns
核心模式
Pattern 1: Pool Setup with mysql2/promise
模式1:使用mysql2/promise配置连接池
Create a connection pool with environment-based configuration and error handling. See examples/core.md for the complete setup pattern.
typescript
// Good Example - Production pool setup
import mysql from "mysql2/promise";
import type { Pool } from "mysql2/promise";
const DEFAULT_CONNECTION_LIMIT = 10;
const DEFAULT_IDLE_TIMEOUT_MS = 60_000;
function createDatabasePool(): Pool {
const url = process.env.DATABASE_URL;
if (!url) {
throw new Error("DATABASE_URL environment variable is required");
}
return mysql.createPool({
uri: url,
waitForConnections: true,
connectionLimit: DEFAULT_CONNECTION_LIMIT,
maxIdle: DEFAULT_CONNECTION_LIMIT,
idleTimeout: DEFAULT_IDLE_TIMEOUT_MS,
enableKeepAlive: true,
keepAliveInitialDelay: 0,
});
}
export { createDatabasePool };Why good: Environment variable validation, named constants for limits, queues requests instead of throwing, prevents stale connections
waitForConnections: trueenableKeepAlivetypescript
// Bad Example - Hardcoded single connection
import mysql from "mysql2/promise";
const connection = await mysql.createConnection({
host: "localhost",
user: "root",
password: "password123",
database: "mydb",
});
// Hardcoded credentials, single connection exhausts under load, no poolWhy bad: Hardcoded credentials leak in version control, single connection cannot handle concurrent requests, no automatic reconnection
基于环境变量配置创建连接池并处理错误。完整配置模式请查看examples/core.md。
typescript
// Good Example - Production pool setup
import mysql from "mysql2/promise";
import type { Pool } from "mysql2/promise";
const DEFAULT_CONNECTION_LIMIT = 10;
const DEFAULT_IDLE_TIMEOUT_MS = 60_000;
function createDatabasePool(): Pool {
const url = process.env.DATABASE_URL;
if (!url) {
throw new Error("DATABASE_URL environment variable is required");
}
return mysql.createPool({
uri: url,
waitForConnections: true,
connectionLimit: DEFAULT_CONNECTION_LIMIT,
maxIdle: DEFAULT_CONNECTION_LIMIT,
idleTimeout: DEFAULT_IDLE_TIMEOUT_MS,
enableKeepAlive: true,
keepAliveInitialDelay: 0,
});
}
export { createDatabasePool };优点: 环境变量验证、使用命名常量设置限制、会排队请求而非抛出错误、防止连接过期
waitForConnections: trueenableKeepAlivetypescript
// Bad Example - Hardcoded single connection
import mysql from "mysql2/promise";
const connection = await mysql.createConnection({
host: "localhost",
user: "root",
password: "password123",
database: "mydb",
});
// Hardcoded credentials, single connection exhausts under load, no pool缺点: 硬编码凭证会在版本控制中泄露、单连接无法处理并发请求、无自动重连机制
Pattern 2: Typed Queries with Generics
模式2:使用泛型实现类型化查询
Use for SELECTs and for mutations. See examples/core.md for all type patterns.
RowDataPacketResultSetHeadertypescript
// Good Example - Typed SELECT and INSERT
import type { Pool, RowDataPacket, ResultSetHeader } from "mysql2/promise";
interface UserRow extends RowDataPacket {
id: number;
email: string;
name: string;
created_at: Date;
}
async function getUserById(
pool: Pool,
userId: number,
): Promise<UserRow | null> {
const [rows] = await pool.execute<UserRow[]>(
"SELECT id, email, name, created_at FROM users WHERE id = ?",
[userId],
);
return rows[0] ?? null;
}
async function createUser(
pool: Pool,
email: string,
name: string,
): Promise<number> {
const [result] = await pool.execute<ResultSetHeader>(
"INSERT INTO users (email, name) VALUES (?, ?)",
[email, name],
);
return result.insertId;
}Why good: Interface extends for type safety, uses prepared statements, destructured skips field metadata, null check for missing rows
RowDataPacketexecute()[rows]typescript
// Bad Example - Untyped query with interpolation
const [rows] = await pool.query(`SELECT * FROM users WHERE id = ${userId}`);
// SQL injection vulnerability, untyped results, query() skips prepared statementsWhy bad: SQL injection via string interpolation, -typed results, does not use prepared statement protocol
anyquery()SELECT查询使用,变更操作使用。所有类型模式请查看examples/core.md。
RowDataPacketResultSetHeadertypescript
// Good Example - Typed SELECT and INSERT
import type { Pool, RowDataPacket, ResultSetHeader } from "mysql2/promise";
interface UserRow extends RowDataPacket {
id: number;
email: string;
name: string;
created_at: Date;
}
async function getUserById(
pool: Pool,
userId: number,
): Promise<UserRow | null> {
const [rows] = await pool.execute<UserRow[]>(
"SELECT id, email, name, created_at FROM users WHERE id = ?",
[userId],
);
return rows[0] ?? null;
}
async function createUser(
pool: Pool,
email: string,
name: string,
): Promise<number> {
const [result] = await pool.execute<ResultSetHeader>(
"INSERT INTO users (email, name) VALUES (?, ?)",
[email, name],
);
return result.insertId;
}优点: 接口继承保证类型安全、使用预编译语句、解构跳过字段元数据、对缺失行进行空值检查
RowDataPacketexecute()[rows]typescript
// Bad Example - Untyped query with interpolation
const [rows] = await pool.query(`SELECT * FROM users WHERE id = ${userId}`);
// SQL injection vulnerability, untyped results, query() skips prepared statements缺点: 字符串插值存在SQL注入风险、结果无类型、不使用预编译语句协议
query()Pattern 3: Transaction with Dedicated Connection
模式3:使用专用连接处理事务
Transactions require a single connection from the pool. See examples/transactions.md for savepoints, deadlock retry, and nested operations.
typescript
// Good Example - Transfer with transaction
async function transferFunds(
pool: Pool,
fromId: number,
toId: number,
amount: number,
): Promise<void> {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
await connection.execute(
"UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
[amount, fromId, amount],
);
await connection.execute(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
[amount, toId],
);
await connection.commit();
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}Why good: pins one connection, guarantees release even on error, in catch prevents partial commits, balance check in SQL prevents overdraft
getConnection()finallyrollback()事务需要从池中获取单个连接。保存点、死锁重试和嵌套操作请查看examples/transactions.md。
typescript
// Good Example - Transfer with transaction
async function transferFunds(
pool: Pool,
fromId: number,
toId: number,
amount: number,
): Promise<void> {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
await connection.execute(
"UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?",
[amount, fromId, amount],
);
await connection.execute(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
[amount, toId],
);
await connection.commit();
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
}优点: 固定单个连接、块保证即使出错也会释放连接、catch块中的防止部分提交、SQL中的余额检查防止透支
getConnection()finallyrollback()Pattern 4: Streaming Large Result Sets
模式4:流式处理大型结果集
Use the callback-based API for streaming -- the promise API does not support . See examples/streaming.md for backpressure handling and transform streams.
.stream()typescript
// Good Example - Stream rows without loading all into memory
import mysql from "mysql2";
function streamUsers(
pool: ReturnType<typeof mysql.createPool>,
): NodeJS.ReadableStream {
return pool
.query("SELECT * FROM users WHERE active = 1")
.stream({ highWaterMark: 100 });
}Why good: controls buffer size, rows are emitted one at a time via Node.js stream interface, constant memory usage regardless of result set size
highWaterMarkWhen to use: Result sets with 10K+ rows, ETL pipelines, CSV exports, data migrations
使用基于回调的API实现流式处理——Promise API不支持。背压处理和转换流请查看examples/streaming.md。
.stream()typescript
// Good Example - Stream rows without loading all into memory
import mysql from "mysql2";
function streamUsers(
pool: ReturnType<typeof mysql.createPool>,
): NodeJS.ReadableStream {
return pool
.query("SELECT * FROM users WHERE active = 1")
.stream({ highWaterMark: 100 });
}优点: 控制缓冲区大小、通过Node.js流接口逐行输出、无论结果集大小内存使用保持恒定
highWaterMark适用场景: 结果集包含10K+行、ETL管道、CSV导出、数据迁移
Pattern 5: Error Code Handling
模式5:错误码处理
MySQL errors carry structured and fields. See examples/error-handling.md for retry strategies and connection error handling.
codeerrnotypescript
// Good Example - Structured error handling
import type { Pool, ResultSetHeader } from "mysql2/promise";
const MYSQL_ER_DUP_ENTRY = "ER_DUP_ENTRY";
interface MysqlError extends Error {
code: string;
errno: number;
sqlState: string;
sqlMessage: string;
}
function isMysqlError(error: unknown): error is MysqlError {
return error instanceof Error && "code" in error && "errno" in error;
}
async function createUserSafe(
pool: Pool,
email: string,
name: string,
): Promise<{ insertId: number } | { duplicate: true }> {
try {
const [result] = await pool.execute<ResultSetHeader>(
"INSERT INTO users (email, name) VALUES (?, ?)",
[email, name],
);
return { insertId: result.insertId };
} catch (error) {
if (isMysqlError(error) && error.code === MYSQL_ER_DUP_ENTRY) {
return { duplicate: true };
}
throw error;
}
}Why good: Type guard for MySQL errors, named constant for error code, returns discriminated union instead of throwing on expected errors, re-throws unexpected errors
</patterns>
<performance>
MySQL错误包含结构化的和字段。重试策略和连接错误处理请查看examples/error-handling.md。
codeerrnotypescript
// Good Example - Structured error handling
import type { Pool, ResultSetHeader } from "mysql2/promise";
const MYSQL_ER_DUP_ENTRY = "ER_DUP_ENTRY";
interface MysqlError extends Error {
code: string;
errno: number;
sqlState: string;
sqlMessage: string;
}
function isMysqlError(error: unknown): error is MysqlError {
return error instanceof Error && "code" in error && "errno" in error;
}
async function createUserSafe(
pool: Pool,
email: string,
name: string,
): Promise<{ insertId: number } | { duplicate: true }> {
try {
const [result] = await pool.execute<ResultSetHeader>(
"INSERT INTO users (email, name) VALUES (?, ?)",
[email, name],
);
return { insertId: result.insertId };
} catch (error) {
if (isMysqlError(error) && error.code === MYSQL_ER_DUP_ENTRY) {
return { duplicate: true };
}
throw error;
}
}优点: MySQL错误类型守卫、错误码使用命名常量、返回区分联合类型而非在预期错误时抛出、重新抛出非预期错误
</patterns>
<performance>
Performance Optimization
性能优化
execute() vs query()
execute() vs query()
execute()execute()query()query().stream()execute()execute()query().stream()query()Pool Sizing
连接池大小
connectionLimit = (number of CPU cores * 2) + number of disk spindlesFor cloud databases, start with and increase under load testing. The MySQL server's must accommodate all application instances' pools combined.
connectionLimit: 10max_connectionsconnectionLimit = (CPU核心数 * 2) + 磁盘主轴数对于云数据库,初始设置,并在负载测试中调整。MySQL服务器的必须能容纳所有应用实例连接池的总和。
connectionLimit: 10max_connectionsenableKeepAlive
enableKeepAlive
Set to prevent firewalls and load balancers from dropping idle connections. Without this, connections that idle for longer than the intermediary's timeout are silently dropped, causing errors on the next query.
enableKeepAlive: trueECONNRESET设置以防止防火墙和负载均衡器断开空闲连接。如果不设置,空闲时间超过中间件超时的连接会被静默断开,导致下一次查询出现错误。
enableKeepAlive: trueECONNRESETBatch Inserts
批量插入
For inserting many rows, use a single statement instead of individual inserts -- see examples/streaming.md.
</performance>
INSERT ... VALUES (...), (...), (...)<decision_framework>
插入大量行时,使用单个语句而非单独插入——请查看examples/streaming.md。
</performance>
INSERT ... VALUES (...), (...), (...)<decision_framework>
Decision Framework
决策框架
Pool vs Connection
连接池 vs 单连接
What am I building?
-- Production server handling concurrent requests? -> createPool()
-- One-off CLI script or migration? -> createConnection() is acceptable
-- Serverless function (Lambda, Vercel)? -> createPool() with connectionLimit: 1我正在构建什么?
-- 处理并发请求的生产服务器? -> createPool()
-- 一次性CLI脚本或迁移? -> createConnection()是可接受的
-- 无服务器函数(Lambda、Vercel)? -> createPool()并设置connectionLimit: 1execute() vs query()
execute() vs query()
Does the SQL have user-provided parameters?
-- YES -> execute() with ? placeholders (ALWAYS)
-- NO, but same SQL runs repeatedly? -> execute() (benefits from LRU cache)
-- NO, SQL text itself is dynamic? -> query() (cannot prepare dynamic SQL)
-- Need to stream results? -> query().stream() on the callback APISQL是否包含用户提供的参数?
-- 是 -> 使用execute()和?占位符(必须)
-- 否,但相同SQL会重复执行? -> execute()(受益于LRU缓存)
-- 否,SQL文本本身是动态的? -> query()(无法预编译动态SQL)
-- 需要流式处理结果? -> 在回调API上使用query().stream()Pool method vs getConnection()
池方法 vs getConnection()
Is this a single query?
-- YES -> pool.execute() or pool.query() (auto-acquires and releases)
-- NO, multiple queries needing same connection? -> pool.getConnection()
-- Transaction? -> pool.getConnection() (REQUIRED)是否是单个查询?
-- 是 -> pool.execute()或pool.query()(自动获取和释放连接)
-- 否,多个查询需要同一连接? -> pool.getConnection()
-- 是否是事务? -> pool.getConnection()(必须)Error Handling Strategy
错误处理策略
What MySQL error did I get?
-- ER_DUP_ENTRY (1062) -> Handle as business logic (return conflict, not throw)
-- ER_LOCK_DEADLOCK (1213) -> Retry the entire transaction (MySQL rolled it back)
-- ER_LOCK_WAIT_TIMEOUT (1205) -> Retry or fail with timeout message
-- ECONNREFUSED / PROTOCOL_CONNECTION_LOST -> Connection issue, pool will reconnect
-- ER_ACCESS_DENIED_ERROR (1045) -> Configuration error, fail fast</decision_framework>
<red_flags>
遇到了什么MySQL错误?
-- ER_DUP_ENTRY (1062) -> 作为业务逻辑处理(返回冲突,不抛出)
-- ER_LOCK_DEADLOCK (1213) -> 重试整个事务(MySQL已回滚)
-- ER_LOCK_WAIT_TIMEOUT (1205) -> 重试或返回超时错误
-- ECONNREFUSED / PROTOCOL_CONNECTION_LOST -> 连接问题,池会自动重连
-- ER_ACCESS_DENIED_ERROR (1045) -> 配置错误,快速失败</decision_framework>
<red_flags>
RED FLAGS
警示信号
High Priority Issues:
- Interpolating user input into SQL strings (SELECT * FROM users WHERE id = ${id}`
\?) -- SQL injection vulnerability, always useexecute()`placeholders with - Using or
pool.execute()for transactions -- each call may use a different connection, breaking transaction isolation; usepool.query()pool.getConnection() - Importing from instead of
mysql2for async/await code -- the base module returns callback-based objects,mysql2/promisewill not work as expectedawait - Not releasing connections acquired with -- connection leak exhausts the pool; always release in a
pool.getConnection()blockfinally
Medium Priority Issues:
- Using instead of
query()for parameterized queries -- misses prepared statement caching and binary protocol efficiencyexecute() - Missing pool event handler -- unhandled connection errors crash the Node.js process
error - Setting too high -- each MySQL connection uses ~10 MB of server memory; 10-20 is usually sufficient
connectionLimit - Not setting -- idle connections get dropped by firewalls/load balancers causing
enableKeepAlive: trueECONNRESET
Common Mistakes:
- Expecting to wait for active queries -- it immediately destroys all connections; drain queries first
pool.end() - Using to check if an UPDATE affected rows -- use
rows.lengthfromresult.affectedRowsinsteadResultSetHeader - Assuming is always the auto-increment value -- for
insertId,INSERT ... ON DUPLICATE KEY UPDATEisinsertIdif the existing row was updated, not inserted0 - Calling after
connection.release()-- destroy removes the connection from the pool entirely; release returns itconnection.destroy() - Treating and
nullas interchangeable in parameter arrays -- mysql2 convertsundefinedto SQLnullbutNULLcauses a protocol errorundefined
Gotchas & Edge Cases:
- and
DECIMALcolumns are returned as strings by default to avoid JavaScript floating-point precision loss -- parse explicitly if you need numbersBIGINT - columns return JavaScript
DATEobjects, butDateprecision beyond milliseconds is truncated -- MySQL supports microsecond precision, JavaScriptDATETIMEdoes notDate - with named placeholders requires
execute()on the pool/connection config -- the default is unnamednamedPlaceholders: trueonly? - is a security risk -- it enables SQL injection via
multipleStatements: truein user input if combined with;; only enable when needed and never with user-provided SQLquery() - Pool throws immediately when all connections are in use instead of queuing -- the default
waitForConnections: falseis almost always what you wanttrue - indicates server warnings -- check it after DDL operations (the deprecated
ResultSetHeader.warningStatustype had a separateOkPacketfield;warningCounthas always usedResultSetHeader)warningStatus
</red_flags>
<critical_reminders>
高优先级问题:
- 将用户输入插入SQL字符串(SELECT * FROM users WHERE id = ${id}`
\execute())——存在SQL注入漏洞,始终使用?`占位符和 - 使用或
pool.execute()处理事务——每次调用可能使用不同连接,破坏事务隔离;请使用pool.query()pool.getConnection() - 异步代码从而非
mysql2导入——基础模块返回基于回调的对象,mysql2/promise无法按预期工作await - 未释放通过获取的连接——连接泄漏会耗尽池;始终在
pool.getConnection()块中释放finally
中优先级问题:
- 使用而非
query()执行参数化查询——错过预编译语句缓存和二进制协议效率execute() - 未处理池的事件——未处理的连接错误会导致Node.js进程崩溃
error - 设置过高——每个MySQL连接占用约10MB服务器内存;通常10-20足够
connectionLimit - 未设置——空闲连接会被防火墙/负载均衡器断开,导致
enableKeepAlive: true错误ECONNRESET
常见错误:
- 期望等待活跃查询完成——它会立即销毁所有连接;先完成查询再调用
pool.end() - 使用检查UPDATE是否影响行——请使用
rows.length的ResultSetHeaderresult.affectedRows - 假设始终是自增的值——对于
insertId,如果更新现有行而非插入新行,INSERT ... ON DUPLICATE KEY UPDATE为insertId0 - 在后调用
connection.destroy()——destroy会将连接从池中完全移除;release会将连接返回池connection.release() - 在参数数组中把和
null视为可互换——mysql2将undefined转换为SQLnull,但NULL会导致协议错误undefined
陷阱与边缘情况:
- 默认情况下和
DECIMAL列返回字符串,避免JavaScript浮点数精度丢失——如果需要数字请显式解析BIGINT - 列返回JavaScript
DATE对象,但Date的毫秒级以上精度会被截断——MySQL支持微秒精度,JavaScriptDATETIME不支持Date - 使用命名占位符的需要在池/连接配置中设置
execute()——默认仅支持匿名namedPlaceholders: true? - 存在安全风险——如果结合
multipleStatements: true使用,用户输入中的query()会导致SQL注入;仅在必要时启用,且绝不要与用户提供的SQL一起使用; - 池的会在所有连接被占用时立即抛出错误而非排队——默认的
waitForConnections: false几乎总是正确选择true - 表示服务器警告——DDL操作后请检查它(已弃用的
ResultSetHeader.warningStatus类型有单独的OkPacket字段;warningCount始终使用ResultSetHeader)warningStatus
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
重要提醒
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,, named constants)import type
(You MUST use with placeholders for ALL queries containing user input -- NEVER interpolate values into SQL strings with template literals or string concatenation)
execute()?(You MUST use for transactions and release the connection in a block -- pool convenience methods () use a different connection per call and cannot maintain transaction state)
pool.getConnection()finallypool.execute()(You MUST always import from for async/await code -- the base module returns callback-based objects that do not support )
mysql2/promisemysql2await(You MUST handle the pool event -- unhandled connection errors crash the Node.js process)
errorFailure to follow these rules will cause SQL injection vulnerabilities, transaction corruption, connection pool exhaustion, and application crashes.
</critical_reminders>
所有代码必须遵循CLAUDE.md中的项目约定(短横线命名、命名导出、导入顺序、、命名常量)import type
(所有包含用户输入的查询必须使用和占位符——绝不要用模板字符串或字符串拼接将值插入SQL字符串)
execute()?(事务必须使用获取连接,并在块中释放连接——池便捷方法()每次调用使用不同连接,无法维护事务状态)
pool.getConnection()finallypool.execute()(异步代码必须始终从导入——基础模块返回基于回调的对象,不支持)
mysql2/promisemysql2await(必须处理池的事件——未处理的连接错误会导致Node.js进程崩溃)
error不遵守这些规则会导致SQL注入漏洞、事务损坏、连接池耗尽和应用崩溃。
</critical_reminders>