Егор Банин

Основы работы с MySQL (MariaDB) с помощью mysqli

Если вы только осваиваетесь в php, то скорее всего допускаете неочевидные ошибки при работе с базой данных. Особенно досадно, что такие ошибки могут быть довольно опасными. Я постараюсь вкратце объяснить, как всё работает, и помочь вам избежать типичных проблем.

Что такое база данных и mysqli

База данных это специальная программа, которая умеет сохранять и извлекать ранее сохранённые данные. Она чем-то похожа на файл – вы можете записать в неё данные, а можете прочитать их. Но в отличие от файла база данных предлагает гораздо более мощные инструменты взаимодействия с данными. Например MySQL позволяет работать с данными множеству пользователей одновременно, поддерживает разнообразные типы данных и язык запросов SQL, оптимизирует доступ к данным, обеспечивая высокую производительность.

Чтобы работать с базой данных, надо подключиться и взаимодействовать с ней с помощью специального интерфейса. Этот интерфейс в php обеспечивает библиотека mysqli. С этой библиотекой можно работать в процедурном и в ООП-стиле. В этой статье я буду разбирать только процедурный стиль.

Подключение

Для подключения к базе используется функция mysqli_connect . Код подключения выглядит приблизительно так:

$db = mysqli_connect('localhost', 'username', 'password', 'defaultDb', 3306);

Попытка подключения может быть неуспешной по разным причинам (например, админ поменял пароль). Поведением mysqli при ошибках управляет функция mysqli_report . До php 8.1 по умолчанию в случае проблем функции mysqli будут вызывать ошибки, после – исключения. Исключения гораздо удобнее, но если вы ещё не разобрались с ними, то я покажу как работать с ошибками. Для этого установим MYSQLI_REPORT_OFF.

mysqli_report(MYSQLI_REPORT_OFF);
$db = @mysqli_connect('localhost', 'username', 'password', 'defaultDb', 3306);
if ( ! $db) {
	echo '
		<h1>Технические неполадки</h1>
		<p>
			Сайт временно не работает, зайдите позже или свяжитесь с нами по телефону
			<a href="tel:+71234567890">+7 123 456-78-90</a>.
		</p>
	';
	error_log(sprintf('Не удалось подключиться к базе данных: %s', mysqli_connect_error()));
	exit(1);
}

Замечание насчёт @

Вы могли встретить рекомендацию не использовать @ в своём коде. Это хороший совет, во многих случаях лучше управлять ошибками через настройки error_reporting и display_errors. Но в этом руководстве для новичков я осознано упрощаю код, чтобы вы могли сосредоточиться именно на работе с mysqli.

Обработка ошибок – важная часть любой программы. Именно в случае возникновения проблем пользователи особенно требовательны к вашему приложению. Кроме того и вам, хорошо бы получить уведомление о проблеме, чтобы как можно скорее отреагировать на неё. Поэтому не ограничивайтесь конструкцией типа or die. Обратите внимание, что в разных случаях на ошибки надо реагировать по-разному. Если мы пишем интерфейс для работы с базой данных, где пользователь сам вводит имя и пароль (например, phpMyAdmin или adminer), то ошибка подключения – проблема пользователя, и ему надо предложить проверить и исправить данные подключения. Но если мы делаем сайт интернет-магазина, то та же ошибка подключения – проблема магазина, и магазину надо помочь пользователю сориентироваться в этой ситуации.

Настройка кодировки

Строки в вашем php-файле, строки, переданные вам пользователем, и строки в базе данных могут быть в разных кодировках. Обязательно настройте кодировку подключения с помощью mysqli_set_charset .

if ( ! mysqli_set_charset($db, "utf8mb4")) {
	internalServerError(mysqli_error($db)); // какой-то ваш обработчик ошибки
}

Установка кодировки означает, что вы будете отправлять запросы в указанной кодировке, убедитесь что это действительно так. Если у вас всё в utf-8 (в современной разработке это почти всегда так), то mysqli_set_charset($db, "utf8mb4") – правильный выбор.

utf8 и utf8mb4

Исторически сложилось, что в MySQL реализация utf-8 была не полной, но в какой-то момент появилась полноценная. utf8 – алиас для utf8mb3, а новую назвали utf8mb4.

Запросы

Если подключение прошло удачно, можно пользоваться созданным подключением для выполнения запросов. Функция, которая делает запрос в базу данных и получает результат, называется mysqli_query. В случае проблем с доступом к базе данных или при ошибках в запросе она тоже будет возвращать false. Если запрос должен возвращать строки, то получить их можно с помощью mysqli_fetch_all или, если вам недоступна эта функция (php < 8.1, с библиотекой libmysqlclient) , mysqli_fetch_assoc .

$result = @mysqli_query($db, '
	select
		`id`,
		`title`
	from `articles`
	where `isPublished` = true
	order by `id` desc
');
if ( ! $result) {
	internalServerError(mysqli_error($db)); // какой-то ваш обработчик ошибки
}

$rows = [];
while ($row = mysqli_fetch_assoc($result)) {
	$rows[] = $row;
}

var_dump($rows); // [['id' => 1, 'title' => 'Основы работы с MySQL (MariaDB) с помощью mysqli'], ...]

Переменные в запросах

При подстановке переменных в запрос надо быть особенно внимательным. Если просто конкатенировать строчки, то SQL-запрос может сломаться или даже стать вредоносным (например, удалит все данные из таблицы). Чтобы стало понятно, как это работает, приведу пример:

// предположим, что мы разрешаем пользователю удалять статьи, автором которых он является
mysqli_query($db, '
	delete
	from `articles`
	where
		`author` = ' . $usreId . '
		and `id` = ' . $_POST['id'] . ' // это небезопасно!
');

Может показаться, что запрос верный. Но так как мы не можем знать, что пользователь передаст в $_POST['id'], то эта конкатенация может сильно изменить запрос. Допустим, пользователь передаст 1 or true. В таком случае он удалит все статьи. Это называется SQL-инъекция .

Валидация не является выходом. Например, если мы добавляем в базу имя пользователя, которого зовут д’Артаньян (вполне валидное имя), то апостроф в его имени может быть интерпретирован базой как начало или конец строкового значения и вызовет ошибку.

Одно из решений этой проблемы – экранирование. Специальня функция mysqli_real_escape_string экранирует все специальные символы строки, которые могли бы быть восприняты базой как часть SQL-синтаксиса. Обратите внимание на предупреждение в документации: “набор символов должен быть задан либо на стороне сервера, либо с помощью API-функции mysqli_set_charset()” .

Все строки, вставляемые в SQL, должны быть экранированы этой функцией:

$name = $_POST['name']?? '';

$result = mysqli_query($db, '
	select
		`articles`.`id`,
		`articles`.`title`
	from `articles`
	inner join `users`
		on `users`.`id` = `articles`.`author`
	where
		`users`.`name` = "' . mysqli_real_escape_string($db, $name) . '"
		and `articles`.`isPublished` = true
	order by `articles`.`id` desc
	limit 10
');

Теперь мы можем получить статьи даже того автора, который назвал себя Robert"; drop table students;--.

Для чисел избежать инъекций поможет приведение типа:

$id = $_POST['id']?? '';

// предположим, что мы разрешаем пользователю удалять статьи, автором которых он является
mysqli_query($db, '
	delete
	from `articles`
	where
		`author` = ' . (int) $usreId . '
		and `id` = ' . (int) $id . ' // теперь всё ok
');

И ещё раз. Отнеситесь к экранированию очень серьёзно. Любые данные, которые приходят из запроса пользователя – это строки, которые нельзя подставлять в запрос просто так.

Подготовленные запросы

Другим способом подставить значения переменных в запрос являются подготовленные запросы. Обязательно изучите эту тему самостоятельно, когда освоитесь с основами mysqli.

Заключение

Освоив азы, изучите объектно ориентированный интерфейс mysqli, подготовленные запросы, асинхронные запросы. Познакомьтесь с другой стандартной библиотекой для работы с базами данных – PDO. Изучайте возможности вашей базы данных: типы данных, индексы, транзакции, партиционирование и репликацию.