mirror of
https://github.com/php/doc-ru.git
synced 2025-08-20 23:31:15 +00:00
1657 lines
70 KiB
XML
1657 lines
70 KiB
XML
<?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>
|