Работа с MySQL в сценариях PHP: от основ до продвинутых приемов

Раздел: Разработка на PHP -> Работа с базами данных

Основные подходы к интеграции MySQL в PHP

Как организовать безопасную и гибкую работу с базой данных?

Наиболее эффективным решением является использование расширения PDO (PHP Data Objects). Оно предоставляет единый интерфейс для работы с разными СУБД, поддерживает подготовленные выражения, транзакции и гибкую обработку ошибок через исключения. PDO считается современным стандартом для взаимодействия PHP с MySQL.


// Подключение к MySQL через PDO
$dsn = 'mysql:host=localhost;dbname=mydb;charset=utf8mb4';
$user = 'root';
$password = '';
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
$pdo = new PDO($dsn, $user, $password, $options);
  

После создания объекта PDO выполняются запросы. Подготовленные выражения защищают от SQL-инъекций:


$sql = 'SELECT * FROM users WHERE email = :email';
$stmt = $pdo->prepare($sql);
$stmt->execute([':email' => 'user@example.com']);
$user = $stmt->fetch();
  

Типичные ошибки: неверный DSN (например, пропущен порт), отсутствие драйвера PDO_mysql в PHP, неправильная обработка исключений (забытый блок try-catch). Решение - проверять наличие расширения через phpinfo(), использовать try-catch и логировать сообщения.

Как использовать MySQLi для подключения и запросов?

MySQLi (MySQL Improved) - нативное расширение для работы только с MySQL. Подходит, когда проект не требует смены СУБД. Существует в объектном и процедурном стилях. Рекомендуется объектный стиль с подготовленными выражениями.


$mysqli = new mysqli('localhost', 'root', '', 'mydb');
if ($mysqli->connect_error) {
    die('Ошибка подключения: ' . $mysqli->connect_error);
}
// Подготовленный запрос
$stmt = $mysqli->prepare('SELECT name FROM users WHERE id = ?');
$stmt->bind_param('i', $userId);
$userId = 42;
$stmt->execute();
$stmt->bind_result($name);
$stmt->fetch();
$stmt->close();
  

Целесообразно применять в легаси-системах или если команда знакома только с MySQLi. Проблемы: путаница между bind_param и bind_result, необходимость явно закрывать Statement, отсутствие единого интерфейса для других баз.

Ошибка: забывают проверять connect_error или не вызывают bind_param с правильными типами (i, s, d, b). Решение - использовать объектный стиль и включить режим исключений через mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT).

Почему не следует применять устаревшие функции mysql_*?

Расширение mysql удалено из PHP 7.0. Оно не поддерживает подготовленные выражения, что делает код уязвимым для SQL-инъекций. Использование этих функций в новых проектах недопустимо. Для совместимости со старыми проектами рекомендуется миграция на PDO или MySQLi.

Проблема: проект работает на древней версии PHP, и разработчик не может обновиться. Решение - в крайнем случае применять экранирование через mysql_real_escape_string, но это не даёт полной защиты. Лучше сразу переписать код на современные расширения.

Какие преимущества даёт использование ORM?

ORM (Object-Relational Mapping) как Doctrine или Eloquent скрывают детали SQL-запросов и позволяют работать с объектами. Подходит для крупных проектов с комплексной бизнес-логикой. Однако ORM добавляет накладные расходы и скрывает оптимизацию запросов.


// Пример с Eloquent (Laravel)
$user = User::where('email', 'user@example.com')->first();
echo $user->name;
  

Цель: ускорение разработки, автоматическое управление связями, кэширование. Случаи использования: приложения с Active Record-паттерном, микросервисы.

Ошибка: N+1 запросов при ленивой загрузке связанных данных. Решение - использовать eager loading (with) или явные JOIN.

Расширенные примеры работы с MySQL через PDO

Транзакции с точками сохранения (savepoints)

Для сложных операций, где требуется откат до определённой точки, используются savepoints.

Пример

$pdo->beginTransaction();
$pdo->exec('INSERT INTO logs (msg) VALUES ("Start")');
$pdo->exec('SAVEPOINT sp1');
$pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
// Если ошибка, откатываем до savepoint
$pdo->exec('ROLLBACK TO SAVEPOINT sp1');
$pdo->commit();
-- Результат: в таблице logs появится запись "Start", а баланс не изменится.

Пояснение: savepoints позволяют частичный откат внутри транзакции, не отменяя предыдущие изменения.

Массовая вставка (bulk insert) с подготовленными выражениями

Для вставки множества записей эффективно использовать один запрос с несколькими наборами параметров.

Пример

$data = [
    ['Alice', 'alice@example.com'],
    ['Bob', 'bob@example.com'],
    ['Charlie', 'charlie@example.com']
];
$pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO users (name, email) VALUES (:name, :email)');
foreach ($data as $row) {
    $stmt->execute([':name' => $row[0], ':email' => $row[1]]);
}
$pdo->commit();

Недостаток: каждое выполнение отдельно, но в транзакции это приемлемо. Более быстрый способ - собрать один запрос с несколькими VALUES, но тогда сложнее с параметрами.

Пример

$placeholders = [];
$values = [];
foreach ($data as $i => $row) {
    $placeholders[] = "(:name$i, :email$i)";
    $values[":name$i"] = $row[0];
    $values[":email$i"] = $row[1];
}
$sql = 'INSERT INTO users (name, email) VALUES ' . implode(', ', $placeholders);
$stmt = $pdo->prepare($sql);
$stmt->execute($values);

Результат: одна операция вставки, быстрее для больших наборов.

Выборка данных с помощью FETCH_CLASS

Можно автоматически заполнять объекты определённого класса.

Пример

class User {
    public $id;
    public $name;
    public $email;
}
$stmt = $pdo->query('SELECT id, name, email FROM users LIMIT 3');
$users = $stmt->fetchAll(PDO::FETCH_CLASS, 'User');
foreach ($users as $user) {
    echo $user->name . "\n";
}
Alice
Bob
Charlie

Пояснение: свойства заполняются до вызова конструктора, если не определить __construct с аргументами.

Обработка ошибок с извлечением SQLSTATE

При использовании PDO::ERRMODE_EXCEPTION исключения содержат код SQLSTATE для анализа.

Пример

try {
    $pdo->exec('INSERT INTO non_existent_table VALUES (1)');
} catch (PDOException $e) {
    $errorCode = $e->getCode(); // SQLSTATE, например '42S02'
    $errorMessage = $e->getMessage();
    if ($errorCode === '42S02') {
        // Таблица не существует
    }
}

Полезно для логирования и выборочной обработки различных типов ошибок.

Работа с BLOB-данными (изображения, файлы)

BLOB хранятся как строки, можно использовать подготовленные выражения.

Пример

$imageData = file_get_contents('photo.jpg');
$stmt = $pdo->prepare('INSERT INTO images (name, data) VALUES (:name, :data)');
$stmt->bindValue(':name', 'photo.jpg');
$stmt->bindValue(':data', $imageData, PDO::PARAM_LOB);
$stmt->execute();

Для выбора используйте fetchColumn или fetch с указанием PDO::PARAM_LOB.

Пример

$stmt = $pdo->prepare('SELECT data FROM images WHERE id = :id');
$stmt->execute([':id' => 1]);
$data = $stmt->fetchColumn();
file_put_contents('output.jpg', $data);

Подключение через Unix-сокет (для локального сервера)

Пример

$dsn = 'mysql:unix_socket=/var/run/mysqld/mysqld.sock;dbname=test';
$pdo = new PDO($dsn, 'root', '');

Это может быть быстрее, чем TCP/IP, особенно на локальной машине.

LIKE-запрос с подстановкой через подготовленное выражение

Пример

$search = '%john%';
$stmt = $pdo->prepare('SELECT * FROM users WHERE name LIKE :search');
$stmt->execute([':search' => $search]);
$users = $stmt->fetchAll();

Важно: параметр должен содержать символы '%' или '_' уже в значении, так как PDO не экранирует их специально.

Работа с MySQL в PHP - comments

En
Php mysql (php)