El padrón de clientes de Aurora Peña tiene 18 mil renglones y vive en la trastienda de una distribuidora de abarrotes en Oaxaca. El reporte mensual de quién no ha pedido en noventa días tardaba 52 segundos. Una línea que Aurora escribió una sola vez lo dejó en 70 milisegundos, y esa línea no cambió un carácter de la consulta.
El mismo padrón carga un segundo problema, más viejo y más caro: el buscador del mostrador no encuentra a la señora Núñez cuando el muchacho teclea Nunez. Los dos son problemas de la base de datos, y ninguno de los dos está en el programa.
Una base de datos no es un lugar donde poner cosas. Es un juego de promesas sobre qué va a seguir siendo cierto después de un apagón, y un juego de tratos sobre qué va a ser rápido. Cada tema de abajo es una de esas dos cosas, y la mitad práctica del asunto es saber cuál se está pagando en este momento.
La línea que quitó 52 segundos
La consulta de Aurora pide los clientes cuya última compra es anterior a noventa días. Sin ayuda, la base tiene una sola manera de contestar: leer los 18 mil renglones y revisar la fecha de cada uno. Un índice es una segunda copia de la columna, mantenida en orden, y con uno la base salta directo a la fecha de corte y lee solamente los renglones que quedan de ese lado. La reparación entera fue CREATE INDEX ON clientes (ultima_compra).
Que esto no salga gratis, y que las bases no indexen todo por si acaso, tiene una razón: la copia hay que mantenerla cierta. Cada alta y cada actualización de esa columna ahora escribe dos veces, una en el renglón y otra en el índice. En una tabla que se lee todo el día y se escribe poco, el trato es excelente. En una que recibe una escritura por segundo y se lee una vez al mes está al revés, y el índice es un impuesto que se paga todos los días por un ahorro que se cobra una.
El comando que termina la discusión es EXPLAIN, que le pregunta a la base qué piensa hacer antes de que lo haga. Contesta con el plan: un recorrido secuencial de 18 mil renglones, o una lectura por índice de 340. Leer esa salida es la habilidad más transferible de este tema, y es para lo que sirve en el fondo SQL.
La colación, que es el problema que la edición en inglés no tiene
El apellido de la clienta se escribe Núñez y el muchacho del mostrador teclea Nunez, sin acento y con ene, porque va de prisa. Que la consulta la encuentre o no lo decide la colación, que es la regla con la que la base compara dos textos, y casi nadie la elige a propósito: llega puesta desde el día en que alguien creó la tabla.
En MySQL 8 la que viene de fábrica es utf8mb4_0900_ai_ci, y las dos siglas del final lo dicen todo: ai es insensible a acentos y ci es insensible a mayúsculas. Con utf8mb4_0900_ai_ci, WHERE nombre = 'Nunez' encuentra a Núñez, y JOSE encuentra a José. Eso es exactamente lo que quiere un buscador de mostrador donde nadie teclea acentos.
Y existe además la colación española, utf8mb4_es_0900_ai_ci, donde la ñ se ordena como la letra propia que es, entre la n y la o. Eso es lo que quiere el listado impreso del padrón, porque en español Núñez va después de Nuño y no revuelto entre las enes. Las dos son correctas y piden cosas distintas: ordenar el padrón y buscar en el padrón quieren colaciones distintas, y el error clásico es elegir una sola vez, al crear la tabla, sin enterarse de que había una decisión que tomar.
En PostgreSQL la comparación no ignora los acentos por su cuenta, y la salida es la extensión unaccent o una colación ICU declarada a propósito. En cualquiera de los dos motores el síntoma es el mismo y es traicionero: nadie reporta un error, sencillamente la clienta no aparece y el muchacho la da de alta otra vez. Al año hay tres señoras Núñez en el padrón y el reporte de los noventa días miente.
Tres hechos que producen casi todas las respuestas equivocadas
El primero devuelve la nada en silencio. NULL no es igual a NULL, así que IS NULL es la única prueba que funciona. NULL no quiere decir cero ni vacío. Quiere decir desconocido, y dos desconocidos no se pueden declarar iguales, de modo que WHERE ultima_compra = NULL no es un error y no empata con ningún renglón. Aurora tenía justo eso en una versión anterior, y el reporte informaba que todos los clientes estaban al corriente.
El segundo cuelga la máquina. Un join sin condición ON es cada fila contra cada fila. Cruzar 18 mil clientes con 600 rutas de reparto sin decir cómo se relacionan pide 10.8 millones de renglones, y la base lo va a hacer, porque es una petición legal. El síntoma no es un mensaje de error. Es una consulta que no ha regresado.
El tercero importa el día que algo falla a media escritura. Una transacción es todo o nada. Pasar un cliente de una ruta a otra son dos escrituras, y un apagón entre las dos deja al cliente sin ruta. Envueltas en BEGIN y COMMIT, o aterrizan las dos o no aterriza ninguna, y no hay un estado intermedio que alguien alcance a ver. Esa es la promesa a la que apunta de verdad la palabra base de datos, y es la razón de que el diseño de base de datos vaya antes que cualquier herramienta.
El orden
Cuatro peldaños, y cada uno es invisible si se empezó arriba de él
Casi todo el mundo empieza en el tercero, porque es el que trae nombre en un anuncio de empleo.
- 04La otra formaDocumentos y claves, cuando un renglón fijo es de verdad el recipiente equivocado.Sáltate los tres primeros y esto es una corazonada
- 03El motorCuál producto, cuál dialecto, y qué hace ante un apagón.Casi siempre se eligió antes de que llegaras
- 02La formaCuáles son las tablas, qué es único, qué no tiene permiso de faltar y cuántos apellidos trae un nombre.Sáltalo y ningún índice te salva
- 01El lenguajePreguntar directo, y leer el plan con el que la base contesta.Sáltalo y el mapeador es opaco
No dice cuánto tarda un peldaño. El segundo se decide en una tarde al principio y cuesta una migración cuando ya hay 18 mil renglones adentro.
El orden, y lo que cuesta saltárselo
El orden que sirve es el lenguaje, después la forma, después el motor. Casi nadie lo hace así, porque el motor es lo que nombra un anuncio de empleo y la forma no la nombra nada.
Tomado al revés la falla tiene firma. Alguien aprende un mapeador de objetos antes que SQL, arma una aplicación que funciona, y después se topa con una página lenta sobre la que no puede razonar, porque la capa que le escribe las consultas es ahora la capa que le esconde qué está lento. El mapeador no es el problema y los datos desde el código es una materia legítima. Solamente es ilegible para quien nunca vio lo que genera.
Hay una decisión de forma que en este mercado se equivoca sistemáticamente, y viene de copiar el esquema de un tutorial en inglés. Un nombre completo aquí trae dos apellidos, el paterno y el materno, y una tabla con las columnas first_name y last_name obliga al muchacho del mostrador a meter los dos apellidos en una sola casilla o a tirar uno. Después nadie puede ordenar por apellido paterno, que es como se ordena un padrón, ni cruzar contra el RFC, que se construye con los dos. La forma correcta se decide en una tarde al principio y cuesta una migración cuando ya hay 18 mil renglones adentro.
Dónde se atora
El atasco de este tema es elegir la base antes de conocer la forma de los datos, y suele decidirlo cuál leyó alguien más recientemente. La posición honesta es que NoSQL es una respuesta real a una pregunta real, y la pregunta es si los datos tienen forma fija y relaciones que importan. Los de Aurora sí: los clientes pertenecen a rutas, los pedidos pertenecen a los dos, y cada reporte que escribe es un cruce. Un almacén de documentos le habría hecho más rápida la primera semana y bastante peor el cuarto mes, porque cada reporte suyo es un cruce.
El segundo atasco es más chico. La gente trata la base como el departamento de alguien más hasta el día en que una página va lenta, y descubre que la reparación nunca estuvo en el código de la aplicación. Es de los pocos lugares de programación donde el arreglo más barato mide una línea de forma confiable y está en un archivo que nadie del equipo ha abierto.
Qué abrir en 2026, y lo que cuesta desde acá
SQLite es el lugar correcto para aprender y cada vez más el lugar correcto para producir. Es un solo archivo, no necesita servidor, está en el dominio público, y reemplazó la costumbre de inventarse un formato de archivo y escribirle un lector. Una distribuidora del tamaño de la de Aurora podría correr sobre SQLite de manera permanente, y esa frase pesa más de este lado: no hay servidor que rentar ni factura en dólares corriendo.
PostgreSQL es lo que está por omisión cuando un servidor sí se justifica, es gratis y de código abierto, y le tomó ese lugar a MySQL a lo largo de la última década por corrección y por hacer más trabajo adentro de la base. MySQL 8 sigue siendo lo que trae el hospedaje compartido barato que de verdad se contrata aquí, así que es el motor con el que la mayoría se topa primero, quiera o no. Los tres entienden EXPLAIN, los tres respetan las transacciones, y el SQL de este tema se mueve entre ellos con diferencias de dialecto y no con una reescritura. Cuál llega a un proyecto casi siempre se decidió antes de que alguien fuera contratado, que es por lo que los motores está planteado como está.
Ninguno de los tres cobra por la licencia. Lo que cobra es dónde vive la base: un hospedaje compartido con MySQL se paga en pesos y al año, y una base administrada en la nube se paga en dólares y al mes, más el IVA de servicios digitales del exterior. Para aprender no hace falta ninguno de los dos, porque SQLite corre en la misma máquina en la que se escribe, y esa es la respuesta a la mitad de las preguntas de precio en este tema. Poner la base donde el público la alcanza es asunto de la puesta en producción.
Lo que nada de esto contesta es qué pasa cuando una sola máquina ya no alcanza, que es donde las garantías de arriba se empiezan a negociar a propósito. La pregunta anterior a esa, la de qué cuesta una operación cuando hay más datos, vive en los fundamentos de programación, y la de por qué una página se siente lenta cuando la base contesta rápido vive en el desarrollo web.