Files
php-doc-ru/reference/mysqli/quickstart.xml
Mikhail Alferov b55a97eb6d Обновление перевода (#635)
Co-authored-by: Sergey Panteleev <sergey@php.net>
2023-12-31 18:01:43 +03:00

1657 lines
70 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="utf-8"?>
<!-- EN-Revision: 60a292997b3c6341cf52099d901aa0b5f8673d87 Maintainer: tmn Status: ready -->
<!-- Reviewed: no -->
<chapter xml:id="mysqli.quickstart" xmlns="http://docbook.org/ns/docbook" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Краткое руководство</title>
<para>
Это практическое руководство помогает освоить основные моменты API-интерфейса
MySQL-сервера.
</para>
<para>
В этом руководстве приводится краткий обзор модуля mysqli. Для всех основных
особенностей API-интерфейса приведены примеры PHP-кода. Также объясняются
принципы работы баз данных в целом, чтобы иметь возможность описать какие-то
отдельные моменты, присущие конкретно MySQL.
</para>
<para>
Требования: Знакомство с языком программирования PHP, SQL, а также знание основ
работы с MySQL-сервером.
</para>
<section xml:id="mysqli.quickstart.dual-interface">
<title>Процедурный и объектно-ориентированный интерфейс</title>
<para>
Модуль mysqli предоставляет двойной интерфейс программисту. Поддерживаются
как процедурная, так и объектно-ориентированная парадигмы программирования.
</para>
<para>
Пользователи, переходящие со старого модуля mysql, возможно, предпочтут
процедурный интерфейс. Он весьма схож с интерфейсом старого модуля, и во
многих случаях функции отличаются только префиксом в имени. Некоторые
mysqli-функции принимают дескриптор соединения первым аргументом, в отличие от
соответствующих им функций старого модуля, которые принимают его в качестве
последнего необязательного аргумента.
</para>
<para>
<example>
<title>Простота перехода со старого модуля mysql</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_connect("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'Пожалуйста, не используйте устаревший модуль mysql в новых проектах.' AS _msg FROM DUAL");
$row = mysqli_fetch_assoc($result);
echo $row['_msg'];
$mysql = mysql_connect("example.com", "user", "password");
mysql_select_db("test");
$result = mysql_query("SELECT 'Используйте вместо него модуль mysqli.' AS _msg FROM DUAL", $mysql);
$row = mysql_fetch_assoc($result);
echo $row['_msg'];
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Пожалуйста, не используйте устаревший модуль mysql в новых проектах. Используйте вместо него модуль mysqli.
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Объектно-ориентированный интерфейс</emphasis>
</para>
<para>
В дополнение к процедурному пользователи могут использовать
объектно-ориентированный интерфейс. Документация заточена именно под
объектный интерфейс. Объектно-ориентированный интерфейс предлагает функции
сгруппированные по цели их применения, что облегчает их поиск и освоение. Тем
не менее, в практических примерах к функциям приводится код для обеих парадигм.
</para>
<para>
Каких-либо принципиальных отличий в производительности между интерфейсами нет.
Пользователи вольны в выборе интерфейса, основываясь на личных предпочтениях.
</para>
<para>
<example>
<title>Объектно-ориентированный и процедурный интерфейсы</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = mysqli_connect("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'Мир, полный ' AS _msg FROM DUAL");
$row = mysqli_fetch_assoc($result);
echo $row['_msg'];
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = $mysqli->query("SELECT 'выбора, чтобы угодить всем.' AS _msg FROM DUAL");
$row = $result->fetch_assoc();
echo $row['_msg'];
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Мир, полный выбора, чтобы угодить всем.
]]>
</screen>
</example>
</para>
<para>
Примеры в этом руководстве будут написаны в объектном стиле в виду того, что
объектному подходу отдавалось предпочтение при создании документации.
</para>
<para>
<emphasis role="bold">Смешивание стилей</emphasis>
</para>
<para>
Переключаться между стилями программирования можно сколь угодно часто и в любое
время, однако делать этого не рекомендуется, так как это ухудшает читаемость
кода и затрудняет его поддержку.
</para>
<para>
<example>
<title>Плохой стиль программирования</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = mysqli_query($mysqli, "SELECT 'Этот код работает, но лучше так не писать.' AS _msg FROM DUAL");
if ($row = $result->fetch_assoc()) {
echo $row['_msg'];
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Этот код работает, но лучше так не писать.
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Смотрите также</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli_result::fetch_assoc</methodname></member>
<member><link linkend="mysqli.connect-errno">$mysqli::connect_errno</link></member>
<member><link linkend="mysqli.connect-error">$mysqli::connect_error</link></member>
<member><link linkend="mysqli.errno">$mysqli::errno</link></member>
<member><link linkend="mysqli.error">$mysqli::error</link></member>
<member><link linkend="mysqli.summary">Общее описание функций модуля MySQLi</link></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.connections">
<title>Соединения</title>
<para>
Сервер MySQL поддерживает различные способы передачи данных. Соединения могут
использовать протоколы TCP/IP, сокеты Unix-доменов или именованные пайпы
Windows.
</para>
<para>
Имя хоста <literal>localhost</literal> имеет особое значение.
Оно используется только в сокетах Unix доменов.
Чтобы открыть TCP/IP-соединение с локальным хостом, необходимо использовать <literal>127.0.0.1</literal> вместо имени хоста <literal>localhost</literal>.
</para>
<para>
<example>
<title>Специальное назначение localhost</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("localhost", "user", "password", "database");
echo $mysqli->host_info . "\n";
$mysqli = new mysqli("127.0.0.1", "user", "password", "database", 3306);
echo $mysqli->host_info . "\n";
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Localhost via UNIX socket
127.0.0.1 via TCP/IP
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Умолчания для параметров соединений</emphasis>
</para>
<para>
В зависимости от функции, осуществляющей подключение, какие-то параметры можно
не задавать. Если параметр не задан, модуль попытается использовать
значение по умолчанию для этого параметра, которое задано в конфигурационном
файле PHP.
</para>
<para>
<example>
<title>Задание значений по умолчанию</title>
<programlisting role="ini">
<![CDATA[
mysqli.default_host=192.168.2.27
mysqli.default_user=root
mysqli.default_pw=""
mysqli.default_port=3306
mysqli.default_socket=/tmp/mysql.sock
]]>
</programlisting>
</example>
</para>
<para>
Далее, чтобы установить соединение, функция передаёт параметры в клиентскую
библиотеку, которой пользуется модуль. Если библиотека обнаружит пустые или
отсутствующие параметры, она может подставить вместо них свои встроенные
значения по умолчанию.
</para>
<para>
<emphasis role="bold">
Встроенные библиотечные значения по умолчанию для параметров соединения
</emphasis>
</para>
<para>
Если имя хоста не задано или передана пустая строка, клиентская библиотека
использует для подключения к Unix-сокету хоста <literal>localhost</literal>.
Если сокет не задан или передана пустая строка, и при этом запрошено
подключение к Unix-сокету, библиотека попытается подключиться к сокету
<literal>/tmp/mysql.sock</literal>.
</para>
<para>
В Windows-системах, если в качестве имени хоста передаётся <literal>.</literal>,
библиотека попытается открыть соединение на основе именованного пайпа. В этом
случае имя сокета будет воспринято как имя пайпа. Если имя сокета не задано, то
будет использовано значение <literal>\\.\pipe\MySQL</literal>.
</para>
<para>
Если соединение не использует ни сокет Unix-домена, ни именованный пайп Windows,
и при этом не задан порт для подключения, библиотека использует номер порта
<literal>3306</literal>.
</para>
<para>
В драйвере <link linkend="mysqlnd.overview">mysqlnd</link> и клиентской
библиотеке MySQL (libmysqlclient) заложена та же логика определения умолчаний.
</para>
<para>
<emphasis role="bold">Настройки соединения</emphasis>
</para>
<para>
Настройки соединения позволяют, например, задать какие-то команды, которые
нужно выполнить сразу после подключения, или отдать распоряжение использовать
определённый набор символов. Настройки должны быть заданы до подключения к
серверу.
</para>
<para>
Когда требуется задать настройки соединения, операция подключения выполняется
в три этапа: функцией <function>mysqli_init</function> или <methodname>mysqli::__construct</methodname> создаётся дескриптор
подключения, затем подключение настраивается с помощью функции
<methodname>mysqli::options</methodname>, и наконец устанавливается сетевое
соединение с сервером посредством функции
<methodname>mysqli::real_connect</methodname>.
</para>
<para>
<emphasis role="bold">Объединение подключений в пул</emphasis>
</para>
<para>
Модуль mysqli поддерживает постоянные соединения с базой данных, которые
представляют из себя специальный вид объединяемых соединений. По умолчанию
каждое открытое скриптом соединение закрывается либо самим скриптом в ходе
выполнения, либо автоматически по завершении работы скрипта. Постоянные
соединения отличаются тем, что не закрываются, а помещаются в пул для
повторного использования в дальнейшем. Если требуется подключиться к тому
же серверу и базе данных, с тем же именем пользователя, паролем, сокетом и
портом, то вместо создания нового подключения из пула извлекается уже
существующее. Повторное использование подключений позволяет избежать
накладных расходов на создание новых соединений.
</para>
<para>
Каждый PHP-процесс использует свой пул подключений mysqli. В зависимости от
конфигурации веб-сервера, PHP-процесс может обслуживать один или несколько
запросов. Соответственно, соединение из пула могут последовательно использовать
несколько скриптов.
</para>
<para>
<emphasis role="bold">Постоянное соединение</emphasis>
</para>
<para>
Новое подключение создаётся, только если в пуле не найдётся свободного
подключения с теми же данными хоста, имени пользователя, пароля, сокета, порта
и базы данных по умолчанию. Механизм постоянных соединений можно включать
и выключать PHP директивой <link linkend="ini.mysqli.allow-persistent">mysqli.allow_persistent</link>.
Максимальное количество соединений, которые может открыть скрипт, ограничено
значением <link linkend="ini.mysqli.max-links">mysqli.max_links</link>.
Максимальное количество соединений, которые может открыть один PHP-процесс,
ограничено значением <link linkend="ini.mysqli.max-persistent">mysqli.max_persistent</link>. Следует
заметить, что веб-сервер может порождать множество PHP процессов.
</para>
<para>
Главный недостаток постоянных подключений заключается в том, что перед
повторным использованием их состояние не сбрасывается к изначальному. Например,
открытые и незавершённые транзакции не будут автоматически откатываться. Также,
если во время нахождения соединения в пуле для процесса изменились
какие-либо разрешения или уровни доступа, этот факт никак не отразится на
подключении при его извлечении из пула. Такое поведение может привести к
нежелательным результатам. Хотя, с другой стороны, название
<literal>постоянный</literal> можно рассматривать, как обещание, что подключение
и правда останется в том состоянии, в котором оно было помещено в пул.
</para>
<para>
Модуль mysqli поддерживает обе интерпретации термина постоянное соединение:
состояние соединения может сохраняться, а может и сбрасываться в изначальное.
По умолчанию при извлечении из пула, соединение сбрасывается. mysqli делает
это неявным вызовом функции <methodname>mysqli::change_user</methodname> каждый раз,
когда подключение используется повторно. С точки зрения пользователя подключение
выглядит, как только что созданное.
</para>
<para>
Однако, вызов функции <methodname>mysqli::change_user</methodname> довольно
дорогостоящая операция. Для улучшения быстродействия можно перекомпилировать
модуль с установленным флагом
<constant>MYSQLI_NO_CHANGE_USER_ON_PCONNECT</constant>.
</para>
<para>
Выбор между безопасным поведением подключений и наилучшим быстродействием
остаётся за пользователем. Здесь нельзя дать однозначного совета. Для простоты
использования, по умолчанию включён безопасный режим с очисткой соединений.
</para>
<para>
<emphasis role="bold">Смотрите также</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><function>mysqli_init</function></member>
<member><methodname>mysqli::options</methodname></member>
<member><methodname>mysqli::real_connect</methodname></member>
<member><methodname>mysqli::change_user</methodname></member>
<member><link linkend="mysqli.get-host-info">$mysqli::host_info</link></member>
<member><link linkend="mysqli.configuration">MySQLi Configuration Options</link></member>
<member><link linkend="features.persistent-connections">Persistent Database Connections</link></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.statements">
<title>Выполнение запросов</title>
<para>
За выполнение запросов отвечают функции
<methodname>mysqli::query</methodname>, <methodname>mysqli::real_query</methodname>
и <methodname>mysqli::multi_query</methodname>. Чаще всего
применяется функция <methodname>mysqli::query</methodname>, так как она выполняет
сразу две задачи: выполняет запрос и буферизует на клиенте результат этого
запроса (если он есть). Вызов <methodname>mysqli::query</methodname> идентичен
последовательному вызову функций <methodname>mysqli::real_query</methodname> и
<methodname>mysqli::store_result</methodname>.
</para>
<para>
<example>
<title>Выполнение запросов</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Буферизация результатов запроса</emphasis>
</para>
<para>
После выполнения запроса результаты можно либо получить сразу,
либо считать строку за строкой с сервера. Буферизация набора результатов
на стороне клиента позволяет серверу как можно раньше высвободить ресурсы, связанные с результатами запроса.
Проще говоря, клиенты медленно используют наборы результатов.
Поэтому рекомендуется использовать буферизованные наборы результатов. <methodname>mysqli::query</methodname>
объединяет выполнение запроса и буферизацию набора результатов.
</para>
<para>
PHP-приложения могут свободно оперировать данными внутри буферизованных
результирующих наборов. Быстрая навигация по строкам наборов обусловлена тем,
что наборы полностью располагаются в памяти клиента. Следует помнить, что
зачастую обработка результатов на клиенте проще, нежели средствами сервера.
</para>
<para>
<example>
<title>Навигация по строкам буферизованной результирующей таблицы</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$result = $mysqli->query("SELECT id FROM test ORDER BY id ASC");
echo "Обратный порядок...\n";
for ($row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) {
$result->data_seek($row_no);
$row = $result->fetch_assoc();
echo " id = " . $row['id'] . "\n";
}
echo "Исходный порядок строк...\n";
foreach ($result as $row) {
echo " id = " . $row['id'] . "\n";
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Обратный порядок...
id = 3
id = 2
id = 1
Исходный порядок строк...
id = 1
id = 2
id = 3
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Небуферизованные результирующие наборы</emphasis>
</para>
<para>
Если клиентские ресурсы ограничены, и в тоже время не требуется поддерживать
низкую нагрузку на сервер, можно использовать не буферизованные результирующие
наборы. Навигация по таким таблицам невозможна, потому что так или иначе должны
быть обработаны все строки набора.
</para>
<para>
<example>
<title>Навигация по строкам небуферизованной результирующей таблицы</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli->real_query("SELECT id FROM test ORDER BY id ASC");
$result = $mysqli->use_result();
echo "Порядок строк в результирующем наборе...\n";
foreach ($result as $row) {
echo " id = " . $row['id'] . "\n";
}
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Типы данных значений в результирующей таблице</emphasis>
</para>
<para>
Функции <methodname>mysqli::query</methodname>,
<methodname>mysqli::real_query</methodname> и <methodname>mysqli::multi_query</methodname>
предназначены для выполнения не подготавливаемых запросов. На уровне протокола
клиент-серверного взаимодействия MySQL за выполнение запросов отвечают команда
<literal>COM_QUERY</literal> и текстовый протокол. Когда используется текстовый
протокол, сервер MySQL перед отправкой клиенту преобразовывает все данные в
результирующем наборе в текстовые строки. Это преобразование выполняется вне
зависимости от типа данных SQL-столбца результирующей таблицы. Клиентские
библиотеки mysql, в свою очередь, получают все данные, принимая их за строки.
На клиенте не проводится никакого обратного преобразования к исходным типам, все
данные, полученные приложением остаются PHP строками.
</para>
<para>
<example>
<title>Текстовый протокол по умолчанию возвращает строки</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label CHAR(1))");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'a')");
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (string)
label = a (string)
]]>
</screen>
</example>
</para>
<para>
Если используется библиотека mysqlnd, можно включить преобразование
целочисленных значений и чисел с плавающей точкой из столбцов таблицы в
PHP числа. Делается это заданием настройки подключения
<constant>MYSQLI_OPT_INT_AND_FLOAT_NATIVE</constant>. В таком случае mysqlnd
будет проверять метаданные столбцов и преобразовывать SQL-числа этих полей в
PHP-числа, если эти значения не выходят за рамки допустимых диапазонов
типов данных PHP. То есть, например, SQL INT число попадёт в PHP
приложение в виде целого (integer).
</para>
<para>
<example>
<title>Получение исходных типов данных в приложении</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli();
$mysqli->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, 1);
$mysqli->real_connect("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label CHAR(1))");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'a')");
$result = $mysqli->query("SELECT id, label FROM test WHERE id = 1");
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer)
label = a (string)
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Смотрите также</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::options</methodname></member>
<member><methodname>mysqli::real_connect</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::use_result</methodname></member>
<member><methodname>mysqli::store_result</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.prepared-statements">
<title>Подготавливаемые запросы</title>
<para>
СУБД MySQL поддерживает подготавливаемые запросы. Подготавливаемые (или
параметризованные) запросы используются для повышения эффективности, когда
один запрос выполняется многократно и защищает от SQL-инъекций.
</para>
<para>
<emphasis role="bold">Принцип работы</emphasis>
</para>
<para>
Выполнение подготавливаемого запроса проводится в два этапа: подготовка и
исполнение. На этапе подготовки на сервер посылается шаблон запроса. Сервер
выполняет синтаксическую проверку этого шаблона, строит план выполнения запроса
и выделяет под него ресурсы.
</para>
<para>
MySQL сервер поддерживает неименованные, или позиционные, псевдопеременные
<literal>?</literal>.
</para>
<para>
За подготовкой следует выполнение. Во время выполнения клиент связывает значения параметров и отправляет их на сервер.
Сервер выполняет запрос со связанными значениями, используя ранее созданные внутренние ресурсы.
</para>
<para>
<example>
<title>Подготовленный запрос</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Неподготовленный запрос */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
/* Подготовленный запрос, шаг 1: подготовка */
$stmt = $mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");
/* Подготовленный запрос, шаг 2: связывание и выполнение */
$id = 1;
$label = 'PHP';
$stmt->bind_param("is", $id, $label); // "is" означает, что $id связывается, как целое число, а $label — как строка
$stmt->execute();
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Повторное выполнение запроса</emphasis>
</para>
<para>
Подготовленный запрос можно запускать многократно. Перед каждым запуском
значения привязанных переменных будут передаваться на сервер и подставляться
в текст запроса. Сам текст запроса повторно не анализируется, равно как и не
отсылается повторно шаблон.
</para>
<para>
<example>
<title>Выражение INSERT один раз подготавливается, а затем многократно выполняется</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Неподготовленный запрос */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
/* Подготовленный запрос, шаг 1: подготовка */
$stmt = $mysqli->prepare("INSERT INTO test(id, label) VALUES (?, ?)");
/* Подготовленный запрос, шаг 2: связывание и выполнение */
$stmt->bind_param("is", $id, $label); // "is" означает, что $id связывается, как целое число, а $label — как строка
$data = [
1 => 'PHP',
2 => 'Java',
3 => 'C++'
];
foreach ($data as $id => $label) {
$stmt->execute();
}
$result = $mysqli->query('SELECT id, label FROM test');
var_dump($result->fetch_all(MYSQLI_ASSOC));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(3) {
[0]=>
array(2) {
["id"]=>
string(1) "1"
["label"]=>
string(3) "PHP"
}
[1]=>
array(2) {
["id"]=>
string(1) "2"
["label"]=>
string(4) "Java"
}
[2]=>
array(2) {
["id"]=>
string(1) "3"
["label"]=>
string(3) "C++"
}
}
]]>
</screen>
</example>
</para>
<para>
Каждый подготавливаемый запрос использует ресурсы сервера. Если запрос больше
не нужен, его необходимо сразу закрыть. Если не сделать этого явно, запрос
закроется сам, но только когда PHP освободит его дескриптор, как правило это
происходит при выходе запроса из области видимости или при завершении работы
скрипта.
</para>
<para>
Использование подготавливаемых запросов не всегда приводит к повышению
эффективности. Если параметризованный запрос запускается лишь раз, это приводит
к большему количеству клиент-серверных обменов данными, нежели при выполнении
простого запроса. Именно по этой причине в примере выше выражение
<literal>SELECT</literal> выполнялось, как обычный запрос.
</para>
<para>
Также имеет смысл рассмотреть SQL-синтаксис вставки множества значений в
выражении INSERT. В примере выше мультивставка (значения для вставки
перечисляются через запятую) в предложении INSERT обошлась бы дешевле,
чем подготовленный запрос.
</para>
<para>
<example>
<title>Меньше обменов данными при использовании мультивставок SQL</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$values = [1, 2, 3, 4];
$stmt = $mysqli->prepare("INSERT INTO test(id) VALUES (?), (?), (?), (?)");
$stmt->bind_param('iiii', ...$values);
$stmt->execute();
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Типы данных значений в результирующей таблице</emphasis>
</para>
<para>
В протоколе клиент-серверного взаимодействия MySQL для обычных и
подготавливаемых запросов определены разные протоколы передачи данных клиенту.
Параметризованные запросы используют так называемый двоичный протокол. Сервер
MySQL посылает результирующий набор клиенту «как есть» в двоичном формате.
Данные в таблице не преобразовываются в текст. Клиентские библиотеки получают двоичные данные и пытаются преобразовать значения в
соответствующие типы данных PHP.
Например, столбец результатов запроса типа SQL <literal>INT</literal> PHP примет
и преобразует в тип integer.
</para>
<para>
<example>
<title>Исходные типы данных</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Неподготовленный запрос */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
printf("id = %s (%s)\n", $row['id'], gettype($row['id']));
printf("label = %s (%s)\n", $row['label'], gettype($row['label']));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer)
label = PHP (string)
]]>
</screen>
</example>
</para>
<para>
Такое поведение не характерно для обычных запросов, которые по умолчанию
все результаты возвращают в виде текстовых строк. Это поведение по умолчанию можно изменить,
настроив соединение соответствующим образом. После такой настройки разницы
между данными подготавливаемого и обычного запросов уже не будет.
</para>
<para>
<emphasis role="bold">Получение результатов запроса с привязкой переменных</emphasis>
</para>
<para>
Результаты из подготовленного запроса можно получить либо привязав выходные
переменные, либо запросив объект <classname>mysqli_result</classname>.
</para>
<para>
Выходные параметры нужно привязывать после выполнения запроса. Каждому столбцу
результирующей таблицы должна соответствовать ровно одна переменная.
</para>
<para>
<example>
<title>Привязка переменных к результату запроса</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Неподготовленный запрос */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$stmt->bind_result($out_id, $out_label);
while ($stmt->fetch()) {
printf("id = %s (%s), label = %s (%s)\n", $out_id, gettype($out_id), $out_label, gettype($out_label));
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1 (integer), label = PHP (string)
]]>
</screen>
</example>
</para>
<para>
Объекты подготавливаемых запросов по умолчанию возвращают небуферизованные
результирующие наборы. Эти таблицы никаким неявным образом не переносятся
на клиента, они остаются на сервере, занимая его ресурсы, пока клиентский
процесс самостоятельно не извлечёт все данные. Если клиент не может извлечь
данные результирующего набора, или после закрытия объекта запроса остаются
невыбранными какие-то данные, то на <literal>mysqli</literal> ложится
ответственность неявно подчистить этот мусор за клиентским процессом.
</para>
<para>
Также можно буферизовать данные результирующих таблиц подготовленного запроса
с помощью функции <methodname>mysqli_stmt::store_result</methodname>.
</para>
<para>
<emphasis role="bold">
Извлечение результатов запроса посредством mysqli_result интерфейса
</emphasis>
</para>
<para>
Вместо использования привязки переменных к результатам запроса, результирующие
таблицы можно извлекать средствами интерфейса mysqli_result. Функция
<methodname>mysqli_stmt::get_result</methodname> возвращает буферизованный
результирующий набор строк.
</para>
<para>
<example>
<title>Использование mysqli_result для выборки результатов запроса</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Неподготовленный запрос */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP')");
$stmt = $mysqli->prepare("SELECT id, label FROM test WHERE id = 1");
$stmt->execute();
$result = $stmt->get_result();
var_dump($result->fetch_all(MYSQLI_ASSOC));
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
[0]=>
array(2) {
["id"]=>
int(1)
["label"]=>
string(3) "PHP"
}
}
]]>
</screen>
</example>
</para>
<para>
Использование интерфейса <classname>mysqli_result</classname> имеет
дополнительное преимущество в том, что буферизация результирующих таблиц на
клиенте предлагает гибкую систему навигации по этим таблицам.
</para>
<para>
<example>
<title>Буферизация результирующего набора для удобства чтения данных</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Неподготовленный запрос */
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT, label TEXT)");
$mysqli->query("INSERT INTO test(id, label) VALUES (1, 'PHP'), (2, 'Java'), (3, 'C++')");
$stmt = $mysqli->prepare("SELECT id, label FROM test");
$stmt->execute();
$result = $stmt->get_result();
for ($row_no = $result->num_rows - 1; $row_no >= 0; $row_no--) {
$result->data_seek($row_no);
var_dump($result->fetch_assoc());
}
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(2) {
["id"]=>
int(3)
["label"]=>
string(3) "C++"
}
array(2) {
["id"]=>
int(2)
["label"]=>
string(4) "Java"
}
array(2) {
["id"]=>
int(1)
["label"]=>
string(3) "PHP"
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Экранирование и SQL-инъекции</emphasis>
</para>
<para>
Привязанные переменные отправляются на сервер отдельно от запроса и таким
образом не могут влиять на него. Сервер использует эти значения непосредственно в
момент выполнения, уже после того, как был обработан шаблон выражения.
Привязанные параметры не нуждаются в экранировании, так как они никогда не подставляются
непосредственно в строку запроса. Необходимо отправлять тип привязанной переменной на сервер,
чтобы определить соответствующее преобразование. Смотрите функцию
<methodname>mysqli_stmt::bind_param</methodname> для получения большей информации.
</para>
<para>
Такое разделение часто считается единственным способом обезопаситься от
SQL-инъекции, но на самом деле такого же уровня безопасности можно добиться
и с неподготовленными выражениями, если правильно отформатировать все значения.
Важно отметить, что правильное форматирование — не то же самое, что и экранирование,
и включает в себя больше логики. Таким образом, подготовленные выражения -
просто более удобный и менее подверженный ошибкам способ для достижения
такой безопасности базы данных.
</para>
<para>
<emphasis role="bold">Эмуляция подготовленного запроса на клиенте</emphasis>
</para>
<para>
В API нет возможности эмулировать подготавливаемые запросы на клиенте.
</para>
<para>
<emphasis role="bold">Смотрите также</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::__construct</methodname></member>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::prepare</methodname></member>
<member><methodname>mysqli_stmt::prepare</methodname></member>
<member><methodname>mysqli_stmt::execute</methodname></member>
<member><methodname>mysqli_stmt::bind_param</methodname></member>
<member><methodname>mysqli_stmt::bind_result</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.stored-procedures">
<title>Хранимые процедуры</title>
<para>
СУБД MySQL поддерживает хранимые процедуры. Под этим термином понимается
последовательность операций, хранящаяся как единое целое в каталоге базы
данных на сервере. Приложения могут вызывать и запускать хранимые процедуры.
Для запуска хранимой процедуры используется SQL выражение
<literal>CALL</literal>.
</para>
<para>
<emphasis role="bold">Параметры</emphasis>
</para>
<para>
Хранимые процедуры могут иметь параметры <literal>IN</literal>,
<literal>INOUT</literal> и <literal>OUT</literal> в зависимости от
версии MySQL. Интерфейс mysqli не делает различий между этими типами
параметров.
</para>
<para>
<emphasis role="bold">Параметр IN</emphasis>
</para>
<para>
Входные параметры указываются внутри предложения <literal>CALL</literal>.
При передаче входных параметров важно убедиться, что их значения корректно
экранированы.
</para>
<para>
<example>
<title>Вызов хранимой процедуры</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query("CREATE PROCEDURE p(IN id_val INT) BEGIN INSERT INTO test(id) VALUES(id_val); END;");
$mysqli->query("CALL p(1)");
$result = $mysqli->query("SELECT id FROM test");
var_dump($result->fetch_assoc());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
["id"]=>
string(1) "1"
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Параметр INOUT/OUT</emphasis>
</para>
<para>
Значения параметров <literal>INOUT</literal>/<literal>OUT</literal> доступны
через переменные сессии.
</para>
<para>
<example>
<title>Использование переменных сессии</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p(OUT msg VARCHAR(50)) BEGIN SELECT "Hi!" INTO msg; END;');
$mysqli->query("SET @msg = ''");
$mysqli->query("CALL p(@msg)");
$result = $mysqli->query("SELECT @msg as _p_out");
$row = $result->fetch_assoc();
echo $row['_p_out'];
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
Hi!
]]>
</screen>
</example>
</para>
<para>
Разработчики приложений и фреймворков могут предоставить более удобный API,
в котором наряду с сессионными переменными используется просмотр каталогов
базы данных напрямую. Однако, стоит учитывать, что такой подход снижает
быстродействие.
</para>
<para>
<emphasis role="bold">Обработка результирующих наборов</emphasis>
</para>
<para>
Хранимые процедуры могут возвращать результирующие наборы строк. Таблицы
результатов работы хранимой процедуры нельзя корректно извлечь средствами
<methodname>mysqli::query</methodname>. Функция <methodname>mysqli::query</methodname>
выполняет две операции: запускает запрос и извлекает первый результирующий
набор, помещая его в буфер. Хранимые процедуры могут возвращать более одного
результирующего набора, но при использовании <methodname>mysqli::query</methodname>
все они, кроме первого, станут недоступны пользователю.
</para>
<para>
Результирующие таблицы хранимых процедур извлекаются функциями
<methodname>mysqli::real_query</methodname> или
<methodname>mysqli::multi_query</methodname>. Обе функции позволяют получить любое
количество результирующих наборов, возвращённых SQL-запросами, таких как
<literal>CALL</literal>. Если в процессе работы не удаётся извлечь все
доступные результаты вызова хранимой процедуры, будет вызываться ошибка.
</para>
<para>
<example>
<title>Извлечение результатов работы хранимой процедуры</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$mysqli->multi_query("CALL p()");
do {
if ($result = $mysqli->store_result()) {
printf("---\n");
var_dump($result->fetch_all());
$result->free();
}
} while ($mysqli->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "1"
}
[1]=>
array(1) {
[0]=>
string(1) "2"
}
[2]=>
array(1) {
[0]=>
string(1) "3"
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
string(1) "2"
}
[1]=>
array(1) {
[0]=>
string(1) "3"
}
[2]=>
array(1) {
[0]=>
string(1) "4"
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Использование подготавливаемых запросов</emphasis>
</para>
<para>
Специальных средств для извлечения данных при использовании подготавливаемых
запросов не требуется. Интерфейсы подготавливаемых и обычных запросов
одинаковы. Однако, нужно учитывать, что не все версии MYSQL поддерживают
подготовку в запросе SQL-выражения <literal>CALL</literal>.
</para>
<para>
<example>
<title>Хранимые процедуры и подготавливаемые запросы</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$stmt = $mysqli->prepare("CALL p()");
$stmt->execute();
do {
if ($result = $stmt->get_result()) {
printf("---\n");
var_dump($result->fetch_all());
$result->free();
}
} while ($stmt->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
---
array(3) {
[0]=>
array(1) {
[0]=>
int(1)
}
[1]=>
array(1) {
[0]=>
int(2)
}
[2]=>
array(1) {
[0]=>
int(3)
}
}
---
array(3) {
[0]=>
array(1) {
[0]=>
int(2)
}
[1]=>
array(1) {
[0]=>
int(3)
}
[2]=>
array(1) {
[0]=>
int(4)
}
}
]]>
</screen>
</example>
</para>
<para>
Само собой, поддерживается привязка результатов к объекту запроса.
</para>
<para>
<example>
<title>
Хранимые процедуры и подготавливаемые запросы с использованием привязки
результатов
</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$mysqli->query("INSERT INTO test(id) VALUES (1), (2), (3)");
$mysqli->query("DROP PROCEDURE IF EXISTS p");
$mysqli->query('CREATE PROCEDURE p() READS SQL DATA BEGIN SELECT id FROM test; SELECT id + 1 FROM test; END;');
$stmt = $mysqli->prepare("CALL p()");
$stmt->execute();
do {
if ($stmt->store_result()) {
$stmt->bind_result($id_out);
while ($stmt->fetch()) {
echo "id = $id_out\n";
}
}
} while ($stmt->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
id = 1
id = 2
id = 3
id = 2
id = 3
id = 4
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Смотрите также</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::next_result</methodname></member>
<member><methodname>mysqli::more_results</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.multiple-statement">
<title>Множественные запросы</title>
<para>
MySQL поддерживает наличие нескольких SQL-запросов в тексте одного запроса, но требует особого обращения.
Пересылка на сервер нескольких выражений в одном запроса уменьшает количество
клиент-серверных взаимодействий, но требует специальной обработки.
</para>
<para>
Множественные запросы, или мультизапросы, должны запускаться функцией
<methodname>mysqli::multi_query</methodname>. Отдельные SQL-предложения в
мультизапросе отделяются точкой с запятой. После выполнения мультизапроса
все результирующие наборы, которые он вернул, необходимо извлечь.
</para>
<para>
MySQL-сервер поддерживает наличие в одном мультизапросе подзапросов, как
возвращающих результирующий набор, так и не возвращающих.
</para>
<para>
<example>
<title>Множественные запросы</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->query("DROP TABLE IF EXISTS test");
$mysqli->query("CREATE TABLE test(id INT)");
$sql = "SELECT COUNT(*) AS _num FROM test;
INSERT INTO test(id) VALUES (1);
SELECT COUNT(*) AS _num FROM test; ";
$mysqli->multi_query($sql);
do {
if ($result = $mysqli->store_result()) {
var_dump($result->fetch_all(MYSQLI_ASSOC));
$result->free();
}
} while ($mysqli->next_result());
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "0"
}
}
array(1) {
[0]=>
array(1) {
["_num"]=>
string(1) "1"
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Рассмотрение аспектов безопасности</emphasis>
</para>
<para>
Функции API <methodname>mysqli::query</methodname> и
<methodname>mysqli::real_query</methodname> во время работы не устанавливают
на сервере специальный флаг, необходимый для выполнения мультизапросов.
Отдельная API-функция для мультизапросов позволяет снизить вероятность
случайных SQL-инъекций. Злоумышленник может попытаться добавить в конец запроса
выражения, вроде <literal>; DROP DATABASE mysql</literal> или
<literal>; SELECT SLEEP(999)</literal>. Если ему это удастся, но не будет
использоваться функция <methodname>mysqli::multi_query</methodname>, сервер
не выполнит внедрённое и опасное SQL-выражение.
</para>
<para>
<example>
<title>SQL-инъекция</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$result = $mysqli->query("SELECT 1; DROP TABLE mysql.user");
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
PHP Fatal error: Uncaught mysqli_sql_exception: You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax to
use near 'DROP TABLE mysql.user' at line 1
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Подготавливаемые запросы</emphasis>
</para>
<para>
Использование множества выражений в подготавливаемом запросе не поддерживается.
</para>
<para>
<emphasis role="bold">Смотрите также</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli::multi_query</methodname></member>
<member><methodname>mysqli::next_result</methodname></member>
<member><methodname>mysqli::more_results</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.transactions">
<title>API поддержка транзакций</title>
<para>
Поддержка транзакций в СУБД MySQL зависит от используемого движка хранилища
данных. Начиная с MySQL 5.5, по умолчанию используется движок InnoDB.
InnoDB полностью поддерживает модель транзакций ACID.
</para>
<para>
Транзакциями можно управлять как средствами SQL, так и вызовами API-функций.
Для включения и выключения режима автофиксации изменений (<literal>autocommit</literal>) рекомендуется
пользоваться API функциями.
</para>
<para>
<example>
<title>Установка режима автофиксации (<literal>autocommit</literal>) средствами SQL и функциями API</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
/* Рекомендуется управлять настройками транзакций средствами API */
$mysqli->autocommit(false);
/* Не будет распознаваться и учитываться плагинами репликации и балансировки нагрузки */
$mysqli->query('SET AUTOCOMMIT = 0');
]]>
</programlisting>
</example>
</para>
<para>
Дополнительные службы сервера, такие как плагины репликации и балансировки
нагрузки, могут отслеживать вызовы API-функций. Плагин репликации может
сообщать балансировщику нагрузки о запущенной транзакции, если эта транзакция
обслуживается API-функциями. Сервер не сможет распределять нагрузку между
репликами базы, если смена режима автофиксации (<literal>autocommit</literal>), фиксация и откат транзакций
осуществляются SQL-запросами.
</para>
<para>
<example>
<title>Фиксация и откат</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$mysqli->autocommit(false);
$mysqli->query("INSERT INTO test(id) VALUES (1)");
$mysqli->rollback();
$mysqli->query("INSERT INTO test(id) VALUES (2)");
$mysqli->commit();
]]>
</programlisting>
</example>
</para>
<para>
Следует заметить, что сервер MySQL не может откатить результаты всех запросов.
Некоторые изменения фиксируются неявно.
</para>
<para>
<emphasis role="bold">Смотрите также</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::autocommit</methodname></member>
<member><methodname>mysqli::begin_transaction</methodname></member>
<member><methodname>mysqli::commit</methodname></member>
<member><methodname>mysqli::rollback</methodname></member>
</simplelist>
</para>
</section>
<section xml:id="mysqli.quickstart.metadata">
<title>Метаданные</title>
<para>
Результирующий набор MySQL содержит метаданные. Эти данные описывают столбцы
результирующей таблицы. Все сведения, которые передаёт MySQL, доступны через
<literal>mysqli</literal> интерфейс. Модуль не изменяет получаемые данные,
либо эти изменения незначительны. Различия между версиями MySQL также можно не
принимать во внимание.
</para>
<para>
Метаданные доступны через интерфейс <classname>mysqli_result</classname>.
</para>
<para>
<example>
<title>Доступ к метаданным результирующей таблицы</title>
<programlisting role="php">
<![CDATA[
<?php
$mysqli = new mysqli("example.com", "user", "password", "database");
if ($mysqli->connect_errno) {
echo "Не удалось подключиться к MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
}
$res = $mysqli->query("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
var_dump($res->fetch_fields());
?>
]]>
</programlisting>
&example.outputs;
<screen>
<![CDATA[
array(2) {
[0]=>
object(stdClass)#3 (13) {
["name"]=>
string(4) "_one"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(1)
["length"]=>
int(1)
["charsetnr"]=>
int(63)
["flags"]=>
int(32897)
["type"]=>
int(8)
["decimals"]=>
int(0)
}
[1]=>
object(stdClass)#4 (13) {
["name"]=>
string(4) "_two"
["orgname"]=>
string(0) ""
["table"]=>
string(0) ""
["orgtable"]=>
string(0) ""
["def"]=>
string(0) ""
["db"]=>
string(0) ""
["catalog"]=>
string(3) "def"
["max_length"]=>
int(5)
["length"]=>
int(5)
["charsetnr"]=>
int(8)
["flags"]=>
int(1)
["type"]=>
int(253)
["decimals"]=>
int(31)
}
}
]]>
</screen>
</example>
</para>
<para>
<emphasis role="bold">Подготавливаемые запросы</emphasis>
</para>
<para>
Метаданные результирующих наборов, полученных в результате выполнения
подготовленных запросов, можно получить аналогичным образом. Подходящий
дескриптор <classname>mysqli_result</classname> можно получить функцией
<methodname>mysqli_stmt::result_metadata</methodname>.
</para>
<para>
<example>
<title>Метаданные подготовленных запросов</title>
<programlisting role="php">
<![CDATA[
<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli("example.com", "user", "password", "database");
$stmt = $mysqli->prepare("SELECT 1 AS _one, 'Hello' AS _two FROM DUAL");
$stmt->execute();
$result = $stmt->result_metadata();
var_dump($result->fetch_fields());
]]>
</programlisting>
</example>
</para>
<para>
<emphasis role="bold">Смотрите также</emphasis>
</para>
<para>
<simplelist>
<member><methodname>mysqli::query</methodname></member>
<member><methodname>mysqli_result::fetch_fields</methodname></member>
</simplelist>
</para>
</section>
</chapter>