Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11468 глав6256 карточек справочника
Дерево руководства

Глава 106 · Миграция после первого знакомства с PHP и SQL

Старый mysql_*: соединение, кодировка, запросы и перенос на MySQLi

Прочитать исторический договор без запуска PHP5 и перенести каталог на явное соединение, utf8mb4, обработку ошибок и понятный результат записи.

Зачем читать старый API и что должно сохраниться

Вы открыли старый PHP-проект и увидели mysql_connect, mysql_query или mysql_result. Задача — восстановить смысл программы и перенести его на поддерживаемый драйвер: тот же каталог, те же сохранённые значения и понятные отказы. Здесь старый договор описан полностью в отдельных карточках; новые программы используют MySQLi.

Старый и новый путь одного каталога
  1. PHP-код выбирает драйвер: ext/mysql раньше, MySQLi сейчас
  2. Соединение с сервером и выбор схемы
  3. SQL выбирает id/name из таблицы товаров
  4. Результат запроса — отдельный набор
  5. fetch извлекает очередную PHP-строку
  6. Явное HTML-экранирование → видимая таблица

Если MySQL для вас новая тема, начните с соединения, сервера и схемы и таблиц и связей. Здесь текущий новый шаг — различия API при переносе существующего кода; знание старого расширения не требуется.

Что было в старом APIЧто меняется при переносе
mysql_query($sql,$link)mysqli_query($db,$sql): соединение стало первым обязательным аргументом.
link и result — resourcemysqli и mysqli_result — разные объекты с разными обязанностями.
Последний link и попытка автоматического подключенияЯвно передавать нужный $db и выбирать схему.
fetch возвращал false на концеСовременный fetch возвращает null на конце; отдельно возможна ошибка false.
if(!$result) / die(mysql_error())Различать пустой результат, bool записи и исключение; не раскрывать детали посетителю.
mysql_result($r,позиция,поле)Получить строку или сделать точный WHERE; официальной функции mysqli_result() нет.

MySQLi допускает процедурные вызовы и методы объекта: mysqli_query($db,$sql) и $db->query($sql) работают с тем же соединением. Не требуется переписывать весь проект на классы ради миграции; требуется проверить параметры, возвраты, ошибки и владение результатом. PDO_MySQL — отдельный современный путь; его подключение и подготовленные запросы рассматриваются отдельно.

Практическая ситуация: Find/replace mysql_ to mysqli_ makes code nonfunctional. Автор заменил префиксы во всём коде: редактор не видел синтаксических ошибок, но обращения к базе перестали работать. Разбор показывает явное соединение и изменение порядка аргументов вместо массовой замены имён. Вопрос используется как описание проблемы; код и объяснение написаны заново, технические правила сверены с PHP.net.

Версии: где заканчивается исторический договор

ВерсияИсторическое изменение
PHP4Исходное расширение обычно включалось по умолчанию с клиентской библиотекой.
PHP4.3.0mysql_escape_string устарела: не учитывает charset подключения. Появились client_encoding, real_escape_string, info.
PHP5.2.3Добавлена mysql_set_charset; исторически требовался MySQL5.0.7+.
PHP5.3.0Доступен mysqlnd; mysql_db_query устарела.
PHP5.4.0mysqlnd стал стандартным вариантом клиентской библиотеки.
PHP5.5.0Устарело всё ext/mysql.
PHP7.0.0ext/mysql удалено; mysql_* более не входит в PHP.
PHP8.1+ MySQLiОшибки по умолчанию обрабатываются MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT; в примерах режим задан явно.

Общий официальный changelog ext/mysql отдельно фиксирует удаление в7.0. Частные начальные версии и ранние deprecation указаны у карточек. Мы не объявляем исторические примеры выполненными на PHP8. Проверяется современный перенос, а исторический контракт сверяется с сохранённой официальной документацией.

Подготовить современный перенос и запустить один архив

Для новых программ нужны сервер MySQL8.4, PHP8.2+ с mysqli/mysqlnd, своя учебная схема и ограниченный пользователь. Проверочный стенд — PHP8.5.11/MySQL8.4.13LTS. Сервер и база не упакованы в архив; каждый пример сам создаёт TEMPORARY-таблицу своего соединения.

  • Подготовить локальный сервер и пользователя по полной инструкции с CREATE DATABASE/USER/GRANT. Нужны SELECT/INSERT/UPDATE/DELETE/CREATE TEMPORARY TABLES только для учебной схемы.
  • В терминале задать собственные PHPMAN_MYSQL_HOST, PHPMAN_MYSQL_PORT, PHPMAN_MYSQL_DATABASE, PHPMAN_MYSQL_USER, PHPMAN_MYSQL_PASSWORD. Для TCP указан127.0.0.1; порт стенда33070 — не обязательный порт вашего сервера.
  • Распаковать один архив, выполнить php example.php, открыть result.html. README.txt и helper уже вложены; предыдущие программы запускать не нужно.
  • result.json содержит данные таблицы; metrics.json — проверки и версии без пароля. Все новые программы — CLI, а не проверка HTTP-страниц и не демонстрация авторизации.
Шаблон PowerShell: свои настройки, не опубликованные учётные данные
$env:PHPMAN_MYSQL_HOST='127.0.0.1'
$env:PHPMAN_MYSQL_PORT='33070'
$env:PHPMAN_MYSQL_DATABASE='my_training'
$env:PHPMAN_MYSQL_USER='my_student'
# PHPMAN_MYSQL_PASSWORD задайте своим локальным паролем
php example.php

В Unix-shell те же значения задаются export PHPMAN_MYSQL_DATABASE='my_training' и аналогично для остальных переменных. При undefined function mysqli_connect проверьте расширение именно того PHP, которым запускаете программу; при undefined function mysql_connect на PHP8 исправляется код, а не устанавливается старое расширение. Не публикуйте phpinfo с конфигурацией сервера.

Сервер, PHP-расширение и клиентская библиотека

Исторический mysql.requirements требовал сборку PHP с поддержкой MySQL. Наличие сервера само по себе не добавляло PHP-функции. Старый клиентский модуль мог использовать libmysqlclient или mysqlnd; версия сервера, клиентская библиотека и версия PHP — три разных величины.

Сегодня расширение ext/mysql не является доступной зависимостью PHP7/8. Для работы архивов проверяют mysqli и mysqlnd текущего PHP, затем доступность локального MySQL и права на отдельную схему. Нельзя подмешать php_mysql.dll от PHP5 в PHP8. Пути настройки современного PHP даны в начале руководства и подготовке MySQL.

Как читать старые инструкции установки без опасного отката

Историческая сборкаЧто означала настройка
PHP4 / Unixlibmysqlclient по умолчанию; --without-mysql отключало поддержку; клиентские библиотеки поставлялись вместе с PHP.
PHP5.0–5.2 / Unix--with-mysql=DIR указывал каталог headers/libraries; расширение уже не включалось автоматически, библиотеки не были bundled.
PHP5.3 / Unix--with-mysql=mysqlnd выбирал встроенный драйвер; --with-mysql=DIR — libmysqlclient.
PHP5.4 / Unix--with-mysql использовал mysqlnd по умолчанию; отдельный DIR позволял libmysqlclient.
PHP5.0–5.2 / Windowsextension_dir и php_mysql.dll в php.ini; libmysql.dll должна находиться в PATH. Копирование в системный каталог не рекомендовалось.
PHP5.3+ / WindowsПри mysqlnd libmysql.dll более не требовалась; php_mysql.dll относилась к тогдашнему ext/mysql.

Общий исторический --with-mysql[=DIR] относился к конфигурации сборки, DIR — к клиентским библиотекам, не к файлам базы. Расширение работало с MySQL4.1+, но не раскрывало новые возможности этих серверов; для них предназначался MySQLi. Совместная загрузка ext/mysql и mysqli требовала одной клиентской библиотеки, иначе возможны конфликты.

В официальных старых примечаниях отмечены конфликты с recode и ограничения набора charset у bundled libmysqlclient. Ошибка загрузки php_mysql.dll могла означать отсутствие самой DLL или зависимости libmysql.dll. Эти причины относятся к историческому окружению; современный перенос проверяет текущий драйвер и его настройки отдельно.

Все настройки старого mysql.* и их границы

Историческая директиваЗначение и область измененияСмысл при чтении старого ini
mysql.allow_local_infile1; INI_SYSTEMРазрешение локальных файлов для LOAD DATA со стороны PHP. Не включать ради миграции без отдельной задачи.
mysql.allow_persistent1; INI_SYSTEMРазрешение постоянных соединений.
mysql.max_persistent-1; INI_SYSTEMМаксимум постоянных соединений на процесс; -1 без заданного лимита.
mysql.max_links-1; INI_SYSTEMВсе соединения процесса, включая постоянные.
mysql.trace_mode0; INI_ALLС PHP4.3: диагностические предупреждения о scan, неосвобождённых результатах и SQL-ошибках.
mysql.default_portNULL; INI_ALLПри отсутствии: MYSQL_TCP_PORT, mysql-tcp из /etc/services, затем compile-time MYSQL_PORT; Windows использовал последний.
mysql.default_socketNULL; INI_ALLСокет локального сервера при неуказанном пути.
mysql.default_hostNULL; INI_ALLХост по умолчанию; исключение — старый SQL safe mode.
mysql.default_userNULL; INI_ALLПользователь по умолчанию; исключение — SQL safe mode.
mysql.default_passwordNULL; INI_ALLПароль по умолчанию; исключение — SQL safe mode.
mysql.connect_timeout60; INI_ALLСекунды подключения; на Linux также ожидание первого ответа.

INI_SYSTEM задавалось системной конфигурацией; INI_ALL допускало доступные пользовательские способы изменения. Директивы mysql.* принадлежат удалённому расширению, не являются автоматически настройками mysqli.*. Не переносите пароли из общего php.ini в публикуемый пример. В новых программах параметры подключения явны, а таймаут задан до real_connect.

Два исторических ресурса и два современных объекта

УровеньИсторическое значениеСовременный результат
Подключениеlink_identifier: resourcemysqli: канал к серверу, выбранная схема и состояние.
Ответ SELECTresult: resourcemysqli_result: строки, поля и курсор одного запроса.
Успешная команда без набораtruetrue; нельзя передавать в fetch/free результата.
Извлечённая строкаarray или objectОтдельные PHP-данные, не соединение и не весь набор.

У ext/mysql было ровно два специальных вида ресурсов: link и результат запроса. Освобождение набора и закрытие подключения — разные действия. Эта разница показана в самостоятельном сценарии владения результатами. Не используйте is_resource($db) для современного mysqli: он является объектом.

Клиентские флаги и формы результата

Старая константаЧто выбиралаСовременный смысл
MYSQL_CLIENT_COMPRESSПротокол сжатияОтдельная возможность клиентского соединения; не формат таблицы.
MYSQL_CLIENT_IGNORE_SPACEПробелы после имён SQL-функцийМеняет разбор SQL; не обрезает пробелы из данных.
MYSQL_CLIENT_INTERACTIVEinteractive_timeout вместо wait_timeoutПолитика ожидания сервера для соединения.
MYSQL_CLIENT_SSLSSL; требовалась клиентская библиотека4.x+Не доказательство проверки сертификата; современная настройка TLS рассматривается отдельно.
MYSQL_ASSOCКлючи по именам полейMYSQLI_ASSOC.
MYSQL_NUMИндексы0,1,…MYSQLI_NUM.
MYSQL_BOTHИмена и индексы одной строкиMYSQLI_BOTH; не два разных товара.

Старые MYSQL_* определялись только при загрузке ext/mysql; в PHP8 их нет. Флаги соединения комбинировались побитовым OR, а режим fetch выбирался одним значением. В некоторых старых connect-контрактах число128 разрешало LOCAL INFILE; не подставляйте магическое число в современный код. Префикс MYSQLI_ и значение конкретного флага проверяются по актуальному справочнику MySQLi.

Указать соединение и схему явно, затем прочитать каталог

Старый код рассчитывает на последнее открытое соединение. После переноса два модуля могут незаметно работать с разными базами. Начнём с одного канала к серверу: до select_db выбранной схемы нет; после выбора получаем три доступных товара.

Путь данных: Указать соединение и схему явно, затем прочитать каталог
  1. Открыть локальный канал без выбранной схемы
  2. SELECT DATABASE() → NULL
  3. Выбрать свою схему
  4. Создать учебный каталог
  5. SELECT id,name WHERE stock>0 → три строки
  6. fetch_assoc → Чай, Какао, Мёд
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
mysqli_select_db($db, $database);
$result = mysqli_query($db, 'SELECT id,name FROM products WHERE stock > 0 ORDER BY id');
while ($row = mysqli_fetch_assoc($result)) {
    echo $row['name'], "\n";
}

Соединение — канал и состояние клиента. Схема — именованное место таблиц на сервере. $db хранит объект канала; select_db выбирает схему для последующих неуточнённых имён таблиц. mysqli_query принимает $db первым, SQL вторым; mysql_query принимал link последним. Строки не выводятся автоматически: fetch_assoc берёт одну, while повторяет чтение, echo показывает имя.

Что должно получиться

ШагРезультат
До select_dbNULL
Выбрана учебная схемаtrue
Товары в наличииЧай, Какао, Мёд

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Указать соединение и схему явно, затем прочитать каталогСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect(0,false,false);
try {
    $before=$db->query('SELECT DATABASE() AS selected_schema')->fetch_assoc()['selected_schema'];
    $selected=$db->select_db(getenv('PHPMAN_MYSQL_DATABASE'));
    $after=$db->query('SELECT DATABASE() AS selected_schema')->fetch_assoc()['selected_schema'];
    legacy108Catalog($db);
    $result=mysqli_query($db,'SELECT id,name FROM mysql108_legacy_products WHERE stock > 0 ORDER BY id');
    $found=[];
    while ($record=mysqli_fetch_assoc($result)) { $found[]=$record['name']; }
    mysqli_free_result($result);
    $rows=[['До select_db',null],['Выбрана учебная схема',$selected && $after===getenv('PHPMAN_MYSQL_DATABASE')],['Товары в наличии',implode(', ',$found)]];
    $checks=['no_initial_schema'=>$before===null,'selected_explicitly'=>$selected && $after===getenv('PHPMAN_MYSQL_DATABASE'),'catalog_read'=>$found===['Чай','Какао','Мёд']];
    $runtime=legacy108Runtime($db);
} finally { $db->close(); }
legacy108Report('mysql108-connect-schemas', ['Шаг', 'Результат'], $rows, $checks, $runtime, $details);
echo "Explicit connection and schema: 3 available products\n";
Ожидаемый результат
Explicit connection and schema: 3 available products

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Короткая запись использует уже открытое $db и таблицу products; полный архив создаёт свои mysql108_legacy_products. Если сервер недоступен, схема отсутствует или нет прав — это разные отказы. Подключение не создаёт базу. Настройка localhost/socket отличается от явного TCP 127.0.0.1.

Попробовать самостоятельно

Выберите stock=0. Решение: заменить условие на равенство; ожидается только Кофе. Не менять остальные поля SELECT.

Перенести имя с апострофом и эмодзи без ручной склейки SQL

После миграции в имени клиента появляется лишний слеш или пропадает эмодзи. Сохраним одно имя двумя путями, а сравнивать будем реально прочитанный текст, не красивый вид SQL.

Путь данных: Перенести имя с апострофом и эмодзи без ручной склейки SQL
  1. Исходный текст O’Reilly / Чай / 🍵
  2. utf8mb4 соединения и столбца
  3. Параметризованная INSERT → ID1
  4. Узкий экранированный literal → ID2
  5. SELECT → обе исходные строки совпадают
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$db->set_charset('utf8mb4');
$stmt = $db->prepare('INSERT INTO names (id,name) VALUES (?,?)');
$stmt->bind_param('is', $id, $name);
$stmt->execute();

set_charset согласует кодировку соединения. ? представляет значение целиком: bind_param передаёт его без ручных SQL-кавычек. В строковом literal одинарные кавычки всё ещё часть SQL; real_escape_string не добавляет их. Полная программа оставляет этот второй путь только для понимания исторического кода.

Что должно получиться

IDПрочитанное имяКодировка соединения
1O'Reilly — Чай 🍵utf8mb4
2O'Reilly — Чай 🍵utf8mb4

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Перенести имя с апострофом и эмодзи без ручной склейки SQLСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();
try {
    $db->query('CREATE TEMPORARY TABLE mysql108_legacy_names (id INT PRIMARY KEY,name VARCHAR(80) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
    $name="O'Reilly — Чай 🍵";
    $stmt=$db->prepare('INSERT INTO mysql108_legacy_names (id,name) VALUES (?,?)');
    $id=1;$stmt->bind_param('is',$id,$name);$stmt->execute();$stmt->close();
    $escaped=$db->real_escape_string($name);
    $db->query("INSERT INTO mysql108_legacy_names (id,name) VALUES (2,'".$escaped."')");
    $result=$db->query('SELECT id,name FROM mysql108_legacy_names ORDER BY id');$rows=[];$names=[];
    while ($r=$result->fetch_assoc()) { $rows[]=[(int)$r['id'],$r['name'],$db->character_set_name()];$names[]=$r['name']; }
    $result->free();
    $details=['escaped_bytes'=>strlen($escaped),'sql_mode'=>$db->query('SELECT @@SESSION.sql_mode AS mode')->fetch_assoc()['mode']];
    $checks=['utf8mb4'=>$db->character_set_name()==='utf8mb4','both_roundtrips'=>$names===[$name,$name],'input_not_modified'=>$name==="O'Reilly — Чай 🍵"];
    $runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-charset-quotes', ['ID', 'Прочитанное имя', 'Кодировка соединения'], $rows, $checks, $runtime, $details);
echo "Two text roundtrips preserved apostrophe and UTF-8\n";
Ожидаемый результат
Two text roundtrips preserved apostrophe and UTF-8

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Основной вариант — prepared. Экранирование SQL не является HTML-экранированием, не делает безопасными имена колонок и не отменяет LIKE %/_. Демонстрация literal ограничена одинарными кавычками; режим NO_BACKSLASH_ESCAPES особенно важен при разборе старого кода с двойными кавычками. Для HTML-отчёта применяется htmlspecialchars отдельно.

Попробовать самостоятельно

Добавьте в имя обратный слеш. Решение: изменить исходный $name; prepared-вариант не требует нового SQL. Сверить обратное чтение, не текст запроса.

Различить пустой SELECT, успешную запись и ошибку запроса

Старый if (!$result) часто превращал разные ситуации в одно «ничего не найдено». В миграции нужно вернуть человеку правильное действие: пустой поиск допустим, несуществующее поле требует исправления программы.

Путь данных: Различить пустой SELECT, успешную запись и ошибку запроса
  1. Пустой SELECT →0 строк, без ошибки
  2. UPDATE →true и1 изменение
  3. SELECT неизвестного поля →1054
  4. Следующий корректный SELECT →остаток13
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
try {
    $result = $db->query($sql);
} catch (mysqli_sql_exception $error) {
    $code = $error->getCode();
    // Техническое сообщение — в закрытый журнал.
}

SELECT возвращает объект набора даже при0 строк. UPDATE возвращает true; affected_rows сообщает1 изменение. Явно включённый MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT переводит SQL-ошибку в mysqli_sql_exception; getCode() даёт1054 для неизвестного поля. Состояние сохраняется в catch до следующей команды.

Что должно получиться

ДействиеТип ответаЗначение
SELECT без совпаденийнабор строк0
UPDATEtrue1
Неизвестное полеисключение1054
Следующий SELECTостаток товара13

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Различить пустой SELECT, успешную запись и ошибку запросаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();legacy108Catalog($db);
try {
    $empty=$db->query('SELECT id,name FROM mysql108_legacy_products WHERE id=999');$emptyCount=$empty->num_rows;$empty->free();
    $ok=$db->query('UPDATE mysql108_legacy_products SET stock=stock+1 WHERE id=1');$modified=$db->affected_rows;
    $caughtCode=0;$caughtText='';
    try {$db->query('SELECT column_that_does_not_exist FROM mysql108_legacy_products');}
    catch(mysqli_sql_exception $e) {$caughtCode=$e->getCode();$caughtText=$e->getMessage();}
    $healthy=(int)$db->query('SELECT stock FROM mysql108_legacy_products WHERE id=1')->fetch_assoc()['stock'];
    $rows=[['SELECT без совпадений','набор строк',0],['UPDATE','true',$modified],['Неизвестное поле','исключение',$caughtCode],['Следующий SELECT','остаток товара',$healthy]];
    $checks=['empty_is_success'=>$emptyCount===0,'write_is_true'=>$ok===true && $modified===1,'error_classified'=>$caughtCode===1054 && $caughtText!=='','connection_still_usable'=>$healthy===13];
    $details=['technical_error_recorded'=>$caughtText!=='','public_policy'=>'error text stays out of the visitor message'];
    $runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-query-errors', ['Действие', 'Тип ответа', 'Значение'], $rows, $checks, $runtime, $details);
echo "Empty result, successful write and SQL error distinguished\n";
Ожидаемый результат
Empty result, successful write and SQL error distinguished

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Не выводите полный текст исключения посетителю: он может содержать внутренние имена. Не подставляйте SQL в сообщение страницы. Этот сценарий проверяет SQL-отказ на существующем соединении; сетевой timeout/неверный пароль относятся к главе подготовки и диагностики.

Попробовать самостоятельно

Замените неизвестное поле на name. Решение: запрос перестанет выбрасывать исключение; обновите ожидаемую ветку, а не подавляйте exception оператором@.

Сразу сохранить ID, количество изменений и сведения UPDATE

Старая админка должна после сохранения открыть созданный товар и правильно сообщить «изменено». Сохраним результаты записи сразу, прежде чем следующий запрос заменит их.

Путь данных: Сразу сохранить ID, количество изменений и сведения UPDATE
  1. INSERT Чай/Кофе → первыйID1, строк2
  2. UPDATE stock5 тем же5 →0
  3. UPDATE stock7 →1
  4. DELETE ID2 →1
  5. SELECT →Чай с остатком7
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$db->query($insertSql);
$id = $db->insert_id;
$changed = $db->affected_rows;
$details = $db->info;

AUTO_INCREMENT выдаёт идентификатор. Вставка двух строк возвращает первый автоматически созданный ID; это не максимальный ID таблицы. affected_rows — число изменений этой команды. info содержит диагностическое пояснение, а SELECT подтверждает конечные данные.

Что должно получиться

ДействиеIDСтрок изменено / итоговый остаток
INSERT двух товаров12
UPDATE прежним значением10
UPDATE новым значением11
DELETE второго товара21
Остался Чай17

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Сразу сохранить ID, количество изменений и сведения UPDATEСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();
try {
    $db->query('CREATE TEMPORARY TABLE mysql108_legacy_changes (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(40),stock INT NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
    $db->query("INSERT INTO mysql108_legacy_changes (name,stock) VALUES ('Чай',5),('Кофе',0)");
    $firstId=$db->insert_id;$inserted=$db->affected_rows;$insertInfo=$db->info;
    $db->query('UPDATE mysql108_legacy_changes SET stock=5 WHERE id=1');$same=$db->affected_rows;$sameInfo=$db->info;
    $db->query('UPDATE mysql108_legacy_changes SET stock=7 WHERE id=1');$changed=$db->affected_rows;$changedInfo=$db->info;
    $db->query('DELETE FROM mysql108_legacy_changes WHERE id=2');$deleted=$db->affected_rows;
    $r=$db->query('SELECT id,name,stock FROM mysql108_legacy_changes ORDER BY id')->fetch_assoc();
    $rows=[['INSERT двух товаров',(int)$firstId,$inserted],['UPDATE прежним значением',1,$same],['UPDATE новым значением',1,$changed],['DELETE второго товара',2,$deleted],['Остался '.$r['name'],(int)$r['id'],(int)$r['stock']]];
    $checks=['first_generated_id'=>(int)$firstId===1,'inserted_two'=>$inserted===2,'unchanged_zero'=>$same===0,'modified_one'=>$changed===1,'deleted_one'=>$deleted===1,'retained_data'=>$r['name']==='Чай' && (int)$r['stock']===7,'info_available'=>is_string($sameInfo) && $sameInfo!==''];
    $details=['insert_info'=>$insertInfo,'unchanged_update_info'=>$sameInfo,'changed_update_info'=>$changedInfo];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-write-outcomes', ['Действие', 'ID', 'Строк изменено / итоговый остаток'], $rows, $checks, $runtime, $details);
echo "ID1; insert2; unchanged0; modified1; delete1; stock7\n";
Ожидаемый результат
ID1; insert2; unchanged0; modified1; delete1; stock7

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

Ноль affected_rows не всегда означает «такого товара нет»: значение могло не измениться. FOUND_ROWS меняет договор. info сохраняется в metrics как наблюдаемая строка; не парсим её как стабильный прикладной протокол. Исторические REPLACE/UPSERT имеют особые счётчики, см. карточку.

Попробовать самостоятельно

Повторите UPDATE stock7. Решение: в обычном режиме ожидается0, а контрольный SELECT остаётся7.

Понять, что persistent-соединение не заменяет настройки каждого входа

В старом проекте pconnect мог хранить link дольше одного запроса. При переносе важно не принять старое состояние соединения за обязательную настройку приложения.

Путь данных: Понять, что persistent-соединение не заменяет настройки каждого входа
  1. Открыть p:-дескриптор
  2. Явно настроить utf8mb4 и схему
  3. Задать маркер первой операции
  4. Закрыть PHP-дескриптор
  5. Открыть следующий и снова настроить
  6. Прочитать маркер второй операции
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$db = new mysqli('p:'.$host, $user, $password, $database, $port);
$db->set_charset('utf8mb4');
// Выполнить работу.
$db->close();

Префикс p: просит MySQLi использовать постоянное соединение из пула процесса. Объект mysqli остаётся дескриптором текущей операции. После открытия мы заново выбираем схему и кодировку, задаём собственный маркер. Thread ID сохраняется лишь как наблюдение, не как обещание обязательного повторного использования.

Что должно получиться

ШагПроверкаРезультат
Первое открытие p:utf8mb4закрыт PHP-дескриптор
Второе открытие p:utf8mb4second operation
Схема выбрана явноtrueне полагаемся на остаточное состояние

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Понять, что persistent-соединение не заменяет настройки каждого входаСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$first=legacy108Connect(0,true);
$runtime=legacy108Runtime($first);$firstThread=$first->thread_id;
$first->query("SET @mysql108_request_marker='first request'");
$firstCharset=$first->character_set_name();$first->close();
$second=legacy108Connect(0,true);
try {
    $secondThread=$second->thread_id;
    $selected=$second->query('SELECT DATABASE() AS db')->fetch_assoc()['db'];
    $second->query("SET @mysql108_request_marker='second operation'");
    $marker=$second->query('SELECT @mysql108_request_marker AS marker')->fetch_assoc()['marker'];
    $rows=[['Первое открытие p:',$firstCharset,'закрыт PHP-дескриптор'],['Второе открытие p:',$second->character_set_name(),$marker],['Схема выбрана явно',$selected===getenv('PHPMAN_MYSQL_DATABASE'),'не полагаемся на остаточное состояние']];
    $checks=['both_initialized'=>$firstCharset==='utf8mb4' && $second->character_set_name()==='utf8mb4','schema_explicit'=>$selected===getenv('PHPMAN_MYSQL_DATABASE'),'marker_reinitialized'=>$marker==='second operation'];
    $details=['first_thread'=>$firstThread,'second_thread'=>$secondThread,'same_server_thread_observed'=>$firstThread===$secondThread,'scope'=>'One CLI process; not a cross-request persistence test'];
} finally {$second->close();}
legacy108Report('mysql108-persistent-boundary', ['Шаг', 'Проверка', 'Результат'], $rows, $checks, $runtime, $details);
echo "Persistent handles explicitly initialized twice; no HTTP reuse claim\n";
Ожидаемый результат
Persistent handles explicitly initialized twice; no HTTP reuse claim

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

MySQLi имеет штатную очистку состояния при выдаче постоянного соединения: rollback, временные таблицы, блокировки и session-параметры. Это не причина хранить в пуле пользователя приложения. Проверяется один CLI-процесс; межзапросное поведение FPM/Apache требует отдельного HTTP-стенда. Пул может увеличить число соединений при большом числе рабочих процессов.

Попробовать самостоятельно

Сделайте второй вызов обычным, без persistent. Решение: прикладной результат должен сохраниться, а наблюдаемая идентичность серверного thread не обязана.

Прочитать поток до конца и только потом отправить следующий запрос

Нужно перенести старую последовательную выгрузку. Она не должна запрашивать общее число строк до чтения и запускать новый SQL посередине незавершённого ответа.

Путь данных: Прочитать поток до конца и только потом отправить следующий запрос
  1. SELECT четырёх товаров без полного буфера
  2. Строка1 →обработка, счётчик1
  3. Дочитать до null →счётчик4
  4. free результата
  5. Следующий COUNT(*) →4
Короткая запись современного API: выдержка/схема полного примера; требует уже созданного соединения и таблиц
$result = $db->query($sql, MYSQLI_USE_RESULT);
while ($row = $result->fetch_assoc()) {
    echo $row['name'], "\n";
}
$result->free();

MYSQLI_USE_RESULT означает последовательное чтение без полного клиентского буфера. Каждая fetch_assoc берёт следующую строку; null обозначает конец. После дочитывания и free соединение снова готово для следующей команды. Собственный счётчик увеличивается только при реально прочитанной строке.

Что должно получиться

IDТоварОстаток
1Чай12
2Кофе0
3Какао7
4Мёд4

Полный example.php ниже создаёт свой исходный набор и сохраняет эту таблицу. Вспомогательный файл legacy108-helper.php содержит только настройку соединения и оформление отчёта; он уже включён в скачиваемый архив, это не обязательная часть самого API-вызова.

Полная самостоятельная современная программа
Прочитать поток до конца и только потом отправить следующий запросСамостоятельный пример
<?php
declare(strict_types=1);
require __DIR__.'/legacy108-helper.php';
$details=[];
$db=legacy108Connect();legacy108Catalog($db);
try {
    $result=$db->query('SELECT id,name,stock FROM mysql108_legacy_products ORDER BY id',MYSQLI_USE_RESULT);
    $rows=[];$ids=[];$count=0;
    while (($r=$result->fetch_assoc())!==null) {
        if ($r===false) {throw new RuntimeException('Fetch failed');}
        $ids[]=(int)$r['id'];$rows[]=[(int)$r['id'],$r['name'],(int)$r['stock']];$count++;
    }
    $result->free();
    $serverCount=(int)$db->query('SELECT COUNT(*) AS total FROM mysql108_legacy_products')->fetch_assoc()['total'];
    $checks=['stream_in_order'=>$ids===[1,2,3,4],'counted_four'=>$count===4,'next_query_after_drain'=>$serverCount===4];
    $details=['stream_rows'=>$count,'count_query_after_free'=>$serverCount,'bounded_teaching_report'=>'Four rows kept for the visible result; not a memory benchmark'];$runtime=legacy108Runtime($db);
} finally {$db->close();}
legacy108Report('mysql108-unbuffered-export', ['ID', 'Товар', 'Остаток'], $rows, $checks, $runtime, $details);
echo "Four streamed rows read; next query succeeded after drain\n";
Ожидаемый результат
Four streamed rows read; next query succeeded after drain

Запуск: php example.php после локальной подготовки. Это MySQLi на PHP8; ни один mysql_* вызов здесь не исполняется.

Открыть сохранённый HTML-отчёт · Данные результата JSON

Границы и частые ошибки

В этом учебном отчёте четыре строки сохранены в массив для показа. Это не потоковая обработка миллионов записей и не сравнение памяти. В рабочем экспортёре пишите очередную строку сразу в выходной файл. Произвольный data_seek несовместим с потоком; раннее num_rows0 не доказывает пустоту.

Попробовать самостоятельно

Ограничьте выборку WHERE stock>0. Решение: счётчик3; для честного сравнения COUNT используйте то же условие.

Полный указатель48 исторических функций

Каждая ссылка ведёт к отдельному историческому контракту с современной заменой и практическими сценариями. Это указатель исходного ext/mysql; актуальный API MySQLi находится в своём справочнике. Соседние главы продолжают метаданные и перечни объектов и диагностику и администрирование.

ФункцияНазначение
mysql_affected_rowsНе принять совпавшие строки за изменённые
mysql_client_encodingУзнать кодировку старого соединения
mysql_closeЗакрыть link, а не набор строк
mysql_connectОткрыть или повторно получить старое подключение
mysql_create_dbДиагностика соединения и административные операции
mysql_data_seekПередвинуть курсор уже полученного буфера
mysql_db_nameМетаданные и перечни объектов
mysql_db_queryНайти скрытое изменение выбранной базы
mysql_drop_dbДиагностика соединения и административные операции
mysql_errnoСохранить код последней ошибки до следующей операции
mysql_errorСохранить текст диагностического отказа отдельно от ответа человеку
mysql_escape_stringУзнать ограничение экранирования без соединения
mysql_fetch_arrayВыбрать ассоциативные и числовые ключи строки
mysql_fetch_assocПрочитать строку с понятными именами полей
mysql_fetch_fieldМетаданные и перечни объектов
mysql_fetch_lengthsИзмерить значения последней прочитанной строки
mysql_fetch_objectПонять превращение строки в свойства объекта
mysql_fetch_rowПрочитать строку по порядку SELECT-полей
mysql_field_flagsМетаданные и перечни объектов
mysql_field_lenМетаданные и перечни объектов
mysql_field_nameМетаданные и перечни объектов
mysql_field_seekМетаданные и перечни объектов
mysql_field_tableМетаданные и перечни объектов
mysql_field_typeМетаданные и перечни объектов
mysql_free_resultОсвободить память результата, сохранив соединение
mysql_get_client_infoДиагностика соединения и административные операции
mysql_get_host_infoДиагностика соединения и административные операции
mysql_get_proto_infoДиагностика соединения и административные операции
mysql_get_server_infoДиагностика соединения и административные операции
mysql_infoПрочитать пояснение записи, не считать его таблицей результата
mysql_insert_idСохранить созданный AUTO_INCREMENT сразу после вставки
mysql_list_dbsМетаданные и перечни объектов
mysql_list_fieldsМетаданные и перечни объектов
mysql_list_processesДиагностика соединения и административные операции
mysql_list_tablesМетаданные и перечни объектов
mysql_num_fieldsПосчитать выбранные колонки даже у пустого набора
mysql_num_rowsПосчитать строки результата, а не строки таблицы вообще
mysql_pconnectРаспознать постоянное соединение в старом проекте
mysql_pingДиагностика соединения и административные операции
mysql_queryОтделить SQL-команду от полученного набора
mysql_real_escape_stringРазобрать старое экранирование с кодировкой link
mysql_resultЗаменить обращение к одной ячейке без потери смысла
mysql_select_dbЯвно определить базу для последующих запросов
mysql_set_charsetСогласовать кодировку клиента и сервера
mysql_statДиагностика соединения и административные операции
mysql_tablenameМетаданные и перечни объектов
mysql_thread_idДиагностика соединения и административные операции
mysql_unbuffered_queryПеренести последовательное чтение большого набора

Обзорный сценарий: от старого HTML-каталога к современному

Базовая задача старого обзорного примера — соединиться, выбрать базу, выполнить SELECT, перебрать строки, показать HTML, освободить результат и закрыть link. Современный перенос сохраняет этот путь, но меняет явное соединение, обработку ошибок и экранирование текста. Мы выбираем нужные поля вместо SELECT * и не печатаем сырой SQL-ответ в HTML.

Все архивы самостоятельны: содержат подготовку исходных строк и полный example.php. Сохранённые HTML/JSON доступны у каждого сценария. Исторические фрагменты в карточках помечены отдельно и не выдаются за выполненные на PHP8.

Функции и методы этой темы

Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.

mysql_connectОткрыть или повторно получить старое подключение

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_connect(string $server=ini_get("mysql.default_host"), string $username=ini_get("mysql.default_user"), string $password=ini_get("mysql.default_password"), bool $new_link=false, int $client_flags=0): resource|false

Открыть или повторно получить старое подключение

ПараметрЧто передавали
serverИмя сервера; допускались host:port или localhost с путём сокета. При отсутствии default_host — localhost:3306.
username / passwordУчётные данные; значения по умолчанию из mysql.default_user/password.
new_linkfalse мог вернуть уже открытый link с теми же аргументами; true требовал новый.
client_flagsБитовая комбинация клиентских констант; историческое128 разрешало LOCAL INFILE.

Возврат и отказ старого API

Ресурс соединения при успехе, false при отказе. Это ещё не выбранная таблица и не результат SELECT.

Ограничения и поведение

В старом SQL safe mode server/username/password/new_link/flags имели специальные ограничения: localhost:3306, владелец процесса, пустой пароль; остальные настройки игнорировались. Это историческое поведение, не режим PHP8.

Историческая запись mysql_connect — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$link = mysql_connect($host, $user, $password);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

mysqli_connect($host,$user,$password,$schema,$port) возвращает mysqli; объект передаётся первым аргументом процедурных вызовов. Не переносите ожидание автоматического повторного link.

Подробный современный контракт: MySQLi: Открыть или повторно получить старое подключение. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_pconnectРаспознать постоянное соединение в старом проекте

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_pconnect(string $server=ini_get("mysql.default_host"), string $username=ini_get("mysql.default_user"), string $password=ini_get("mysql.default_password"), int $client_flags=0): resource|false

Распознать постоянное соединение в старом проекте

ПараметрЧто передавали
serverСервер, порт или сокет, как у connect.
username / passwordПользователь/пароль; историческая документация описывает пользователя процесса и пустой пароль как резервные значения.
client_flagsКомбинация клиентских флагов; new_link у этой функции нет.

Возврат и отказ старого API

Постоянный link либо false. Сначала искалось подходящее уже открытое соединение по серверу, пользователю и паролю.

Ограничения и поведение

Завершение скрипта и mysql_close не закрывали постоянный link. Исторический механизм имел смысл в модульном серверном PHP; лимиты процессов/соединений требовали настройки. Это не общий пул между всеми процессами.

Историческая запись mysql_pconnect — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$link = mysql_pconnect($host, $user, $password);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

В MySQLi постоянство запрашивается префиксом p: хоста. Явно задавайте schema/charset и проверяйте жизненный цикл своего SAPI; наш CLI-пример не доказывает повторное использование между HTTP-запросами.

Подробный современный контракт: MySQLi: Распознать постоянное соединение в старом проекте. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_closeЗакрыть link, а не набор строк

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_close(resource $link_identifier=NULL): bool

Закрыть link, а не набор строк

ПараметрЧто передавали
link_identifierКонкретный непостоянный link; без параметра — последний открытый. При отсутствии соединения — E_WARNING.

Возврат и отказ старого API

true при успешном закрытии, false при неудаче.

Ограничения и поведение

Постоянные соединения pconnect эта функция не закрывала. Обычные links и результаты освобождались при завершении скрипта; явное закрытие завершает владение ресурсом раньше.

Историческая запись mysql_close — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
mysql_close($link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$db->close() закрывает PHP-дескриптор соединения; $result->free() освобождает отдельный результат. В PHP8 mysqli_close всегда возвращает true; отказ последующей работы с закрытым объектом нельзя отследить старым ожиданием false от close.

Подробный современный контракт: MySQLi: Закрыть link, а не набор строк. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_select_dbЯвно определить базу для последующих запросов

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_select_db(string $database_name, resource $link_identifier=NULL): bool

Явно определить базу для последующих запросов

ПараметрЧто передавали
database_nameИмя существующей доступной схемы; не SQL и не путь к файлу.
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

true при выборе, false при отказе: например, схема отсутствует или нет прав.

Ограничения и поведение

Выбор изменял состояние соединения для последующих запросов. Он не создавал базу и не переключал другие links.

Историческая запись mysql_select_db — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
mysql_select_db($database, $link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

mysqli_select_db($db,$schema) меняет порядок аргументов относительно старого вызова. Либо укажите schema при подключении. Имя схемы нельзя подставлять вместо значения в ?.

Подробный современный контракт: MySQLi: Явно определить базу для последующих запросов. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_client_encodingУзнать кодировку старого соединения

PHP4.3.0–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_client_encoding(resource $link_identifier=NULL): string

Узнать кодировку старого соединения

ПараметрЧто передавали
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Название кодировки текущего соединения, например utf8; вызов сам ничего не перекодировал.

Ограничения и поведение

Кодировка соединения не равна автоматически кодировке PHP-файла, HTML-документа или столбца. Проверка имени не доказывает целостность всех уже сохранённых данных.

Историческая запись mysql_client_encoding — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$charset = mysql_client_encoding($link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$db->character_set_name(); для изменения — $db->set_charset("utf8mb4"). Обратное чтение русского имени и эмодзи проверяет реальный путь данных.

Подробный современный контракт: MySQLi: Узнать кодировку старого соединения. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_set_charsetСогласовать кодировку клиента и сервера

PHP5.2.3–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_set_charset(string $charset, resource $link_identifier=NULL): bool

Согласовать кодировку клиента и сервера

ПараметрЧто передавали
charsetПоддерживаемое сервером название кодировки.
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

true при установке, false при отказе.

Ограничения и поведение

Появилась в PHP5.2.3; требовала MySQL5.0.7+. Это рекомендуемый исторический API вместо ручного SET NAMES: библиотека экранирования должна знать charset.

Историческая запись mysql_set_charset — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
mysql_set_charset("utf8", $link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$db->set_charset("utf8mb4") сразу после соединения. utf8mb4 поддерживает четырёхбайтовые символы; перенос настройки не исправляет ранее повреждённые строки.

Подробный современный контракт: MySQLi: Согласовать кодировку клиента и сервера. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_escape_stringУзнать ограничение экранирования без соединения

PHP4.0.3–5.x; deprecated4.3.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_escape_string(string $unescaped_string): string

Узнать ограничение экранирования без соединения

ПараметрЧто передавали
unescaped_stringИсходные байты строки; аргумента подключения нет.

Возврат и отказ старого API

Строка с экранированными специальными байтами; внешние SQL-кавычки не добавляются.

Ограничения и поведение

Не учитывала текущую кодировку соединения. Не экранировала % и _ в LIKE. Устарела уже в PHP4.3.0, раньше всего расширения. Не защищает имена таблиц и произвольный SQL.

Историческая запись mysql_escape_string — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$escaped = mysql_escape_string($name);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

Для значения предпочитайте подготовленный запрос. Когда разбираете старую склейку, MySQLi real_escape_string требует правильного charset и своего контекста SQL; универсальной «очистки ввода» нет.

Подробный современный контракт: MySQLi: Узнать ограничение экранирования без соединения. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_real_escape_stringРазобрать старое экранирование с кодировкой link

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_real_escape_string(string $unescaped_string, resource $link_identifier=NULL): string|false

Разобрать старое экранирование с кодировкой link

ПараметрЧто передавали
unescaped_stringСтрока для одного SQL-литерала, не целая команда.
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Экранированная строка, false при ошибке. Без действующего подключения также E_WARNING.

Ограничения и поведение

Учитывала charset соединения; исторически обрабатывала NUL, перевод строки, CR, обратный слеш, обе кавычки, Ctrl-Z. Не добавляла внешние кавычки и не отменяла LIKE %/_. Charset задавался сервером или set_charset, не угадывался.

Историческая запись mysql_real_escape_string — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$escaped = mysql_real_escape_string($name, $link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

Подготовленные параметры — основной вариант. Современное $db->real_escape_string($text) в примере используется только внутри одинарного SQL-литерала с явно заданным utf8mb4; влияние sql_mode не следует игнорировать.

Подробный современный контракт: MySQLi: Разобрать старое экранирование с кодировкой link. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_queryОтделить SQL-команду от полученного набора

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_query(string $query, resource $link_identifier=NULL): resource|bool

Отделить SQL-команду от полученного набора

ПараметрЧто передавали
queryОдна SQL-команда, не пакет statements. Исторический справочник просил не завершать её точкой с запятой.
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Для SELECT/SHOW/DESCRIBE/EXPLAIN — resource; для INSERT/UPDATE/DELETE — true. При отказе, в том числе недостатке прав, false.

Ограничения и поведение

Пустой SELECT остаётся успешным ресурсом. Данные надо извлечь fetch-функцией; resource не содержит готовый HTML. num_rows относится к результату, affected_rows — к записи.

Историческая запись mysql_query — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$result = mysql_query($sql, $link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

mysqli_query($db,$sql) или $db->query($sql). В PHP8.1+ стандартный строгий режим MySQLi выбрасывает исключение при ошибке; задаём его явно. Пользовательские значения — в prepared.

Подробный современный контракт: MySQLi: Отделить SQL-команду от полученного набора. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_db_queryНайти скрытое изменение выбранной базы

PHP4/5; deprecated5.3.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_db_query(string $database, string $query, resource $link_identifier=NULL): resource|bool

Найти скрытое изменение выбранной базы

ПараметрЧто передавали
databaseСхема, которая становилась выбранной.
queryОдна SQL-команда.
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Ресурс для выборки, true для успешного изменения, false при отказе.

Ограничения и поведение

Функция НЕ восстанавливала предыдущую схему после запроса. Устарела в PHP5.3.0. Это опасное для сопровождения неявное изменение контекста.

Историческая запись mysql_db_query — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$result = mysql_db_query($database, $sql, $link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

Выбирать schema явно при соединении/через select_db; затем query. Для нескольких схем допустима фиксированная запись schema.table при соответствующих правах. Здесь пример не запрашивает доступ к чужим схемам.

Подробный современный контракт: MySQLi: Найти скрытое изменение выбранной базы. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_unbuffered_queryПеренести последовательное чтение большого набора

PHP4.0.6–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_unbuffered_query(string $query, resource $link_identifier=NULL): resource|bool

Перенести последовательное чтение большого набора

ПараметрЧто передавали
queryОдна SQL-команда; строки ответа заранее целиком не буферизуются.
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Ресурс для выборки, true для успешной команды без набора, false при ошибке.

Ограничения и поведение

До полного чтения нельзя достоверно получить общее num_rows; data_seek не подходит для этого потока. Следующую команду тем же link отправляли только после чтения оставшихся строк. Это сокращает клиентский буфер, но не отменяет работу сервера.

Историческая запись mysql_unbuffered_query — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$result = mysql_unbuffered_query($sql, $link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$db->query($sql,MYSQLI_USE_RESULT), последовательный fetch, затем free. Не превращайте поток обратно в огромный PHP-массив ради одной выгрузки; наша таблица всего из четырёх строк служит видимому учебному результату.

Подробный современный контракт: MySQLi: Перенести последовательное чтение большого набора. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_errnoСохранить код последней ошибки до следующей операции

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_errno(resource $link_identifier=NULL): int

Сохранить код последней ошибки до следующей операции

ПараметрЧто передавали
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Числовой код последней ошибки либо 0, если её нет.

Ограничения и поведение

Проверять сразу после неуспешной операции: следующий вызов MySQL мог изменить диагностическое состояние. error/errno сами не затирали его. Серверный отказ не всегда становился PHP warning.

Историческая запись mysql_errno — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$code = mysql_errno($link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

В строгом MySQLi catch(mysqli_sql_exception $e) и $e->getCode(); в нестрогом — $db->errno. Код для классификации, закрытый журнал для подробностей; не присваивайте всем отказам смысл «нет строк».

Подробный современный контракт: MySQLi: Сохранить код последней ошибки до следующей операции. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_errorСохранить текст диагностического отказа отдельно от ответа человеку

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_error(resource $link_identifier=NULL): string

Сохранить текст диагностического отказа отдельно от ответа человеку

ПараметрЧто передавали
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Текст последней ошибки, пустая строка при её отсутствии.

Ограничения и поведение

Сообщение относится к последней операции; прочитать до следующего MySQL-вызова. Оно может раскрыть имена схем, таблиц и детали сервера, поэтому старое die(mysql_error()) не переносим на публичную страницу.

Историческая запись mysql_error — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$message = mysql_error($link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

В исключении $e->getMessage(); детали — в защищённый журнал. В результате сценария публикуется классификация и код, а не SQL с учётными данными. Для ошибок соединения есть отдельная диагностика connect.

Подробный современный контракт: MySQLi: Сохранить текст диагностического отказа отдельно от ответа человеку. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_affected_rowsНе принять совпавшие строки за изменённые

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_affected_rows(resource $link_identifier=NULL): int

Не принять совпавшие строки за изменённые

ПараметрЧто передавали
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Число затронутых строк последней INSERT/UPDATE/REPLACE/DELETE; -1 после неудачного запроса.

Ограничения и поведение

Обычный UPDATE считал реально изменённые строки; прежнее значение даёт0. REPLACE суммировал удаление и вставку; UPSERT различал вставку1/обновление2. Считать до COMMIT; каскадные изменения FK не включаются. До MySQL4.1.2 DELETE без WHERE мог возвращать0.

Историческая запись mysql_affected_rows — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$changed = mysql_affected_rows($link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$db->affected_rows сразу после записи. Для SELECT используйте num_rows, а не этот счётчик. FOUND_ROWS меняет договор UPDATE; он отдельно разобран в главе редактирования.

Подробный современный контракт: MySQLi: Не принять совпавшие строки за изменённые. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_insert_idСохранить созданный AUTO_INCREMENT сразу после вставки

PHP4/5; deprecated PHP5.5.0; removed PHP7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_insert_id(resource $link_identifier=NULL): int|false

Сохранить созданный AUTO_INCREMENT сразу после вставки

ПараметрЧто передавали
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Выданный AUTO_INCREMENT последней операции; 0, когда значение не выдавалось; false без подключения.

Ограничения и поведение

Прочитать немедленно после INSERT тем же link. Исторический PHP int мог не вместить BIGINT; SQL LAST_INSERT_ID() позволял получить значение без такого приведения и сохранял последнее выданное значение между запросами.

Историческая запись mysql_insert_id — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$id = mysql_insert_id($link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$db->insert_id; современный контракт допускает int|string для больших значений. Не использовать SELECT MAX(id): он не принадлежит конкретной вставке. Для нескольких вставленных строк API возвращает первый автоматически выданный ID.

Подробный современный контракт: MySQLi: Сохранить созданный AUTO_INCREMENT сразу после вставки. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

mysql_infoПрочитать пояснение записи, не считать его таблицей результата

PHP4.3.0–5.x; deprecated5.5.0; removed7.0.0 · ext/mysql (удалено); замена MySQLi

mysql_info(resource $link_identifier=NULL): string|false

Прочитать пояснение записи, не считать его таблицей результата

ПараметрЧто передавали
link_identifierНеобязательный link_identifier — ресурс подключения. Без него использовалось последнее открытое соединение; большинство функций пыталось открыть соединение с настройками php.ini, если его не было. Неудача давала E_WARNING/false по контракту операции. В новом коде соединение передаётся явно.

Возврат и отказ старого API

Информационная строка для поддерживаемой команды; false, когда информации нет или операция не подходит.

Ограничения и поведение

Исторические формы: многорядная INSERT VALUES / INSERT SELECT / LOAD DATA — records/duplicates/warnings; ALTER — records/duplicates/warnings; UPDATE — matched/changed/warnings. Для одиночной INSERT VALUES информация не обещалась.

Историческая запись mysql_info — удалена в PHP7, не выполняется на PHP8Фрагмент или команда
$information = mysql_info($link);

Фрагмент для чтения старого проекта; исходные $link/$result/$sql должны были создаваться старым API. Это не проверенный PHP8-пример.

Современная замена и два практических случая

$db->info может вернуть строку или null. Сохраняем для диагностики сразу после команды, но решение приложения строим на affected_rows, ID и контрольном чтении, не на парсинге локализованного текста.

Подробный современный контракт: MySQLi: Прочитать пояснение записи, не считать его таблицей результата. Современные соединения и результаты нельзя передавать старым mysql_* функциям.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.