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;| nombre | precio |
|---|---|
| iPhone 15 Pro | 1299.99 |
| Samsung Galaxy S24 | 899.99 |
| Google Pixel 8 | 699.00 |
| Xiaomi 14 | 599.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;| nombre | precio |
|---|---|
| MacBook Air M3 | 1399.00 |
| Lenovo ThinkPad X1 | 1549.00 |
| ASUS ROG Zephyrus | 1899.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;| nombre | precio |
|---|---|
| Zapatillas running pro | 129.99 |
| Zapatillas trail | 149.99 |
| Estantería modular | 149.99 |
| Mancuernas ajustables | 199.99 |
| Robot de cocina | 249.99 |
| Escritorio ajustable | 399.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 valorLos 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
