MySQL Prepared Statements

Prepared statements let you write a SQL query once with placeholders and execute it repeatedly with different values, safely and efficiently.

What a Prepared Statement Does

A prepared statement is a query template sent to MySQL with placeholders (typically written as ?) standing in for values. MySQL parses and plans the query structure once, then you supply the actual values in a separate step to execute it. Because the values are bound as data rather than inserted into the SQL text, they can never change the query's structure — this is what makes prepared statements the standard defense against SQL injection.

Prepared Statements at the SQL Level

MySQL supports prepared statements directly in SQL using PREPARE, EXECUTE, and DEALLOCATE PREPARE, useful inside stored procedures or ad-hoc admin scripts.

Raw SQL prepared statement

PREPARE find_user FROM 'SELECT id, email FROM users WHERE id = ?';
SET @user_id = 42;
EXECUTE find_user USING @user_id;
DEALLOCATE PREPARE find_user;

Prepared Statements from Application Code

In practice, most prepared statements are issued through a driver or ORM, which handles PREPARE/EXECUTE behind a simple API. The placeholder syntax differs slightly by driver: mysql2 for Node.js uses ?, while PHP's PDO supports both ? and named placeholders like :id.

Node.js with mysql2 — positional placeholders

const [rows] = await connection.execute(
  'SELECT id, email FROM users WHERE id = ? AND status = ?',
  [userId, 'active']
);

PHP with PDO — named placeholders

$stmt = $pdo->prepare('SELECT id, email FROM users WHERE id = :id AND status = :status');
$stmt->execute(['id' => $userId, 'status' => 'active']);
$user = $stmt->fetch();

Reusing a Prepared Statement

One of the practical benefits of prepared statements, beyond security, is performance when the same query shape runs many times: MySQL can reuse the parsed execution plan instead of re-parsing identical SQL text on every call.

Executing the same prepared statement with different values

PREPARE insert_log FROM 'INSERT INTO audit_log (user_id, action) VALUES (?, ?)';

SET @uid = 7, @act = 'login';
EXECUTE insert_log USING @uid, @act;

SET @uid = 8, @act = 'logout';
EXECUTE insert_log USING @uid, @act;

DEALLOCATE PREPARE insert_log;
  • Placeholders (? or :name) mark where a value goes; MySQL treats bound values strictly as data, never as SQL syntax.
  • The query's structure is parsed once and can be executed multiple times with different bound values.
  • Most application code never calls PREPARE/EXECUTE directly — the database driver does it under the hood.
  • Always deallocate raw SQL prepared statements when done, or let the session end, to free server resources.
  • Prepared statements are the standard, reliable defense against SQL injection — safer than manual input escaping.
Note: Never build the placeholder-bound query string itself through concatenation — for example, don't concatenate a table or column name into the query using user input, since placeholders only work for values, not identifiers. Validate identifiers against an allowlist instead.
Note: A prepared statement protects the values you bind through it — it does nothing if you still concatenate other untrusted input into the same SQL string alongside the placeholders.

Exercise: MySQL Prepared Statements

What is the basic sequence when using a prepared statement?