Prepared Statements

Un Prepared Statement (sentencia preparada) es una consulta SQL que se compila una vez y se ejecuta múltiples veces con diferentes valores. MySQL analiza y optimiza la sentencia en el paso de preparación, y después solo necesita sustituir los valores en cada ejecución. Esto ofrece dos ventajas principales: mejor rendimiento para consultas repetidas y protección contra inyección SQL.

Sintaxis

-- 1. Preparar la sentencia
PREPARE nombre_stmt FROM 'sentencia SQL con ? como marcadores';
 
-- 2. Asignar valores y ejecutar
SET @variable = valor;
EXECUTE nombre_stmt USING @variable1, @variable2;
 
-- 3. Liberar recursos cuando ya no se necesite
DEALLOCATE PREPARE nombre_stmt;

Los marcadores ? son placeholders que se sustituyen por valores reales al ejecutar. MySQL se encarga de escapar los valores automáticamente, lo que previene inyección SQL.

Ejemplo básico: SELECT

Preparar una consulta para buscar productos por categoría:

PREPARE buscar_productos FROM
    'SELECT nombre, precio FROM productos WHERE categoria_id = ?';
 
SET @cat = 6;
EXECUTE buscar_productos USING @cat;
nombreprecio
iPhone 15 Pro1299.99
Samsung Galaxy S24899.99
Google Pixel 8699.00
Xiaomi 14599.99

La sentencia se prepara una vez. Puedes ejecutarla con diferentes categorías sin volver a prepararla:

SET @cat = 7;
EXECUTE buscar_productos USING @cat;
nombreprecio
MacBook Air M31399.00
Lenovo ThinkPad X11549.00
ASUS ROG Zephyrus1899.99

Con múltiples parámetros

Los Prepared Statements aceptan múltiples marcadores:

PREPARE buscar_rango FROM
    'SELECT nombre, precio FROM productos WHERE precio BETWEEN ? AND ? ORDER BY precio';
 
SET @min = 100;
SET @max = 500;
EXECUTE buscar_rango USING @min, @max;
nombreprecio
Zapatillas running pro129.99
Zapatillas trail149.99
Estantería modular149.99
Mancuernas ajustables199.99
Robot de cocina249.99
Escritorio ajustable399.00

Los marcadores ? se sustituyen en orden: el primer ? recibe el valor de @min y el segundo el de @max.

INSERT con Prepared Statement

PREPARE insertar_etiqueta FROM
    'INSERT INTO etiquetas_producto (producto_id, etiqueta) VALUES (?, ?)';
 
SET @prod = 10;
SET @tag = 'accesorio esencial';
EXECUTE insertar_etiqueta USING @prod, @tag;
Query OK, 1 row affected (0.01 sec)

Puedes ejecutar el mismo INSERT preparado múltiples veces con valores diferentes:

SET @prod = 12;
SET @tag = 'deporte';
EXECUTE insertar_etiqueta USING @prod, @tag;
 
SET @prod = 14;
SET @tag = 'casual';
EXECUTE insertar_etiqueta USING @prod, @tag;

Las tres inserciones reutilizan la misma sentencia preparada, ahorrando el análisis y optimización en cada ejecución.

UPDATE y DELETE con Prepared Statement

-- UPDATE preparado
PREPARE actualizar_precio FROM
    'UPDATE productos SET precio = ? WHERE id = ?';
 
SET @nuevo_precio = 1249.99;
SET @prod_id = 1;
EXECUTE actualizar_precio USING @nuevo_precio, @prod_id;
 
-- DELETE preparado
PREPARE eliminar_etiqueta FROM
    'DELETE FROM etiquetas_producto WHERE producto_id = ? AND etiqueta = ?';
 
SET @prod = 10;
SET @tag = 'accesorio esencial';
EXECUTE eliminar_etiqueta USING @prod, @tag;

Protección contra inyección SQL

La principal ventaja de los Prepared Statements en aplicaciones es la prevención de inyección SQL. Cuando los valores se pasan como parámetros, MySQL los trata siempre como datos, nunca como parte de la sentencia SQL:

-- VULNERABLE (nunca hagas esto en una aplicación):
SET @input = "'; DROP TABLE productos; --";
-- Si construyes SQL concatenando: SELECT * FROM productos WHERE nombre = '' + @input
-- El atacante podría eliminar tu tabla
 
-- SEGURO con Prepared Statement:
PREPARE buscar FROM 'SELECT * FROM productos WHERE nombre = ?';
SET @input = "'; DROP TABLE productos; --";
EXECUTE buscar USING @input;
Empty set (0.00 sec)

Con el Prepared Statement, el valor malicioso se trata como un string literal que se busca en la columna nombre. No se interpreta como SQL y no causa ningún daño. La consulta simplemente no encuentra ningún producto con ese nombre.

Sentencias preparadas en aplicaciones

En la práctica, los Prepared Statements se usan más desde lenguajes de programación que directamente en el cliente MySQL. Los drivers y ORMs de los lenguajes proporcionan APIs más cómodas:

-- En MySQL puro (lo que estamos viendo)
PREPARE stmt FROM 'SELECT * FROM productos WHERE precio > ?';
SET @precio = 500;
EXECUTE stmt USING @precio;
 
-- En Python (mysql-connector)
-- cursor.execute("SELECT * FROM productos WHERE precio > %s", (500,))
 
-- En Node.js (mysql2)
-- connection.execute("SELECT * FROM productos WHERE precio > ?", [500])
 
-- En Java (JDBC)
-- PreparedStatement ps = conn.prepareStatement("SELECT * FROM productos WHERE precio > ?");
-- ps.setDouble(1, 500);

Todas estas formas envían la sentencia y los valores por separado al servidor MySQL, obteniendo la misma protección contra inyección SQL.

Limitaciones

Los Prepared Statements tienen algunas restricciones. Los marcadores ? solo pueden sustituir valores de datos, no nombres de tablas, columnas o palabras clave SQL:

-- Esto NO funciona:
PREPARE stmt FROM 'SELECT * FROM ?';             -- Error: no se puede parametrizar tablas
PREPARE stmt FROM 'SELECT * FROM productos ORDER BY ?';  -- Error: no ordena por el valor

Los Prepared Statements son locales a la sesión: se pierden cuando la conexión se cierra. No se pueden anidar: no puedes usar un Prepared Statement dentro de otro.

Liberar recursos

Cuando ya no necesites un Prepared Statement, libera los recursos asociados:

DEALLOCATE PREPARE buscar_productos;
DEALLOCATE PREPARE buscar_rango;
DEALLOCATE PREPARE insertar_etiqueta;
DEALLOCATE PREPARE actualizar_precio;
DEALLOCATE PREPARE eliminar_etiqueta;
DEALLOCATE PREPARE buscar;

Si no los liberas explícitamente, se liberan automáticamente al cerrar la sesión. Pero en aplicaciones de larga duración con muchas sentencias preparadas, liberarlas manualmente evita consumir memoria innecesariamente.

Con esto completamos la sección de manipulación de datos. Hemos cubierto todas las operaciones fundamentales: INSERT para crear datos, UPDATE para modificarlos, DELETE para eliminarlos, y las variantes especializadas como REPLACE, TRUNCATE y Prepared Statements. En la siguiente sección exploraremos la gestión de bases de datos y la definición de tablas.

Escrito por Eduardo Lázaro