Pregunta 1¿Cuál de las siguientes describe mejor el propósito de ejecutar ANALYZE?
Estadísticas y el optimizador — ANALYZE y selectividad
Recorre cómo la base de datos decide si usar un índice o leer cada fila, basándose en las estadísticas recogidas por ANALYZE. Verás cómo las columnas de alta selectividad y las de baja selectividad parten el plan, con EXPLAIN QUERY PLAN como testigo.
El dataset de este artículo — perf_sales (50 000 filas de ventas)
Este artículo trata sobre cómo la base de datos decide qué índice usar.
La pieza clave son las estadísticas.
Las estadísticas son hechos sobre la distribución de tus datos — "cuántas filas tiene la tabla", "cuántos valores distintos tiene una columna" — y las recoges con el comando ANALYZE.
A partir de esas estadísticas la base de datos calcula la selectividad (la estimación de qué fracción de filas sobrevive a una condición) y decide si usar un índice o leer toda la tabla.
Trabajarás con la tabla de ventas perf_sales (50 000 filas).
Tiene columnas con cardinalidades muy distintas: emp_id (30 comerciales distintos), region (4 valores: East / West / North / South), product (200 valores, P001 a P200) y status (en su mayoría paid, además de pending y refunded).
Verás cómo cambia el plan antes y después de ANALYZE.
ANALYZE y sqlite_stat1 — recoger estadísticas
Ejecuta ANALYZE y la base de datos recorre las tablas y los índices, recoge estadísticas y las guarda en una tabla interna llamada sqlite_stat1.
sqlite_stat1 registra hechos como "cuántas filas, de media, corresponden a un solo valor de este índice".
Ejecutar ANALYZE construye estadísticas basadas en tus datos reales, lo que ayuda al optimizador a elegir planes de ejecución precisos.
Usa ANALYZE por sí solo para cubrir todas las tablas, o ANALYZE table_name para enfocarte en una tabla específica.
-- Indexa dos columnas con distintas cardinalidades (status=3 valores / qty=20 valores)
DROP INDEX IF EXISTS ix_status;
DROP INDEX IF EXISTS ix_qty;
CREATE INDEX ix_status ON perf_sales(status);
CREATE INDEX ix_qty ON perf_sales(qty);
-- Recoge estadísticas y mira lo guardado (filas medias por valor)
ANALYZE;
SELECT tbl, idx, stat FROM sqlite_stat1
WHERE tbl = 'perf_sales' ORDER BY idx;
--> ix_status: una fila como 50000 16667 (unas 16667 filas por valor)
--> ix_qty: una fila como 50000 2500 (unas 2500 filas por valor)
La selectividad cambia el plan — columnas que compensan frente a las que no
La selectividad es la estimación de "qué fracción del total de filas sobrevive después de un filtro".
Cuanto más pequeña sea esa fracción, mayor la selectividad y más compensa el índice.
emp_id = 7 acota a unas 1 667 filas (alrededor del 3% de la tabla), así que tiene alta selectividad y vale la pena usar el índice.
Por otro lado, status = 'paid' deja unas 40 000 filas (alrededor del 80% de la tabla), así que la selectividad es baja y leer cada fila en orden supera a saltar por el índice fila a fila.
Una vez que las estadísticas están en su sitio, el optimizador (el planificador de consultas) lee la selectividad y compara lo caro que sería un plan con índice frente a un barrido completo.
Con índices compuestos o múltiples candidatos en juego, se apoya en las estadísticas para preferir "el índice que acota a menos filas".
El siguiente ejercicio muestra que incluso con la misma tabla y los mismos índices, el plan puede cambiar según la selectividad de la columna del WHERE y si las columnas que seleccionas están en el índice.
-- Indexa una columna de alta selectividad (amount: casi único) y una de baja (status: 3 valores),
-- luego, tras ANALYZE, compara los planes para consultas de la misma forma
DROP INDEX IF EXISTS ix_amount;
DROP INDEX IF EXISTS ix_status;
CREATE INDEX ix_amount ON perf_sales(amount);
CREATE INDEX ix_status ON perf_sales(status);
ANALYZE;
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE amount = 500000;
--> SEARCH perf_sales USING INDEX ix_amount (amount=?)
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE status = 'paid';
--> la baja selectividad abre la posibilidad de un plan inclinado a SCAN
En la consola de este curso, la baja selectividad puede aún mantener un INDEX SCAN
En la consola del navegador aquí (SQLite ejecutándose en memoria), el plan de status = 'paid' del paso ③ puede quedarse en SEARCH perf_sales USING INDEX ix_status (status=?). Aquí está la razón.
- Se ejecuta en memoria: en una base de datos en disco, el hueco entre "E/S aleatoria a través de un índice" y "un SCAN secuencial" es grande, y por eso la baja selectividad favorece a SCAN. En memoria, ambos son rápidos y el hueco se reduce.
- Las entradas del índice están ordenadas por valor: ix_status está ordenado por status, así que el bloque 'paid' se lee como un rango contiguo — no hay acceso aleatorio.
- El acceso a tabla basado en rowid es barato: SQLite puede obtener una fila de la tabla directamente vía su rowid interno, así que incluso las lecturas de tabla guiadas por índice se mantienen razonablemente baratas.
El comportamiento de un cambio nítido a `SCAN` cuando la selectividad cae es más fácil de observar en PostgreSQL u Oracle, RDBMS con almacenamiento real en disco y un optimizador completo basado en coste. Llévate de este ejercicio que hay un principio en juego: incluso con índices idénticos, la combinación de selectividad y columnas seleccionadas puede invertir la elección del optimizador. El umbral exacto entre SCAN y SEARCH se mueve con el motor, la presencia de disco y la precisión de las estadísticas.
Optimización basada en coste y variables de enlace — internos de otras bases de datos
La maquinaria que has visto — "estimar la selectividad a partir de estadísticas, luego elegir el plan más barato" — se llama optimizador basado en coste (CBO).
En bases de datos grandes como Oracle y SQL Server, esta maquinaria tiene más capas dentro.
Una es el parsing (análisis de la sentencia SQL): analizar una sentencia SQL por primera vez y construir un plan es un hard parse, mientras que reutilizar un plan analizado previamente es un soft parse.
Otra son las variables de enlace (variable de enlace: un mecanismo que no pone valores directamente en el SQL sino que deja un hueco como ? y pasa el valor en tiempo de ejecución), junto con el bind peeking — mirar el valor que se le da al optimizador y elegir un plan basado en cómo de sesgado está ese valor.
Estos son mecanismos internos de los motores de ejecución de las bases de datos grandes. Conocer los conceptos hace que las discusiones de tuning sean mucho más fáciles de seguir.
En la consola del navegador de este curso, puedes observar realmente cómo el plan reacciona a la selectividad (la sección anterior), pero el cambio entre hard y soft parses y el comportamiento del bind peeking no se pueden reproducir aquí como explica el callout de abajo, así que trabajarás los conceptos con un diagrama y un ejemplo de código de solo lectura.
Los hard / soft parses y el bind peeking son internos al estilo Oracle
El cambio entre hard y soft parses y el bind peeking (volver a elegir el plan basado en cómo de sesgado está el valor pasado) viven dentro de los motores de ejecución de bases de datos grandes como Oracle y SQL Server.
La consola del navegador de este curso no expone las vistas necesarias para observar estos estados internos (piensa en V$SQL de Oracle), así que no hay forma de demostrarlos en la consola.
Aquí obtendrás el concepto del diagrama y del ejemplo de código de solo lectura de abajo.
Por otro lado, la recogida de estadísticas con ANALYZE y cómo el plan reacciona a la selectividad, ambas cubiertas antes, son observables de verdad.
El corazón de la optimización basada en coste — "estadísticas -> selectividad -> elección del plan" — es algo que puedes confirmar de forma práctica en la consola de este curso, así que domínalo primero.
-- Una idea de cómo se ve esto en Oracle (solo lectura; no ejecutar en la consola de este curso)
-- Las variables de enlace pasan el valor después (:s se rellena en tiempo de ejecución)
-- SELECT * FROM perf_sales WHERE status = :s;
--
-- Cadenas SQL idénticas reutilizan el plan = soft parse
-- Cadenas incluso ligeramente distintas lo reconstruyen = hard parse
-- En Oracle inspeccionarías el parsing vía V$SQL.SQL_TEXT, pero aquí no
-- Lo que sí puedes observar de verdad en la consola de este curso:
-- una vez recogidas las estadísticas, la estimación de selectividad se basa en datos reales
ANALYZE;
EXPLAIN QUERY PLAN
SELECT COUNT(*) FROM perf_sales WHERE status = 'pending';
--> SEARCH perf_sales USING INDEX ... (status=?)
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2¿Cuál de las siguientes describe correctamente la selectividad?
Pregunta 3¿Cuál de las siguientes describe correctamente la diferencia entre un hard parse y un soft parse (un mecanismo interno en bases de datos grandes como Oracle)?