Conectar Python con MySQL

Python es uno de los lenguajes más utilizados para el análisis de datos, desarrollo web y automatización, y MySQL es una de las bases de datos más populares del mundo. Conectar ambas tecnologías te permite construir aplicaciones potentes que interactúan con datos almacenados de forma estructurada. En este artículo aprenderás a establecer la conexión usando el conector oficial mysql-connector-python, gestionar pools de conexiones y manejar errores de forma apropiada.

Cuando una aplicación Python necesita persistir información o recuperar datos almacenados, la comunicación con MySQL se realiza a través de un conector que actúa como puente entre el código y el servidor de base de datos. Este conector traduce las instrucciones de Python en paquetes que MySQL entiende, siguiendo el protocolo cliente-servidor propio de MySQL. Entender cómo funciona esta comunicación es fundamental para diseñar aplicaciones que gestionen los recursos de forma eficiente, eviten fugas de conexiones y manejen los errores de red de manera elegante.

Requisitos previos

Antes de escribir la primera línea de código, necesitas asegurarte de que tu entorno cumple con algunos requisitos básicos. Necesitas tener Python 3.7 o superior instalado en tu sistema. Puedes verificar tu versión ejecutando python --version o python3 --version en la terminal. También necesitas un servidor MySQL en ejecución con un usuario que tenga permisos para crear bases de datos y tablas.

Si todavía no tienes MySQL instalado, revisa el artículo de instalación de MySQL donde cubrimos el proceso para Windows, macOS y Linux. Para verificar que tu servidor está activo, puedes intentar conectar al servidor desde la terminal con el cliente de línea de comandos. Si la conexión funciona desde la terminal, también funcionará desde Python, ya que ambos usan el mismo protocolo de red.

Asegúrate también de tener una base de datos creada. Si no la tienes, puedes crear una base de datos rápidamente con CREATE DATABASE tienda CHARACTER SET utf8mb4;. Trabajar con utf8mb4 desde el inicio te evitará problemas con caracteres especiales y emojis en el futuro.

Instalación del conector

El primer paso práctico es instalar la librería que permite a Python comunicarse con MySQL. Instala el conector oficial usando pip:

# pip install mysql-connector-python

Este paquete es mantenido por Oracle, el mismo equipo detrás de MySQL, lo que garantiza compatibilidad y actualizaciones constantes. Existe una alternativa popular llamada PyMySQL que es una implementación pura de Python, pero el conector oficial ofrece mejor rendimiento gracias a su extensión en C.

La extensión en C es relevante cuando tu aplicación realiza miles de consultas por segundo, porque la serialización y deserialización de los paquetes de red es significativamente más rápida en C que en Python puro. Para proyectos pequeños la diferencia es negligible, pero en aplicaciones de producción con tráfico elevado puede suponer una mejora notable en tiempos de respuesta.

Si encuentras problemas durante la instalación, una causa frecuente es no tener las dependencias de compilación necesarias para la extensión C. En ese caso, puedes forzar la instalación de la versión pura con pip install mysql-connector-python --no-binary :all:, o bien instalar las dependencias del sistema (python3-dev, gcc) y volver a intentarlo.

Conexión básica

A continuación se muestra un ejemplo completo que establece una conexión, ejecuta una consulta de prueba y cierra la conexión. Este patrón es el más sencillo y es perfecto para entender el flujo fundamental antes de pasar a técnicas más avanzadas como los pools de conexiones:

import mysql.connector
 
def conectar():
    conexion = mysql.connector.connect(
        host='localhost',
        user='root',
        password='tu_contraseña',
        database='tienda'
    )
 
    cursor = conexion.cursor()
    cursor.execute('SELECT 1 + 1 AS resultado')
    fila = cursor.fetchone()
    print(f'Conexión exitosa. Resultado: {fila[0]}')
 
    cursor.close()
    conexion.close()
 
conectar()

Salida esperada:

Conexión exitosa. Resultado: 2

El flujo sigue siempre el mismo orden: crear la conexión, obtener un cursor, ejecutar la consulta, leer los resultados, cerrar el cursor y cerrar la conexión. Respetar este orden es importante porque las conexiones a MySQL son recursos finitos del servidor. Cada conexión abierta consume memoria y cuenta contra el límite de max_connections configurado en el servidor. Si tu aplicación abre conexiones sin cerrarlas, eventualmente el servidor dejará de aceptar nuevas conexiones y todos los clientes quedarán bloqueados.

Parámetros de configuración

La función mysql.connector.connect() crea una conexión al servidor MySQL. Acepta múltiples parámetros de configuración que controlan el comportamiento de la conexión. Cuando la aplicación crece en complejidad, resulta más práctico agrupar estos parámetros en un diccionario de configuración en lugar de pasarlos como argumentos individuales:

import mysql.connector
 
config = {
    'host': 'localhost',         # Dirección del servidor
    'port': 3306,                # Puerto (3306 por defecto)
    'user': 'root',              # Usuario de MySQL
    'password': 'tu_contraseña', # Contraseña
    'database': 'tienda',        # Base de datos
    'charset': 'utf8mb4',        # Juego de caracteres
    'collation': 'utf8mb4_general_ci',
    'autocommit': False,         # Control manual de transacciones
    'connection_timeout': 10,    # Timeout en segundos
    'use_pure': False,           # Usar extensión C para mejor rendimiento
    'raise_on_warnings': True    # Lanzar excepción en warnings
}
 
conexion = mysql.connector.connect(**config)
print(f'Conectado a MySQL Server versión {conexion.get_server_info()}')
print(f'ID de conexión: {conexion.connection_id}')
conexion.close()

Salida esperada:

Conectado a MySQL Server versión 8.0.35
ID de conexión: 42

Pasar la configuración como diccionario es una práctica recomendada porque permite cargar los valores desde un archivo de configuración o variables de entorno sin modificar el código. De hecho, en aplicaciones de producción nunca deberías escribir las credenciales directamente en el código fuente. En su lugar, usa variables de entorno con os.getenv() o un archivo .env con la librería python-dotenv. Esto evita que las credenciales acaben en el repositorio de código y facilita el despliegue en diferentes entornos (desarrollo, pruebas, producción).

El parámetro autocommit merece una mención especial. Cuando está en False (el valor por defecto), cualquier operación de escritura como INSERT, UPDATE o DELETE requiere que llames explícitamente a conexion.commit() para que los cambios se persistan. Esto te da control total sobre las transacciones, pero también significa que olvidar el commit() hará que los cambios se pierdan silenciosamente al cerrar la conexión.

Usar el context manager (with)

Python ofrece el patrón with para gestionar recursos que necesitan ser liberados al terminar. Este patrón es especialmente valioso con las conexiones a bases de datos porque garantiza que la conexión se cerrará incluso si ocurre una excepción durante la ejecución de las consultas. Aunque mysql.connector.connect() no implementa el protocolo de context manager por defecto, puedes crear uno fácilmente con el decorador contextmanager:

import mysql.connector
from contextlib import contextmanager
 
@contextmanager
def obtener_conexion():
    conexion = mysql.connector.connect(
        host='localhost',
        user='root',
        password='tu_contraseña',
        database='tienda'
    )
    try:
        yield conexion
    finally:
        conexion.close()
 
# Uso con context manager
with obtener_conexion() as conexion:
    cursor = conexion.cursor()
    cursor.execute('SELECT DATABASE()')
    resultado = cursor.fetchone()
    print(f'Base de datos actual: {resultado[0]}')
    cursor.close()
# La conexión se cierra automáticamente al salir del bloque with

Salida esperada:

Base de datos actual: tienda

La ventaja clave del context manager es la seguridad ante errores. Si dentro del bloque with se lanza una excepción, el bloque finally se ejecuta igualmente y la conexión se cierra. Sin este patrón, tendrías que envolver todo el código en un try...finally manualmente cada vez que abres una conexión. El context manager centraliza esa lógica de limpieza en un solo lugar y hace que el código sea más limpio y menos propenso a fugas de recursos.

Pool de conexiones

Para aplicaciones que necesitan manejar múltiples solicitudes concurrentes, como un servidor web, crear y cerrar conexiones constantemente es ineficiente. Cada nueva conexión implica un handshake TCP, autenticación con el servidor MySQL y negociación de parámetros, un proceso que puede tardar varios milisegundos. En una aplicación web que recibe cientos de peticiones por segundo, esos milisegundos se acumulan rápidamente. Un pool de conexiones resuelve esto manteniendo varias conexiones abiertas y reutilizándolas:

import mysql.connector
from mysql.connector import pooling
 
# Crear el pool (se recomienda hacerlo una sola vez al iniciar la aplicación)
pool = pooling.MySQLConnectionPool(
    pool_name='tienda_pool',
    pool_size=5,                 # Número de conexiones en el pool
    pool_reset_session=True,     # Resetear sesión al devolver al pool
    host='localhost',
    user='root',
    password='tu_contraseña',
    database='tienda',
    charset='utf8mb4',
    autocommit=True
)
 
def consultar_productos():
    conexion = pool.get_connection()
    try:
        cursor = conexion.cursor(dictionary=True)
        cursor.execute('SELECT id, nombre, precio FROM productos LIMIT 5')
        productos = cursor.fetchall()
 
        print(f'Productos encontrados: {len(productos)}')
        for p in productos:
            print(f"  {p['nombre']}: ${p['precio']}")
 
        cursor.close()
    finally:
        conexion.close()  # Devuelve la conexión al pool
 
consultar_productos()

Salida esperada:

Productos encontrados: 5
  Laptop HP Pavilion: $12999.99
  Mouse Logitech MX Master: $1599.00
  Teclado Mecánico Corsair K70: $2299.50
  Monitor Samsung 27": $6499.00
  Auriculares Sony WH-1000XM5: $5999.99

Cuando llamas a conexion.close() en una conexión obtenida del pool, la conexión no se destruye realmente sino que se devuelve al pool para ser reutilizada. Si todas las conexiones del pool están en uso, get_connection() lanzará un error PoolError. El parámetro pool_reset_session=True es importante porque garantiza que cada vez que una conexión se reutiliza, su estado de sesión (variables de sesión, transacciones pendientes, etc.) se resetea, evitando que el estado de un usuario afecte a otro.

Elegir el tamaño del pool requiere algo de criterio. Un valor demasiado bajo hará que las peticiones tengan que esperar cuando todas las conexiones están ocupadas. Un valor demasiado alto consumirá recursos innecesarios en el servidor MySQL. Una buena regla general es configurar el pool con tantas conexiones como hilos de trabajo tenga tu servidor web, que suele estar entre 4 y 20 para la mayoría de aplicaciones.

Caso práctico con clase reutilizable

En una aplicación real, como un servidor Flask o FastAPI, la gestión de la base de datos debe estar centralizada en una clase o módulo reutilizable. Esto evita repetir la configuración de conexión en cada archivo y facilita el mantenimiento. Veamos cómo estructurar la conexión en una clase que combina el pool con context managers:

import mysql.connector
from mysql.connector import pooling
from contextlib import contextmanager
import os
 
class BaseDatos:
    _pool = None
 
    @classmethod
    def inicializar(cls):
        cls._pool = pooling.MySQLConnectionPool(
            pool_name='app_pool',
            pool_size=10,
            host=os.getenv('DB_HOST', 'localhost'),
            user=os.getenv('DB_USER', 'root'),
            password=os.getenv('DB_PASSWORD', ''),
            database=os.getenv('DB_NAME', 'tienda'),
            charset='utf8mb4',
            autocommit=True
        )
        print('Pool de conexiones inicializado')
 
    @classmethod
    @contextmanager
    def obtener_conexion(cls):
        conexion = cls._pool.get_connection()
        try:
            yield conexion
        finally:
            conexion.close()
 
    @classmethod
    @contextmanager
    def obtener_cursor(cls, dictionary=True):
        with cls.obtener_conexion() as conexion:
            cursor = conexion.cursor(dictionary=dictionary)
            try:
                yield cursor
            finally:
                cursor.close()
 
# Inicializar al arrancar la aplicación
BaseDatos.inicializar()
 
# Uso en cualquier parte del código
with BaseDatos.obtener_cursor() as cursor:
    cursor.execute('SELECT COUNT(*) AS total FROM productos')
    resultado = cursor.fetchone()
    print(f"Total de productos: {resultado['total']}")

Esta estructura tiene varias ventajas. Primero, la configuración del pool se define una sola vez en inicializar(). Segundo, los context managers obtener_conexion() y obtener_cursor() encapsulan toda la lógica de obtener y liberar recursos. Tercero, cualquier parte del código puede ejecutar consultas con solo dos líneas: el with y el execute(). Cuarto, si en el futuro necesitas cambiar la librería de conexión (por ejemplo, migrar a aiomysql para una aplicación asíncrona), solo tienes que modificar esta clase y el resto de la aplicación sigue funcionando.

Tipos de cursor

El conector de MySQL para Python ofrece varios tipos de cursor, cada uno con un comportamiento diferente a la hora de devolver los resultados. Elegir el cursor adecuado puede mejorar tanto la legibilidad del código como el rendimiento de tu aplicación.

El cursor por defecto devuelve cada fila como una tupla, donde accedes a los valores por posición numérica. El cursor dictionary=True devuelve diccionarios donde las claves son los nombres de las columnas, lo que hace el código mucho más legible. Existe también el cursor named_tuple=True que devuelve tuplas con nombre, combinando el rendimiento de las tuplas con la legibilidad de los diccionarios:

import mysql.connector
 
conexion = mysql.connector.connect(
    host='localhost', user='root',
    password='tu_contraseña', database='tienda'
)
 
# Cursor normal: tuplas
cursor_tupla = conexion.cursor()
cursor_tupla.execute('SELECT nombre, precio FROM productos LIMIT 2')
for fila in cursor_tupla:
    print(f'Tupla: {fila[0]} cuesta {fila[1]}')
cursor_tupla.close()
 
# Cursor diccionario: más legible
cursor_dict = conexion.cursor(dictionary=True)
cursor_dict.execute('SELECT nombre, precio FROM productos LIMIT 2')
for fila in cursor_dict:
    print(f"Dict: {fila['nombre']} cuesta {fila['precio']}")
cursor_dict.close()
 
# Cursor named_tuple: equilibrio rendimiento/legibilidad
cursor_nt = conexion.cursor(named_tuple=True)
cursor_nt.execute('SELECT nombre, precio FROM productos LIMIT 2')
for fila in cursor_nt:
    print(f'NT: {fila.nombre} cuesta {fila.precio}')
cursor_nt.close()
 
conexion.close()

Para scripts rápidos y prototipos, el cursor diccionario es la opción más cómoda. Para aplicaciones de producción donde el rendimiento es crítico y procesas millones de filas, el cursor por defecto (tuplas) o el de tuplas con nombre ofrecen mejor rendimiento porque evitan la sobrecarga de crear un diccionario por cada fila.

Manejo de errores

La conexión puede fallar por diversas razones: credenciales incorrectas, servidor no disponible, base de datos inexistente, tiempo de espera agotado o problemas de red. El conector de MySQL proporciona excepciones específicas para cada tipo de error, lo que te permite reaccionar de forma diferente según la causa:

import mysql.connector
from mysql.connector import Error, errorcode
 
def conectar_con_manejo():
    try:
        conexion = mysql.connector.connect(
            host='localhost',
            user='root',
            password='contraseña_incorrecta',
            database='base_inexistente'
        )
        conexion.close()
 
    except Error as error:
        if error.errno == errorcode.ER_ACCESS_DENIED_ERROR:
            print('Credenciales incorrectas. Verifica usuario y contraseña.')
        elif error.errno == errorcode.ER_BAD_DB_ERROR:
            print('La base de datos especificada no existe.')
        elif error.errno == errorcode.CR_CONN_HOST_ERROR:
            print('No se pudo conectar al servidor MySQL. ¿Está en ejecución?')
        elif error.errno == errorcode.CR_CONNECTION_ERROR:
            print('Error de conexión. Verifica host y puerto.')
        else:
            print(f'Error MySQL [{error.errno}]: {error.msg}')
 
conectar_con_manejo()

Manejar cada tipo de error de forma específica te permite dar mensajes claros al usuario o al equipo de operaciones. Un error de credenciales indica un problema de configuración, mientras que un error de host indica un problema de red o de infraestructura. Esta distinción es crucial para el diagnóstico rápido de incidencias en producción.

Reconexión automática

En aplicaciones de larga duración como servicios web o workers de procesamiento en segundo plano, la conexión con MySQL puede perderse por cortes de red temporales, reinicios del servidor o tiempos de inactividad prolongados. El conector ofrece un método reconnect() que intenta restablecer la conexión de forma automática:

from mysql.connector import Error
 
def ejecutar_consulta(conexion, sql, params=None):
    try:
        if not conexion.is_connected():
            print('Reconectando...')
            conexion.reconnect(attempts=3, delay=2)
 
        cursor = conexion.cursor(dictionary=True)
        cursor.execute(sql, params)
        resultados = cursor.fetchall()
        cursor.close()
        return resultados
 
    except Error as error:
        print(f'Error en la consulta: {error}')
        return []

El método is_connected() comprueba si la conexión sigue activa enviando un ping al servidor. Si no lo está, reconnect() intenta reconectar el número de veces especificado en attempts, esperando delay segundos entre cada intento. Este patrón es especialmente útil en combinación con el pool de conexiones, donde puedes verificar la salud de la conexión antes de cada uso.

Seguridad: consultas parametrizadas

Un aspecto fundamental al conectar Python con MySQL es la seguridad. Nunca debes construir consultas SQL concatenando cadenas de texto con datos proporcionados por el usuario, porque eso abre la puerta a inyecciones SQL. El conector ofrece consultas parametrizadas que escapan automáticamente los valores peligrosos:

import mysql.connector
 
conexion = mysql.connector.connect(
    host='localhost', user='root',
    password='tu_contraseña', database='tienda'
)
 
# PELIGROSO: nunca hagas esto
# cursor.execute(f"SELECT * FROM productos WHERE nombre = '{nombre_usuario}'")
 
# SEGURO: usa parámetros
cursor = conexion.cursor(dictionary=True)
nombre_buscar = "iPhone 15 Pro"
cursor.execute(
    'SELECT nombre, precio FROM productos WHERE nombre = %s',
    (nombre_buscar,)
)
 
producto = cursor.fetchone()
if producto:
    print(f"Encontrado: {producto['nombre']} - ${producto['precio']}")
else:
    print('Producto no encontrado')
 
cursor.close()
conexion.close()

Los marcadores %s actúan como placeholders que el conector reemplaza de forma segura, escapando cualquier carácter especial que pudiera alterar la estructura de la consulta SQL. Para profundizar en la seguridad de las consultas parametrizadas del lado del servidor MySQL, consulta el artículo sobre Prepared Statements.

Para gestionar los permisos del usuario de base de datos que usa tu aplicación Python, consulta los artículos sobre CREATE USER y GRANT. Python es también el lenguaje dominante en análisis de datos e inteligencia artificial, donde MySQL actúa como fuente de datos para entrenar modelos o alimentar pipelines de procesamiento. En Andorra Dev aplicamos esta combinación en proyectos de IA que consumen datos directamente desde MySQL.

Errores comunes

Incluso los desarrolladores experimentados se encuentran con ciertos errores recurrentes al trabajar con Python y MySQL. Conocerlos de antemano te ahorrará tiempo de depuración.

El error más frecuente es olvidar cerrar la conexión. Cuando ejecutas un script que abre conexiones repetidamente sin cerrarlas, eventualmente MySQL rechaza nuevas conexiones con el mensaje Too many connections. La solución es siempre usar context managers o bloques try...finally.

Otro error habitual es olvidar el commit() cuando autocommit está desactivado. Tu código ejecuta un INSERT o UPDATE, no lanza ninguna excepción, pero los datos no aparecen en la base de datos. Esto ocurre porque sin commit(), la transacción se descarta al cerrar la conexión.

También es común confundir los tipos de datos. Python usa None para representar valores nulos, pero si pasas None como parámetro en una consulta WHERE columna = %s, MySQL no encontrará resultados porque la comparación con NULL requiere IS NULL, no =. Para estos casos, necesitas construir la consulta dinámicamente según si el valor es None o no.

Ahora que sabes cómo establecer la conexión entre Python y MySQL, en el siguiente artículo aprenderás a ejecutar consultas SELECT para recuperar datos de forma eficiente.

Escrito por Eduardo Lázaro