mysql2
>=3.9.8postconditions28functions15last verified2026-06-24coverage score79%Postconditions: what we check
- connect · connection-failureerrorWhenCannot connect (wrong credentials, host unreachable, etc.)Throws
Error with code 'ECONNREFUSED', 'ER_ACCESS_DENIED_ERROR', etc.Required handlingCaller MUST catch connection errors. Common error codes: - ECONNREFUSED: MySQL server not running - ER_ACCESS_DENIED_ERROR: Wrong username/password - ETIMEDOUT: Network timeout Implement retry with exponential backoff for transient issues.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - query · syntax-errorerrorWhenSQL syntax errorThrows
Error with code 'ER_PARSE_ERROR' or similarRequired handlingCaller MUST validate SQL syntax before execution. DO NOT retry - indicates SQL syntax error. Check error.sqlMessage for details.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - query · constraint-violationerrorWhenUnique constraint, foreign key, or NOT NULL violationThrows
Error with code 'ER_DUP_ENTRY', 'ER_NO_REFERENCED_ROW', 'ER_BAD_NULL_ERROR'Required handlingCaller MUST handle constraint violations: - ER_DUP_ENTRY (1062): Duplicate key violation - ER_NO_REFERENCED_ROW_2 (1452): Foreign key constraint - ER_BAD_NULL_ERROR (1048): NOT NULL constraint Extract details from error.sqlMessage. DO NOT retry without fixing data.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - query · connection-errorerrorWhenConnection lost during query executionThrows
Error with code 'PROTOCOL_CONNECTION_LOST', 'ECONNRESET'Required handlingCaller MUST handle connection errors. Connection may be lost due to timeout or server restart. Implement retry with exponential backoff.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - query · table-not-founderrorWhenTable or view does not existThrows
Error with code 'ER_NO_SUCH_TABLE' (1146)Required handlingCaller MUST verify table exists before querying. DO NOT retry - indicates schema mismatch or missing migration.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - execute · syntax-errorerrorWhenSQL syntax error in prepared statementThrows
Error with code 'ER_PARSE_ERROR' or similarRequired handlingCaller MUST validate SQL syntax. Prepared statements prevent SQL injection but not syntax errors. DO NOT retry - fix SQL syntax.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - execute · constraint-violationerrorWhenUnique, foreign key, or NOT NULL constraint violationThrows
Error with code 'ER_DUP_ENTRY', 'ER_NO_REFERENCED_ROW_2', 'ER_BAD_NULL_ERROR'Required handlingCaller MUST handle constraint violations gracefully. DO NOT retry without changing violating data.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - execute · connection-errorerrorWhenConnection lost or timeoutThrows
Error with code 'PROTOCOL_CONNECTION_LOST', 'ETIMEDOUT'Required handlingCaller MUST handle connection errors. May be transient - implement retry logic.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - getConnection · pool-exhaustederrorWhenAll connections in pool are busy and timeout exceededThrows
Error with message 'Pool is closed' or timeout errorRequired handlingCaller MUST handle pool exhaustion. Root causes: 1. Connections not released (forgot connection.release()) 2. Pool size too small for load 3. Queries taking too long ALWAYS release connections in finally block.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - getConnection · connection-failureerrorWhenCannot establish connection from poolThrows
Error with code 'ECONNREFUSED', 'ETIMEDOUT'Required handlingCaller MUST handle connection failures. Implement retry with exponential backoff.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - beginTransaction · transaction-start-failureerrorWhenCannot start transaction (connection error, etc.)Throws
Error with connection-related codesRequired handlingCaller MUST catch transaction start errors. Network errors may be transient and retriable.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - commit · commit-failureerrorWhenTransaction commit fails (constraint violation, connection lost, etc.)Throws
Error with various codes depending on failure reasonRequired handlingCaller MUST catch commit errors. If commit fails, transaction is rolled back. Caller should handle rollback appropriately.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - rollback · rollback-failureerrorWhenRollback fails (connection lost, etc.)Throws
Error with connection-related codesRequired handlingCaller MUST catch rollback errors. Even rollback can fail due to connection issues. Log rollback failures for investigation.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - createConnection · createconnection-host-unreachableerrorWhenMySQL server is not running, host is wrong, or port is unreachableThrows
Promise rejects with Error: ECONNREFUSED (connect ECONNREFUSED 127.0.0.1:3306)Required handlingWrap await createConnection() in try-catch. Handle ECONNREFUSED at application startup and retry with backoff. In containerized deployments, add readiness checks before attempting connection.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - createConnection · createconnection-access-deniederrorWhenUsername, password, or database name is not authorized on the MySQL serverThrows
Promise rejects with Error: ER_ACCESS_DENIED_ERROR (MySQL errno 1045, SQLSTATE 28000)Required handlingWrap await createConnection() in try-catch. Check err.code === 'ER_ACCESS_DENIED_ERROR' and log a clear error message. Verify credentials in environment variables before deployment. Do not expose err.message to users.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - createConnection · createconnection-too-many-connectionserrorWhenMySQL server has reached its max_connections limitThrows
Promise rejects with Error: ER_CON_COUNT_ERROR (MySQL errno 1040, SQLSTATE 08004)Required handlingWrap await createConnection() in try-catch. Implement connection pooling (use createPool() instead of createConnection() per-request) to avoid exhausting the server's connection limit. Retry with exponential backoff when ER_CON_COUNT_ERROR.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilitysilentSources[3] - end · end-pool-connection-errorwarningWhenPool.end() called while queries are in-flight or connections fail to closeThrows
Promise rejects with PROTOCOL_CONNECTION_LOST or connection close errorRequired handlingWrap pool.end() or connection.end() in try-catch in shutdown handlers (SIGTERM, process.on('exit')). Log the error but do not re-throw — the goal is graceful shutdown, not failing on cleanup.costlowin prodimmediate exceptionusers seeservice unavailablevisibilitysilentSources[4] - end · end-called-on-closed-connectionwarningWhenpool.end() called while pool.getConnection() calls are still pendingThrows
Pending getConnection() calls receive Error: 'Pool is closed.'Required handlingDrain in-flight requests before calling pool.end(). In graceful shutdown, stop accepting new requests first, then wait for in-flight handlers to complete, then call pool.end(). Wrap pool.end() in try-catch.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - prepare · prepare-sql-syntax-errorerrorWhenSQL template passed to prepare() has a syntax errorThrows
Promise rejects with Error: ER_PARSE_ERROR (MySQL errno 1064)Required handlingWrap await prepare() in try-catch. Validate SQL templates in CI/CD rather than at runtime. Check err.code === 'ER_PARSE_ERROR' to distinguish syntax errors from connection failures.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - prepare · prepare-connection-errorerrorWhenConnection drops between createConnection() and prepare(), or is invalidated by changeUser()Throws
Promise rejects with PROTOCOL_CONNECTION_LOST or ECONNRESETRequired handlingWrap await prepare() in try-catch. Re-prepare statements after any connection reset or changeUser(). Do not cache PreparedStatementInfo objects across connection lifecycle events.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilitysilentSources[5] - PreparedStatementInfo.execute · prepared-statement-constraint-violationerrorWhenBound parameters violate a database constraint (unique, FK, NOT NULL)Throws
Promise rejects with ER_DUP_ENTRY (1062), ER_NO_REFERENCED_ROW_2 (1452), or ER_BAD_NULL_ERROR (1048)Required handlingWrap await stmt.execute() in try-catch. Check err.code to distinguish constraint violations (do not retry without fixing data) from connection errors (may retry).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - PreparedStatementInfo.execute · prepared-statement-stale-after-reconnecterrorWhenPreparedStatementInfo used after connection reset, changeUser(), or reconnectionThrows
Promise rejects with unknown statement ID error or PROTOCOL_CONNECTION_LOSTRequired handlingDo not cache PreparedStatementInfo objects across connection lifecycle events. Re-call connection.prepare() after any changeUser(), reset(), or reconnection. Wrap await stmt.execute() in try-catch.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilitysilent - changeUser · changeuser-access-deniederrorWhenNew user credentials are invalid or not authorized for the target databaseThrows
Promise rejects with ER_ACCESS_DENIED_ERROR (errno 1045); connection marked fatal (err.fatal = true)Required handlingWrap await connection.changeUser() in try-catch. On ER_ACCESS_DENIED_ERROR, destroy the connection and create a new one — the connection is permanently unusable after a fatal changeUser error. Do not return it to the pool.costhighin prodimmediate exceptionusers seeservice unavailablevisibilitysilentSources[8] - changeUser · changeuser-prepared-statements-invalidatedwarningWhenchangeUser() called on a connection that has cached PreparedStatementInfo objectsThrows
Does not throw on changeUser() itself, but subsequent stmt.execute() throws unknown statement ID errorRequired handlingAfter calling changeUser(), invalidate all cached PreparedStatementInfo objects for that connection. Re-call connection.prepare() before using any statement on the connection after changeUser().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilitysilentSources[8] - ping · ping-connection-deaderrorWhenConnection to MySQL is no longer alive (server closed due to wait_timeout or network failure)Throws
Promise rejects with PROTOCOL_CONNECTION_LOST or ECONNRESETRequired handlingWrap await connection.ping() in try-catch. In health check handlers, catch the error and return a 503/unhealthy status rather than letting it propagate as a 500. After a failed ping, destroy the connection and establish a new one.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - reset · reset-connection-deaderrorWhenThe connection is no longer alive (server closed due to wait_timeout, network failure, or the connection was destroyed) when reset() is called. The COM_RESET_CONNECTION packet cannot be sent on a dead socket.Throws
Promise rejects with PROTOCOL_CONNECTION_LOST, ECONNRESET, or ECONNREFUSED. Same error codes as ping() — the connection is permanently unusable after this error.Required handlingWrap await connection.reset() in try-catch. On error, destroy the connection and create a new one — do not attempt to reuse. In pool scenarios, call connection.destroy() rather than connection.release() when reset() fails. const connection = await pool.getConnection(); try { await connection.reset(); // Use connection... } catch (resetErr) { connection.destroy(); // NOT release() — this connection is broken throw resetErr; } finally { // Only release if no error (destroy() handles cleanup on error) }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilitysilent - reset · reset-invalidates-prepared-statementswarningWhenreset() is called on a connection that has cached PreparedStatementInfo objects (created via connection.prepare()). reset() sends COM_RESET_CONNECTION which causes the MySQL server to invalidate all prepared statements for this connection. The mysql2 client also clears its local _statements LRU cache on reset. Any PreparedStatementInfo objects held by the caller are now stale and will fail when execute() is called on them.Throws
reset() itself succeeds (resolves). Subsequent stmt.execute() on a stale PreparedStatementInfo rejects with unknown statement ID error or PROTOCOL_CONNECTION_LOST.Required handlingAfter calling reset(), invalidate and discard all PreparedStatementInfo objects associated with that connection. Re-call connection.prepare() before executing any statements on the connection. await connection.reset(); // All PreparedStatementInfo from before reset() are now invalid. // Re-prepare any statements you need. const stmt = await connection.prepare('SELECT * FROM users WHERE id = ?'); const [rows] = await stmt.execute([userId]); await stmt.close(); Note: connection.execute() (not prepare()) uses an auto-managed LRU cache that mysql2 clears automatically on reset() — only manually prepared statements via connection.prepare() require manual re-preparation.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilitysilent - PreparedStatementInfo.close · prepared-statement-close-missingwarningWhenconnection.prepare() is called but statement.close() is never called after the prepared statement is no longer needed. Each unclosed prepared statement occupies a slot on the MySQL server. MySQL's global max_prepared_stmt_count (default: 16,382) limits the total number of prepared statements across all connections. Long-running services that repeatedly call connection.prepare() without close() will eventually exhaust this limit.Throws
Once max_prepared_stmt_count is reached, new connection.prepare() calls reject with ER_MAX_PREPARED_STMT_COUNT_REACHED (MySQL errno 1461): "Can't create more than max_prepared_stmt_count statements (current value: 16382)"Required handlingAlways call statement.close() after finishing with a manually-prepared statement. Use try-finally to ensure close() is called even when execute() throws: const stmt = await connection.prepare('INSERT INTO events (type, payload) VALUES (?, ?)'); try { await stmt.execute([eventType, JSON.stringify(payload)]); } finally { await stmt.close(); // Always close — fire-and-forget, always resolves } Prefer connection.execute() (auto-cached LRU, auto-closes evicted statements) over connection.prepare() unless you need explicit statement lifecycle control. The auto-cache manages closing evicted statements transparently. For batch operations where a statement is reused many times, prepare() + close() is more efficient than execute() (which re-parses the statement cache on each call). In these cases, always close in finally.costmediumin proddelayed failureusers seeservice unavailablevisibilitysilent
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
- [2]dev.mysql.com/doc/mysql-errors/8.0Server Error Reference
- [7]sidorares.github.io/node-mysql2/docs/documentationPrepared Statements
- [11]dev.mysql.com/doc/refman/8.0Server System Variables
- [1]github.com/sidorares/node-mysql2sidorares/node-mysql2
- [3]github.com/sidorares/node-mysql2/blobsidorares/node-mysql2 · promise.js
- [4]github.com/sidorares/node-mysql2/blobsidorares/node-mysql2 · pool.js
- [5]github.com/sidorares/node-mysql2/blobsidorares/node-mysql2 · connection.js
- [6]github.com/sidorares/node-mysql2/blobsidorares/node-mysql2 · prepared_statement_info.js
- [8]github.com/sidorares/node-mysql2/blobsidorares/node-mysql2 · connection.js
- [9]github.com/sidorares/node-mysql2/blobsidorares/node-mysql2 · reset_connection.js
- [10]github.com/sidorares/node-mysql2/blobsidorares/node-mysql2 · close_statement.js
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
mysql2 - Nark profile Sources
Package Overview
- Package: mysql2
- npm: https://www.npmjs.com/package/mysql2
- GitHub: https://github.com/sidorares/node-mysql2
- Official Documentation: https://sidorares.github.io/node-mysql2/docs
- Type: MySQL driver for Node.js (promise-based and callback-based)
- Category: Database driver (throws exceptions on errors)
Error Handling Philosophy
mysql2 is a MySQL driver that throws exceptions for all error conditions. Unlike libraries that use error callbacks exclusively, mysql2's promise API requires explicit error handling with try-catch blocks. The driver passes through MySQL server errors, Node.js network errors, and internal protocol errors.
Connection Methods
1. createConnection()
Creates a single persistent connection to MySQL server.
Source: MySQL2 Quickstart
Usage:
import mysql from 'mysql2/promise';
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
database: 'test'
});
Error Codes:
ECONNREFUSED- MySQL server not running or unreachableER_ACCESS_DENIED_ERROR(1045) - Invalid credentialsETIMEDOUT- Connection timeoutPROTOCOL_CONNECTION_LOST- Connection dropped during use
2. createPool()
Creates a connection pool for better resource management.
Source: createPool Documentation
Configuration Options:
connectionLimit(default: 10) - Maximum number of connectionsmaxIdle(default: same as connectionLimit) - Maximum idle connectionsidleTimeout(default: 60000ms) - Time before idle connections are closedqueueLimit(default: 0 = unlimited) - Maximum queued connection requestswaitForConnections(default: true) - Queue requests when pool is fullenableKeepAlive(default: true) - Maintain connections with keepalive packetskeepAliveInitialDelay(default: 0ms) - Delay before first keepalive
Pool Methods:
pool.execute()- Execute query using any available connectionpool.query()- Execute query without prepared statementspool.getConnection()- Get dedicated connection for transactions
Source: Connection Pooling Best Practices
3. createPoolCluster()
Creates cluster of connection pools for read/write splitting or load balancing.
Error Management:
- Tracks
errorCountper node - Removes node when
errorCount > removeNodeErrorCount
Query Methods
query() vs execute()
Critical Security Difference:
Source: Query vs Execute Discussion
query() - String-based queries
const [rows] = await connection.query('SELECT * FROM users WHERE id = ?', [userId]);
Security Risk: If not using placeholders (?), vulnerable to SQL injection:
// DANGEROUS - DO NOT DO THIS
const [rows] = await connection.query(`SELECT * FROM users WHERE id = ${userId}`);
Use Case: Simple queries, or when hitting prepared statement syntax limitations.
execute() - Prepared statements
const [rows] = await connection.execute('SELECT * FROM users WHERE id = ?', [userId]);
Security Benefit: Parameters are serialized server-side, preventing SQL injection.
How it works:
- Statement sent to server once and parsed
- Server stores compiled statement
- Subsequent calls send only parameters
- Parameters treated as literal data, not SQL code
Source: SQL Injection Prevention Guide
Recommendation: Use execute() by default for all parameterized queries.
Error Codes Reference
Connection Errors
ECONNREFUSED
Type: Node.js network error Meaning: Connection refused by server Cause: MySQL server not running or wrong host/port Retry: Yes, with exponential backoff Source: ECONNREFUSED Issue
ER_ACCESS_DENIED_ERROR (1045)
Type: MySQL server error SQLSTATE: 28000 Message: Access denied for user 'username'@'host' (using password: YES/NO) Cause: Invalid username, password, or host permissions Retry: No, fix credentials Source: MySQL Error Reference
ETIMEDOUT
Type: Node.js network error Meaning: Connection attempt timed out Cause: Network issues, firewall, or slow server Retry: Yes, with exponential backoff
PROTOCOL_CONNECTION_LOST
Type: mysql2 internal error Meaning: Connection lost during query execution Cause: Server restart, timeout (wait_timeout exceeded), network interruption Retry: Yes, reconnect and retry query Source: Lost Connection Reference
Common Causes:
- Server
wait_timeoutsetting (default 28800 seconds) - Large result sets exceeding
net_read_timeout(default 30 seconds) - Server crash or restart
- Network interruption
Solution: Increase net_read_timeout for large queries or implement connection retry logic.
SQL Syntax Errors
ER_PARSE_ERROR (1064)
Type: MySQL server error SQLSTATE: 42000 Message: You have an error in your SQL syntax Cause: Invalid SQL statement Retry: No, fix SQL syntax Source: MySQL Error Reference
Schema Errors
ER_NO_SUCH_TABLE (1146)
Type: MySQL server error SQLSTATE: 42S02 Message: Table 'database.table_name' doesn't exist Cause: Table not created or wrong database selected Retry: No, create table or check schema Source: MySQL Error Reference
Constraint Violations
ER_DUP_ENTRY (1062)
Type: MySQL server error SQLSTATE: 23000 Message: Duplicate entry 'value' for key 'key_name' Cause: Unique or primary key constraint violation Retry: No, handle duplicate appropriately (ignore, update, or error) Source: ER_DUP_ENTRY Discussion
Error Object Properties:
{
code: 'ER_DUP_ENTRY',
errno: 1062,
sqlState: '23000',
sqlMessage: "Duplicate entry 'john@example.com' for key 'email'",
sql: 'INSERT INTO users (email) VALUES (?)'
}
Handling Pattern:
try {
await connection.execute('INSERT INTO users (email) VALUES (?)', [email]);
} catch (error) {
if (error.code === 'ER_DUP_ENTRY') {
// Handle duplicate: return existing user or show error
console.log('User already exists');
}
throw error;
}
ER_NO_REFERENCED_ROW_2 (1452)
Type: MySQL server error SQLSTATE: 23000 Message: Cannot add or update a child row: a foreign key constraint fails Cause: Foreign key references non-existent parent row Retry: No, ensure parent row exists Source: MySQL Error Reference
ER_BAD_NULL_ERROR (1048)
Type: MySQL server error SQLSTATE: 23000 Message: Column 'column_name' cannot be null Cause: Attempting to insert NULL into NOT NULL column Retry: No, provide non-null value Source: MySQL Error Reference
Transaction Handling
Best Practices
Source: MySQL Transactions Guide
Pattern for Transactions:
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
// Execute multiple queries
await connection.execute('INSERT INTO orders (user_id) VALUES (?)', [userId]);
await connection.execute('UPDATE inventory SET quantity = quantity - 1 WHERE id = ?', [itemId]);
await connection.commit();
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release(); // CRITICAL: Always release connection
}
Source: Transaction Best Practices
Transaction Rules
- Always end transactions: Every transaction MUST end with COMMIT or ROLLBACK
- Keep transactions short: Minimize lock contention
- Implement error handling: Catch errors and decide commit vs rollback
- Avoid user interaction: No prompts within transactions
- Release connections: Use
finallyblock to ensureconnection.release()
Source: autocommit and Transaction Behavior
Lock Management
- Both COMMIT and ROLLBACK release all InnoDB locks
- Autocommit is enabled by default (each statement is a transaction)
- Disable autocommit when using explicit transactions
Connection Pool Issues
Common Bugs
Source: Pool Connection Issues
1. Connection Leaks
Problem: Forgetting to call connection.release()
Symptom: "Too many connections" error after N requests
Solution: Always use finally block to release connections
Source: Pool Execute Won't Release
2. Multiple release() Calls
Problem: Calling connection.release() multiple times
Impact: Pool inconsistency, same connection assigned to multiple requests
Solution: Track release state, call release() exactly once
Source: Repeated release() Issue
3. Pool Exhaustion
Problem: All connections busy, queue timeout exceeded Error: "Pool is closed" or timeout error Root Causes:
- Connections not released (connection leaks)
- Pool size too small (
connectionLimit) - Queries taking too long
Source: Too Many Connections Issue
4. Stale Connections
Problem: Pool doesn't detect disconnected idle connections
Error: ECONNRESET on newly acquired connection
Solution: Configure enableKeepAlive: true and appropriate idleTimeout
Source: Idle Connection Issue
Promise API with Pools
Correct usage:
const pool = mysql.createPool(config).promise();
// Method 1: Direct pool execution (connection managed automatically)
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [id]);
// Method 2: Manual connection for transactions
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
// ... queries
await connection.commit();
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release(); // REQUIRED
}
Source: Promise Pool Discussion
Important: pool.execute() cannot be used for transactions because each call may use a different connection. Transactions require holding the same connection for the entire sequence: getConnection → beginTransaction → queries → commit/rollback → release.
Common Production Mistakes
1. SQL Injection via String Concatenation
// WRONG - Vulnerable to SQL injection
await pool.query(`SELECT * FROM users WHERE email = '${email}'`);
// CORRECT - Use placeholders
await pool.execute('SELECT * FROM users WHERE email = ?', [email]);
2. Forgetting to Release Connections
// WRONG - Connection leaked if error occurs
const connection = await pool.getConnection();
await connection.execute('SELECT * FROM users');
connection.release();
// CORRECT - Always release in finally
const connection = await pool.getConnection();
try {
await connection.execute('SELECT * FROM users');
} finally {
connection.release();
}
3. No Error Handling for Constraints
// WRONG - Unhandled ER_DUP_ENTRY crashes server
await connection.execute('INSERT INTO users (email) VALUES (?)', [email]);
// CORRECT - Handle duplicate gracefully
try {
await connection.execute('INSERT INTO users (email) VALUES (?)', [email]);
} catch (error) {
if (error.code === 'ER_DUP_ENTRY') {
return { error: 'Email already registered' };
}
throw error;
}
4. Using query() Instead of execute()
// LESS SECURE - Parameters serialized client-side
await pool.query('SELECT * FROM users WHERE id = ?', [id]);
// MORE SECURE - Parameters serialized server-side
await pool.execute('SELECT * FROM users WHERE id = ?', [id]);
Source: Query vs Execute Security
5. Pool Closed During Execution
// WRONG - pool.end() closes pool while queries running
pool.query('SELECT ...');
pool.end(); // Kills active queries
// CORRECT - Wait for queries to finish
await pool.query('SELECT ...');
await pool.end();
Source: Pool End Issue
Error Handling FAQs
Source: How to Handle Errors
Promise API Error Handling
Connection Errors:
try {
const connection = await mysql.createConnection(config);
} catch (err) {
console.log('Connection error:', err.code); // ECONNREFUSED, etc.
}
Query Errors:
try {
const [rows] = await connection.execute('SELECT * FROM users');
} catch (err) {
console.log('Query error:', err.code, err.sqlMessage);
}
Error Object Properties
{
code: 'ER_DUP_ENTRY', // Error code
errno: 1062, // MySQL error number
sqlState: '23000', // SQL standard error code
sqlMessage: 'Duplicate entry...', // MySQL error message
sql: 'INSERT INTO...' // Original SQL query
}
TypeScript Support
Source: TypeScript Examples
mysql2 provides TypeScript type definitions:
ConnectionOptions- Connection configurationPoolOptions- Pool configurationConnection- Connection interfacePool- Pool interfaceRowDataPacket- Query result typeResultSetHeader- INSERT/UPDATE result type
import mysql, { ConnectionOptions, PoolOptions } from 'mysql2/promise';
const config: PoolOptions = {
host: 'localhost',
user: 'root',
database: 'test',
connectionLimit: 10
};
const pool = mysql.createPool(config);
Performance Considerations
Connection Pooling vs Single Connection
Use createPool() for:
- Web applications with concurrent requests
- Applications with sporadic database access
- Better resource management
Use createConnection() for:
- Long-running processes with continuous DB access
- Background workers with dedicated DB operations
- Lower overhead for single-threaded apps
Prepared Statement Performance
Source: MySQL Prepared Statements
Benefits of execute():
- Statement parsed once, executed multiple times
- More efficient for repeated queries
- Prevents SQL injection
Overhead:
- Small overhead for single-execution queries
- Benefits increase with query complexity and repetition