Menu

Sentencias preparadas en SQLite: prepare, bind, step

Qué son las sentencias preparadas en SQLite, para qué sirven y cómo funciona el ciclo prepare/bind/step/finalize que usan todos los drivers por debajo.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Qué es realmente una sentencia preparada

Cuando le pasas un string SQL a SQLite, hay todo un trabajo previo antes de que se mueva una sola fila: tokenizar la cadena, parsearla, verificar que las tablas y columnas existan, planear cómo ejecutarla y compilar ese plan a bytecode para la máquina virtual de SQLite. Recién entonces la consulta corre de verdad.

Una sentencia preparada (o prepared statement) es lo que obtienes cuando te detienes en el paso "compilado a bytecode" y conservas ese resultado. El programa compilado tiene huecos — placeholders — donde después se rellenan los valores reales. Puedes ejecutar ese mismo programa compilado muchas veces con valores distintos, y hacerlo de forma segura incluso cuando los valores vienen de una entrada no confiable.

Piénsalo así: es la diferencia entre darle a alguien una receta para que la lea en voz alta cada vez que cocina, y enseñarle la receta una sola vez para luego limitarte a nombrarle los ingredientes el día que toca.

El ciclo de vida: prepare, bind, step, finalize

Cualquier driver de SQLite, en cualquier lenguaje, termina envolviendo las mismas cuatro llamadas de la API C. Conviene saberse los nombres aunque nunca escribas C, porque los mensajes de error y la documentación usan este vocabulario:

  1. sqlite3_prepare_v2 — compila un string SQL en un handle de sentencia.
  2. sqlite3_bind_* — rellena los valores de los placeholders (una función por tipo).
  3. sqlite3_step — ejecuta el programa. Para un SELECT, lo llamas repetidamente para recorrer las filas. Para INSERT/UPDATE/DELETE, una sola llamada hace todo el trabajo.
  4. sqlite3_finalize — libera el programa compilado cuando ya no lo necesitas.

Entre pasos, sqlite3_reset rebobina una sentencia ya ejecutada para que puedas volver a hacer bind y reejecutarla sin tener que prepararla otra vez.

Placeholders en el SQL

Dentro del string SQL, marcas cada lugar donde va un valor con un placeholder, en vez de incrustar el valor directamente. SQLite admite varias formas:

-- Anónimo, posicional:
INSERT INTO users (name, email) VALUES (?, ?);

-- Numerado:
INSERT INTO users (name, email) VALUES (?1, ?2);

-- Con nombre:
INSERT INTO users (name, email) VALUES (:name, :email);
INSERT INTO users (name, email) VALUES (@name, @email);
INSERT INTO users (name, email) VALUES ($name, $email);

? es lo más habitual en código a nivel de driver. Los placeholders con nombre (:name) se leen mejor cuando tienes varios parámetros o cuando el mismo valor aparece más de una vez. Elige un estilo para cada proyecto y mantenlo.

Lo que no debes hacer es armar la consulta concatenando strings:

-- NO HAGAS ESTO:
"INSERT INTO users (name) VALUES ('" + user_input + "')"

Ese es el camino directo a la inyección SQL, y además tira por la borda la reutilización de bytecode de la que vas a leer a continuación.

Un ejemplo práctico en SQL

Para ver cómo funciona el ciclo prepare/bind/step sin recurrir a un lenguaje anfitrión, aquí tienes el equivalente usando solo las funciones de SQL que SQLite trae de fábrica. Vamos a crear una tabla e insertar una fila usando un marcador de parámetro relleno con un literal:

En una aplicación real no incrustarías los valores directamente en el SQL. Lo que harías es preparar el INSERT una sola vez con marcadores ?, ?, y luego, por cada usuario, hacer bind del par nombre/email y llamar a step. El bytecode compilado es idéntico en cada llamada; lo único que cambia son los valores enlazados.

Reutilizar una sentencia preparada (la clave del rendimiento)

Este es el patrón que tu driver te deja escribir. Es pseudocódigo —cada lenguaje lo escribe a su manera—, pero la estructura es siempre la misma:

-- preparado una vez:
INSERT INTO users (name, email) VALUES (?, ?);

-- luego, en un bucle:
--   bind(1, name)
--   bind(2, email)
--   step()
--   reset()

Preparar la consulta hace que SQL se analice y compile una sola vez. En cada iteración solo se ejecuta el bytecode y se copian los valores a sus huecos. Para inserciones masivas (imagina importar 100.000 filas), esto es muchísimo más rápido que ejecutar 100.000 sentencias parseadas por separado: hablamos de un orden de magnitud, sobre todo si lo envuelves todo en una única transacción.

Un error muy típico: hacer un bucle llamando a prepare dentro del loop. Así tiras a la basura toda la ventaja. La regla es clara: prepare fuera del bucle, y bind + step dentro.

Por qué es la forma segura de hacerlo

Los parámetros enlazados con bind no son strings que se sustituyen dentro del SQL. Son valores que llegan al programa de bytecode a través de huecos tipados: huecos para enteros, para texto, para blobs. SQLite nunca los vuelve a interpretar como SQL, así que ningún valor puede alterar la estructura de la consulta.

Compara:

-- Vulnerable. Si user_input es:  '); DROP TABLE users;--
-- la consulta se vuelve destructiva.
"SELECT * FROM users WHERE name = '" + user_input + "'"

-- Seguro. user_input se vincula como un valor TEXT y solo se
-- compara como cadena, sin importar lo que contenga.
SELECT * FROM users WHERE name = ?;

La segunda forma es segura aunque user_input valga '); DROP TABLE users;--. SQLite buscará obedientemente un usuario cuyo nombre sea exactamente esa cadena (rarísima), no encontrará ninguno y devolverá cero filas. Nada en la estructura de la consulta puede cambiar según el valor.

Más adelante dedicaremos un documento entero a la inyección SQL, pero quédate con esta idea: las sentencias preparadas no son una defensa frente a SQL injection — son la defensa.

Sentencias que devuelven filas

En el caso de un SELECT, step devuelve las filas de una en una. Lo habitual es que el driver itere en bucle hasta que step indique "terminado":

En el código de tu aplicación, el driver se encargaría de hacer prepare sobre ese SELECT poniendo un ? en lugar de 2.00, vincularía (bind) el valor del umbral y llamaría a step dentro de un bucle, leyendo una fila por cada llamada. Cuando ya no quedan más filas, step avisa de que terminó, y el driver puede hacer reset sobre la sentencia (para reutilizarla con otro umbral) o finalize para liberarla.

No te olvides del finalize

Una sentencia preparada es una pequeña reserva de memoria dentro de SQLite. Si las dejas sueltas, no solo consumen memoria: lo más grave es que mantienen un bloqueo interno sobre la base de datos que puede dejar parados a otros procesos que quieran escribir. Todos los drivers ofrecen alguna forma de limpiar automáticamente —context managers en Python, bloques using en C#, RAII en C++— y conviene usarlos:

  • El módulo sqlite3 de Python hace el finalize cuando el cursor pasa por el garbage collector, pero llamar explícitamente a cursor.close() es más limpio.
  • better-sqlite3 (Node) libera la sentencia cuando el Statement es recolectado por el GC; mantener sentencias preparadas vivas durante mucho tiempo no es problema.
  • En C puro, eres tú quien llama a sqlite3_finalize. Olvidarse es un bug real.

La regla de oro: si tú lo preparaste, alguien tiene que hacerle finalize.

Cuándo puede que no la necesites tú directamente

Rara vez vas a llamar a sqlite3_prepare_v2 por tu cuenta. Los drivers de alto nivel convierten connection.execute("SELECT ... WHERE id = ?", (42,)) en prepare/bind/step/finalize por ti. ¿Por qué entonces conviene entender el ciclo de vida?

  • Reconocerás qué está pasando cuando aparezcan errores tipo "statement is busy" o "cannot operate on a finalized statement".
  • Sabrás que conviene cachear sentencias preparadas de larga vida cuando estés haciendo inserts dentro de un bucle apretado.
  • Escribirás consultas parametrizadas por instinto, aunque concatenar strings parezca lo más rápido.

Los ORMs y query builders llevan esto un paso más allá. Construyen el SQL, gestionan las sentencias preparadas y te devuelven resultados ya tipados. Pero por dentro siguen siendo las mismas cuatro llamadas.

Lo que viene: bind de parámetros

Hasta ahora hemos hablado de los placeholders en abstracto. En la siguiente parte vamos a ver el bind con más detalle: parámetros posicionales frente a nombrados, manejo de tipos, NULL y los pequeños detalles que aparecen cuando empiezas a pasar datos reales de tu aplicación a las consultas.

Preguntas frecuentes

¿Qué es una sentencia preparada en SQLite?

Es una consulta SQL que ya se ha parseado y compilado a un programa de bytecode reutilizable, pero con placeholders (? o :nombre) en los sitios donde irán los valores. Esos valores los pasas aparte en el momento de ejecutarla. SQLite expone todo esto a través de sqlite3_prepare_v2, sqlite3_bind_*, sqlite3_step y sqlite3_finalize.

¿Por qué conviene usar sentencias preparadas en SQLite?

Por dos motivos: seguridad y rendimiento. Los parámetros que se enlazan (bind) nunca se confunden con sintaxis SQL, así que la SQL injection deja de ser un problema. Y si vas a ejecutar la misma consulta muchas veces —por ejemplo, insertar 10.000 filas— preparar una sola vez y rebindear evita pasar por el parser en cada iteración, lo cual se nota.

¿En qué se diferencia una sentencia preparada de una consulta normal?

Una llamada a sqlite3_exec parsea y ejecuta el SQL de una sola tacada, con los valores incrustados como texto. Una sentencia preparada separa la compilación de la ejecución: haces prepare del SQL una vez, bind de los valores tipados en los placeholders, step para recorrer los resultados y reset si quieres volver a ejecutarla. De hecho, todos los drivers de alto nivel (el módulo sqlite3 de Python, better-sqlite3, etc.) usan sentencias preparadas por debajo.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR