MySQLi (MySQL Improved) es la extensión nativa de PHP para conectar con MySQL y MariaDB. A diferencia de PDO, que es una capa de abstracción multi-base-de-datos, MySQLi está optimizada específicamente para MySQL y ofrece algunas características exclusivas como la ejecución de múltiples queries y el acceso a ciertos metadatos de MySQL que PDO no expone. Ofrece dos APIs: procedural (mysqli_connect()) y orientada a objetos (new mysqli()). Esta guía usa la OOP.
Conectar con new mysqli()
<?php
$mysqli = new mysqli('localhost', 'usuario', 'contraseña', 'mi_base_datos');
if ($mysqli->connect_error) {
throw new RuntimeException(
"Error de conexión ({$mysqli->connect_errno}): {$mysqli->connect_error}"
);
}
$mysqli->set_charset('utf8mb4');
Consultas preparadas con bind_param()
Las consultas preparadas son la forma correcta de trabajar con datos del usuario: evitan la inyección SQL separando el código de los datos.
<?php
// INSERT con consulta preparada
$stmt = $mysqli->prepare(
"INSERT INTO usuarios (nombre, email, edad) VALUES (?, ?, ?)"
);
// 's' = string, 'i' = int, 'd' = double/float, 'b' = blob
$nombre = 'Ana García';
$email = '[email protected]';
$edad = 28;
$stmt->bind_param('ssi', $nombre, $email, $edad);
$stmt->execute();
echo "ID insertado: " . $mysqli->insert_id . "n"; // ID del registro nuevo
$stmt->close();
// SELECT con consulta preparada
$stmt = $mysqli->prepare("SELECT id, nombre, email FROM usuarios WHERE edad > ?");
$stmt->bind_param('i', $minEdad);
$minEdad = 25;
$stmt->execute();
get_result() vs bind_result()
Hay dos formas de recoger los resultados de un SELECT preparado:
<?php
// Opción 1: get_result() más cómoda, requiere mysqlnd
$stmt = $mysqli->prepare("SELECT id, nombre, email FROM usuarios WHERE activo = ?");
$stmt->bind_param('i', $activo);
$activo = 1;
$stmt->execute();
$result = $stmt->get_result();
while ($fila = $result->fetch_assoc()) {
echo "{$fila['nombre']} ({$fila['email']})n";
}
// Opción 2: bind_result() compatible con todas las instalaciones
$stmt = $mysqli->prepare("SELECT id, nombre, email FROM usuarios WHERE activo = ?");
$stmt->bind_param('i', $activo);
$activo = 1;
$stmt->execute();
$stmt->bind_result($id, $nombre, $email);
while ($stmt->fetch()) {
echo "$nombre ($email)n";
}
$stmt->close();
Transacciones
<?php
$mysqli->begin_transaction();
try {
$stmtDescuento = $mysqli->prepare(
"UPDATE cuentas SET saldo = saldo - ? WHERE id = ?"
);
$stmtDeposito = $mysqli->prepare(
"UPDATE cuentas SET saldo = saldo + ? WHERE id = ?"
);
$importe = 500.00;
$cuentaOrigen = 1;
$cuentaDestino = 2;
$stmtDescuento->bind_param('di', $importe, $cuentaOrigen);
$stmtDescuento->execute();
$stmtDeposito->bind_param('di', $importe, $cuentaDestino);
$stmtDeposito->execute();
$mysqli->commit();
echo "Transferencia completadan";
} catch (Exception $e) {
$mysqli->rollback();
echo "Error: " . $e->getMessage() . "n";
} finally {
$stmtDescuento->close();
$stmtDeposito->close();
}
Gestión de errores
<?php
// Activar excepciones (recomendado)
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
// A partir de aquí, cualquier error lanzará una mysqli_sql_exception
try {
$mysqli = new mysqli('localhost', 'usuario', 'pass', 'bd');
$stmt = $mysqli->prepare("SELECT * FROM tabla_inexistente");
$stmt->execute();
} catch (mysqli_sql_exception $e) {
echo "Error MySQL [{$e->getCode()}]: {$e->getMessage()}n";
}
MySQLi vs PDO: ¿cuándo elegir cada uno?
- Usa MySQLi si tu proyecto usa exclusivamente MySQL/MariaDB, necesitas múltiples queries en una llamada (
multi_query()), o quieres el rendimiento más ajustado con esa base de datos. - Usa PDO si tu proyecto puede cambiar de base de datos (SQLite para tests, PostgreSQL en producción), quieres una API más uniforme o prefieres
fetchAll()con objetos tipados y named placeholders (:nombreen lugar de?).
Ambas son opciones válidas y modernas. Lo que nunca debes usar son las funciones mysql_* (eliminadas en PHP 7) ni inyectar variables directamente en las queries.
