Адаптация PHP для взаимодействия с MySQL восьмой версии

Раздел: Программирование на PHP -> Работа с версиями PHP и MySQL

Работа с MySQL 8 в PHP

Основной рекомендуемый способ

Наиболее эффективное решение для подключения PHP к MySQL 8 - использование расширения PDO с драйвером mysql. Это обеспечивает единообразный интерфейс, поддержку подготовленных запросов, работу с транзакциями и безопасную обработку данных. Для MySQL 8.0 и выше важно указать корректный набор символов utf8mb4 (полная поддержка эмодзи и редких символов) и отключить эмуляцию подготовленных запросов, чтобы использовать реальные подготовленные выражения на стороне сервера.

<?php
$host = '127.0.0.1';
$dbname = 'test';
$user = 'root';
$pass = 'password';
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$dbname;charset=$charset";

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
    echo 'Подключение успешно';
} catch (PDOException $e) {
    echo 'Ошибка: ' . $e->getMessage();
}
?>

Пояснение: PDO::ATTR_EMULATE_PREPARES = false заставляет драйвер отправлять настоящие подготовленные запросы, что позволяет MySQL 8 оптимизировать выполнение. utf8mb4 обязателен для корректной работы с эмодзи и некоторыми кириллическими символами в MySQL 8.

Как подключиться к MySQL 8 через расширение mysqli?

Расширение mysqli (Improved MySQL) также полностью совместимо с MySQL 8. Оно доступно в двух стилях: объектном и процедурном. Для подключения необходимо указать хост, имя пользователя, пароль, базу данных и порт (по умолчанию 3306).

<?php
$mysqli = new mysqli('127.0.0.1', 'root', 'password', 'test');

if ($mysqli->connect_errno) {
    echo 'Ошибка подключения: ' . $mysqli->connect_error;
    exit;
}

$result = $mysqli->query('SELECT VERSION() AS ver');
$row = $result->fetch_assoc();
echo 'Версия MySQL: ' . $row['ver'];

$mysqli->close();
?>

Важно: при использовании процедурного стиля функции начинаются с mysqli_. Начиная с PHP 8.1, mysqli по умолчанию выбрасывает исключения, если включён режим строгой типизации (mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT)).

Типичные ошибки и их решение

  • Ошибка 2054 (caching_sha2_password): MySQL 8 по умолчанию использует плагин аутентификации caching_sha2_password. Если сервер не настроен на старый mysql_native_password, при подключении может возникнуть ошибка. Решение: изменить плагин для пользователя через SQL-команду ALTER USER 'user'@'host' IDENTIFIED WITH mysql_native_password BY 'password'; или установить параметр default_authentication_plugin=mysql_native_password в my.cnf.
  • Проблемы с кодировкой: если не указать charset в mysqli, используется latin1, что приводит к искажению кириллицы. Рекомендуется сразу после подключения выполнить $mysqli->set_charset('utf8mb4'); или указать в конструкторе параметр с charset.

Что делать при ошибке аутентификации caching_sha2_password?

С версии MySQL 8.0.4 плагин caching_sha2_password стал плагином по умолчанию. Старые клиенты PHP (до PHP 7.1) могут не поддерживать его. Даже в современных версиях встречается ошибка Authentication method 'caching_sha2_password' not supported, если сервер не настроен соответствующим образом.

Существует три способа решения:

  1. На стороне сервера: изменить плагин для конкретного пользователя.
    ALTER USER 'appuser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'newpass';
  2. В конфигурационном файле MySQL: добавить строку default_authentication_plugin=mysql_native_password в раздел [mysqld] и перезапустить сервер.
  3. Использовать расширение mysql_xdevapi (X DevAPI) - оно корректно работает с caching_sha2_password, но требует отдельной настройки и другого API.

При выборе второго способа все новые пользователи будут создаваться с mysql_native_password, что может снизить уровень безопасности. Рекомендуется использовать первый метод, изменяя только тех пользователей, которые подключаются через PHP.

Как выполнить запрос с обобщённым табличным выражением (CTE) в PHP?

MySQL 8 поддерживает CTE (WITH ... AS) и оконные функции. Эти возможности доступны через любые PHP расширения, так как они передают SQL-запрос серверу без дополнительной обработки. Главное - использовать корректный синтаксис и обрабатывать результаты.

<?php
$pdo->query('CREATE TABLE IF NOT EXISTS sales (
    id INT AUTO_INCREMENT PRIMARY KEY,
    product VARCHAR(50),
    amount DECIMAL(10,2),
    sale_date DATE
) ENGINE=InnoDB');

// Пример CTE с ранжированием
$sql = "WITH product_rank AS (
    SELECT product, SUM(amount) AS total,
           RANK() OVER (ORDER BY SUM(amount) DESC) AS rnk
    FROM sales
    GROUP BY product
)
SELECT product, total, rnk
FROM product_rank
WHERE rnk <= 3";

$stmt = $pdo->query($sql);
while ($row = $stmt->fetch()) {
    echo $row['product'] . ': ' . $row['total'] . ' (место ' . $row['rnk'] . ')<br>';
}
?>

Пояснение: CTE product_rank вычисляет общую сумму продаж по продуктам и присваивает им ранг. Внешний запрос выбирает три лучших продукта. Такой запрос выполняется на сервере, в PHP лишь обрабатывается результат.

Возможная проблема: если в таблице много строк, запрос без индексов может выполняться медленно. Рекомендуется добавить индексы на поля product и sale_date для ускорения группировки.

Как работать с JSON-данными в MySQL 8 через PHP?

MySQL 8 имеет встроенный тип данных JSON и функции для его обработки (JSON_EXTRACT, JSON_OBJECT, JSON_ARRAY и др.). В PHP данные JSON можно передавать как обычные строки, а сервер будет проверять их валидность.

<?php
$jsonData = json_encode(['name' => 'Товар', 'price' => 150.50, 'tags' => ['новинка', 'акция']]);

$stmt = $pdo->prepare('INSERT INTO products (data) VALUES (?)');
$stmt->execute([$jsonData]);

$select = $pdo->query('SELECT id, data->>"$.name" AS name FROM products');
while ($row = $select->fetch()) {
    echo 'ID: ' . $row['id'] . ', Название: ' . $row['name'];
}
?>

Оператор ->> (JSON_EXTRACT с разыменованием) возвращает значение как строку. Можно также использовать JSON_UNQUOTE(JSON_EXTRACT(...)). В PHP результат - обычный скаляр.

Ошибки: если JSON невалидный, сервер вернёт ошибку Invalid JSON text. При вставке через PDO исключение перехватывается в блоке catch. Также следует помнить, что индексация JSON-полей в MySQL 8 возможна только через виртуальные столбцы, что может замедлить выборки без них.

Расширенные примеры работы PHP с MySQL 8

Использование CTE с рекурсией (иерархические данные)

Пример
<?php
// Создаём таблицу категорий с родительскими ссылками
$pdo->exec('CREATE TABLE categories (
    id INT PRIMARY KEY,
    name VARCHAR(50),
    parent_id INT,
    FOREIGN KEY (parent_id) REFERENCES categories(id)
) ENGINE=InnoDB');

// Вставляем данные (пример древовидной структуры)
$categories = [
    [1, 'Электроника', null],
    [2, 'Компьютеры', 1],
    [3, 'Ноутбуки', 2],
    [4, 'Планшеты', 2],
    [5, 'Мониторы', 2],
    [6, 'Смартфоны', 1],
];
$stmt = $pdo->prepare('INSERT INTO categories (id, name, parent_id) VALUES (?, ?, ?)');
foreach ($categories as $row) {
    $stmt->execute($row);
}

// Рекурсивный CTE для получения всех подкатегорий 'Компьютеры'
$sql = "WITH RECURSIVE subcat AS (
    SELECT id, name, parent_id
    FROM categories
    WHERE id = 2
    UNION ALL
    SELECT c.id, c.name, c.parent_id
    FROM categories c
    INNER JOIN subcat s ON c.parent_id = s.id
)
SELECT * FROM subcat";

$result = $pdo->query($sql);
echo 'Подкатегории:';
while ($row = $result->fetch()) {
    echo ' - ' . $row['name'] . ' (id=' . $row['id'] . ')<br>';
}
?>
Подкатегории:
 - Компьютеры (id=2)
 - Ноутбуки (id=3)
 - Планшеты (id=4)
 - Мониторы (id=5)

Оконные функции: скользящее среднее

Пример
<?php
$pdo->exec('CREATE TABLE daily_sales (
    sale_date DATE,
    revenue DECIMAL(10,2)
) ENGINE=InnoDB');

// Вставляем данные за несколько дней
$data = [
    ['2025-01-01', 100.00],
    ['2025-01-02', 120.00],
    ['2025-01-03', 110.00],
    ['2025-01-04', 130.00],
    ['2025-01-05', 105.00],
];
$stmt = $pdo->prepare('INSERT INTO daily_sales (sale_date, revenue) VALUES (?, ?)');
foreach ($data as $d) {
    $stmt->execute($d);
}

// Скользящее среднее за 3 дня (центрированное)
$sql = "SELECT sale_date, revenue,
         AVG(revenue) OVER (ORDER BY sale_date ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moving_avg
         FROM daily_sales
         ORDER BY sale_date";

$result = $pdo->query($sql);
echo 'Дата | Выручка | Скользящее среднее<br>';
while ($row = $result->fetch()) {
    echo $row['sale_date'] . ' | ' . $row['revenue'] . ' | ' . round($row['moving_avg'], 2) . '<br>';
}
?>
Дата | Выручка | Скользящее среднее
2025-01-01 | 100.00 | 110.00
2025-01-02 | 120.00 | 110.00
2025-01-03 | 110.00 | 120.00
2025-01-04 | 130.00 | 115.00
2025-01-05 | 105.00 | 117.50

Транзакции с PDO и проверка целостности

Пример
<?php
try {
    $pdo->beginTransaction();
    $pdo->exec('UPDATE accounts SET balance = balance - 100 WHERE id = 1');
    $pdo->exec('UPDATE accounts SET balance = balance + 100 WHERE id = 2');
    $pdo->commit();
    echo 'Перевод средств выполнен успешно';
} catch (PDOException $e) {
    $pdo->rollBack();
    echo 'Ошибка транзакции: ' . $e->getMessage();
}
?>

Использование SSL-соединения с MySQL 8 через PDO

Пример
<?php
$dsn = 'mysql:host=mysql-server;dbname=secure_db;charset=utf8mb4';
$options = [
    PDO::MYSQL_ATTR_SSL_CA    => '/path/to/ca-cert.pem',
    PDO::MYSQL_ATTR_SSL_CERT  => '/path/to/client-cert.pem',
    PDO::MYSQL_ATTR_SSL_KEY   => '/path/to/client-key.pem',
    PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true,
];
$pdo = new PDO($dsn, 'user', 'pass', $options);
?>

Внимание: для работы SSL на сервере MySQL должна быть включена поддержка SSL, а пути к сертификатам должны быть доступны для чтения веб-сервером.

Настройка эмуляции подготовленных запросов и получение реальных типов колонок

Пример
<?php
// При эмуляции prepares = false, PDO возвращает колонки с реальными типами MySQL (INT, DECIMAL, DATETIME и т.д.)
// Это удобно, когда нужно точно знать тип данных без дополнительного парсинга.

$stmt = $pdo->query('SELECT id, price, created_at FROM orders LIMIT 1');
$meta = $stmt->getColumnMeta(0); // 'id'
echo 'Тип колонки id: ' . $meta['native_type'] . '<br>'; // например, 'LONGLONG'
?>
Тип колонки id: LONGLONG

Пакетная вставка с использованием JSON-функций MySQL 8

Пример
<?php
// Вставка нескольких записей в одну строку через JSON_ARRAY
$orders = [
    ['product_id' => 1, 'quantity' => 2],
    ['product_id' => 3, 'quantity' => 5],
];
$json = json_encode($orders);
$sql = "INSERT INTO order_details (order_id, items)
         VALUES (1, JSON_ARRAY($json))"; // упрощённо, на практике нужно экранировать
// Вместо этого можно использовать подготовленный запрос с передачей JSON-строки
$stmt = $pdo->prepare('INSERT INTO events (payload) VALUES (?)');
$stmt->execute([json_encode(['type' => 'page_view', 'time' => date('c')])]);
?>

PHP MySQL 8 - comments

En
Php mysql 8 (php)