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

Дерево руководства
Глава 131 · После подключения, реляционных таблиц и основ транзакций
MySQL X DevAPI: пул подключений и жизненный цикл Session
От двух последовательных заданий до независимых транзакций, конечного ожидания пула, отката ошибки, UUID квитанции и честного контракта Client::close.
Сохранить результат задания и вернуть подключение
Фоновый обработчик закончил выгрузку каталога, затем выгрузку квитанций. Для каждого задания нужно сохранить результат в базе. Если каждый путь выполнения забирает соединение и забывает вернуть его, спустя некоторое время следующему заданию нечем работать. В этой главе мы получим две сохранённые квитанции, затем разберём отказ занятого пула, откат неудачного задания и корректное завершение worker.
| id | Задание | Позиций | Состояние |
|---|---|---|---|
| 1 | catalog-export | 2 | done |
| 2 | receipt-export | 3 | done |
Это ожидаемый журнал работы, а не обещание сгенерировать файлы экспорта. Полные программы действительно записывают такие строки, перечитывают их и показывают HTML-таблицу результата. SQL выбирает только нужные поля и строки: работа с пулом не требует выгружать весь каталог в PHP.
- getClient создаёт диспетчер с maxSize = 1
- Задание catalog-export вызывает getSession и получает соединение
- Session сохраняет items = 2, state = done и читает квитанцию
- Session::close возвращает соединение диспетчеру
- Следующее задание получает Session и сохраняет items = 3
- После последнего задания Client::close завершает соединения
Пул можно представить как выдачу ограниченного набора рабочих мест. Client ведёт выдачу, Session — полученное место для текущей работы. Возврат места позволяет взять его следующему заданию. Аналогия не означает, что PHP сам начнёт параллельно выполнять задания: наш первый цикл остаётся последовательным. Она также не гарантирует сохранение одного номера сетевого соединения между выдачами.
Сначала получить Client и одну Session
Предположим, $uri уже содержит настройки отдельной учебной базы. В примере нет буквального пароля: URI берётся из окружения по README. getClient — свободная функция расширения mysql_xdevapi. Обратная косая черта отделяет имя пространства от функции. Она возвращает объект Client, и знак = сохраняет его в переменной $client. Метод getSession вызывается стрелкой -> у этого объекта и возвращает Session.
$client = mysql_xdevapi\getClient($uri);
$worker = $client->getSession();Пока прикладных строк не записано. Получим от базы число позиций и прочитаем его: sql готовит запрос, execute выполняет, fetchOne превращает одну строку результата в ассоциативный массив. Имя items станет ключом массива, значение 2 можно вывести через echo.
$row = $worker->sql('SELECT 2 AS items')->execute()->fetchOne();
echo $row['items']; // 2
$worker->close();
$client->close();Здесь видна обычная последовательность без ошибки. В реальной задаче SQL может завершиться исключением, поэтому возврат Session переносится в finally. Этот блок выполнится и после успеха, и при выходе из try по исключению. Закрытие всего Client откладывается до окончания серии работ. В первой полной программе ниже эта конструкция охватывает настоящий INSERT и чтение квитанции.
$worker = $client->getSession();
try {
$row = $worker->sql('SELECT 2 AS items')->execute()->fetchOne();
echo $row['items'];
} finally {
$worker->close();
}Фрагменты показывают отдельные шаги с живым настроенным Client, а не предназначены для последовательной вставки после уже выполненного close. Каждый самостоятельный архив ниже сам создаёт Client, учебную таблицу и все исходные строки. Начальная настройка находится в главе о X Protocol и первом подключении. Если синтаксис ещё непривычен, полезны объект и вызов метода, ассоциативный массив и try, catch и finally.
Лимит и ожидание задаются до первого checkout
Чтобы ожидание свободного слота было конечным, первый Client каждого архива сразу получает настройки. Человек читает PHP-массив с именами параметров, json_encode превращает его в строку JSON для фабрики. Это конфигурация пула, не JSON-документ каталога. JSON_THROW_ON_ERROR делает ошибку кодирования явной.
$settings = [
'enabled' => true,
'maxSize' => 1,
'queueTimeOut' => 250,
];
$options = json_encode($settings, JSON_THROW_ON_ERROR);
$client = mysql_xdevapi\getClient($uri, $options);| Настройка | Что означает здесь | Что она не делает |
|---|---|---|
| enabled = true | Использовать пул | Не запускает задания параллельно |
| maxSize = 1 | Одновременно держать не более одного соединения этого пула | Не ограничивает суммарные соединения всех процессов сервиса |
| queueTimeOut = 250 | Ждать свободный слот до 250 мс по правилам коннектора | Не прерывает длинный SQL через 250 мс |
| maxIdleTime | Настройка срока простоя уже возвращённого соединения | Не отбирает Session у зависшего задания |
Имена и форма параметров зависят от коннектора. PECL mysql_xdevapi 8.0.30 принимает плоский JSON-объект с queueTimeOut, тогда как общее руководство X DevAPI показывает также другую форму pooling и queueTimeout. Полный список, значения по умолчанию и ограничения приведены в карточке функций и фабрик. Для maxIdleTime здесь не обещается отдельный фоновый таймер в долгоживущем CLI-процессе: соответствующая очистка имеет особенности выбранного исходника. Ни одно из этих свойств не отменяет обязанность явно возвращать Session.
Ещё одна особенность выбранного PECL: внутреннее состояние пула кешируется по URI в процессе, а повторный getClient с тем же URI не обязан применить новые опции. Поэтому задайте конфигурацию один раз при запуске worker. Учебные архивы запускаются каждый отдельным процессом PHP; перестановка примеров в одном бесконечном скрипте может незаметно сохранить старый лимит.
Три разных завершения
| Действие | Ответственность | Пример |
|---|---|---|
| commit / rollback | Подтвердить или отменить изменения текущей транзакции | После дубликата удалить partial, сохранить existing |
| Session::close | Вернуть одно полученное соединение | Освободить единственный слот для следующего задания |
| Client::close | Закрыть текущие управляемые соединения | Завершить worker после готовой квитанции |
Две одновременно взятые Session могут быть двумя независимыми участниками транзакций. Но две переменные, указывающие на одну Session, не дают двух подключений. И дополнительная обёртка Session от таблицы из главы о навигации по объектам не равна новому checkout из Client. Во второй программе это различие подтверждается и разными CONNECTION_ID, и реальным чтением остатка до и после commit.
Остановка пула тоже не заменяет остановку приложения. В наблюдаемой версии Client::close возвращает null, закрывает старые Session, но позволяет тому же Client позже открыть новую. Поэтому worker сначала перестаёт принимать задачи, завершает активные работы, затем закрывает ресурсы. Последний пример сохраняет это отличие видимым, вместо того чтобы скрыть расхождение с bool в официальном synopsis.
Что потребуется для самостоятельного запуска
Профиль этих программ: PHP 8.3.35, PECL mysql_xdevapi 8.0.30 и MySQL 8.4.13 с X Protocol. Это конкретная проверяемая связка, а не заявление о последних версиях. PHPMAN_MYSQLX_URI и PHPMAN_MYSQLX_SCHEMA задают подключение и отдельную учебную схему. В скачиваемых файлах нет пароля. README объясняет подготовку и права; lab.php проверяет окружение и создаёт одну собственную таблицу mysql144_… на программу.
Сначала служебная отдельная Session создаёт таблицу, затем Client обслуживает задания. Эта отдельная Session остаётся доступна для уборки после закрытия Client. DDL находится вне предметных транзакций. result.html показывает фактические строки и наблюдения, result.json — прочитанные записи, номера соединений, UUID и безопасную цепочку ошибок, metrics.json — проверки и удаление собственного объекта. На реальном сервисе таблицы не создают и не удаляют при каждом задании: здесь это воспроизводимая учебная лаборатория.
В выбранном расширении ошибка ожидания может включить полный URI подключения. Функция x144SafeMessage удаляет его и пароль до записи диагностических файлов. x144Failure использует эту очистку для каждого звена цепочки. Это обычные функции lab.php: код открыт в архиве; они не меняют правила SQL и не превращают неожиданную ошибку в успешный результат.
Взять соединение для двух последовательных заданий
Фоновый обработчик закончил экспорт каталога из двух позиций, затем экспорт трёх квитанций. Здесь мы не создаём сам экспорт: сохраняем журнал его завершения. Одновременно работающему заданию достаточно одного подключения, но после каждого задания это подключение нужно вернуть следующему.
- Client допускает одно выданное соединение
- Задание 1 берёт Session и сохраняет catalog-export: 2 позиции
- close возвращает соединение пулу
- Задание 2 берёт Session и сохраняет receipt-export: 3 позиции
- Точечное чтение подтверждает каждую квитанцию; общий журнал содержит две строки
Сначала короткое действие
$worker = $client->getSession();
try {
$row = $worker->sql('SELECT 2 AS exported_items')->execute()->fetchOne();
echo $row['exported_items']; // 2
} finally {
$worker->close();
}$client уже получен через getClient с URI и опциями. getSession без аргументов выдаёт объект для конкретной работы. sql готовит SQL, execute отправляет его, fetchOne возвращает строку как массив. finally выполняет close и при нормальном завершении, и при исключении. В полной программе VALUES (?, ?, ?, ?) получает четыре отдельных значения через цепочку bind; имя собственной таблицы заранее проверено helper-функцией.
Что получится
| Наблюдение | Результат |
|---|---|
| Первое задание | catalog-export |
| Позиций первого задания | 2 |
| Второе задание | receipt-export |
| Позиций второго задания | 3 |
| Сохранено квитанций | 2 |
| Оба задания завершены | да |
| Session возвращены после каждого задания | да |
Самостоятельный файл и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x144Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
$schema = $session->getSchema($config['schema']);
if (!$schema->existsInDatabase()) {
throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
}
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
'host'=>$config['host'], 'port'=>$config['port']];
$name = x144OwnTable($session, $schema, $owned, 'sequential_jobs',
'(id INT PRIMARY KEY, task_name VARCHAR(40) NOT NULL, items INT NOT NULL, state VARCHAR(12) NOT NULL) ENGINE=InnoDB');
$qualified = x144Qualified($schema, $name);
$options = ['enabled'=>true, 'maxSize'=>1, 'maxIdleTime'=>10000, 'queueTimeOut'=>250];
$client = mysql_xdevapi\getClient($config['uri'], json_encode($options, JSON_THROW_ON_ERROR));
$jobs = [[1, 'catalog-export', 2], [2, 'receipt-export', 3]];
$receipts = $connectionIds = $releaseResults = [];
try {
foreach ($jobs as [$id, $taskName, $items]) {
$worker = $client->getSession();
try {
$connectionIds[] = $worker->sql('SELECT CONNECTION_ID() AS id')->execute()->fetchOne()['id'];
$worker->sql('INSERT INTO '.$qualified.' (id, task_name, items, state) VALUES (?, ?, ?, ?)')
->bind($id)->bind($taskName)->bind($items)->bind('done')->execute();
$receipts[] = $worker->sql('SELECT id, task_name, items, state FROM '.$qualified.' WHERE id = ?')
->bind($id)->execute()->fetchOne();
} finally {
$releaseResults[] = $worker->close();
}
}
} finally {
$client->close();
}
$saved = $session->sql('SELECT id, task_name, items, state FROM '.$qualified.' ORDER BY id')->execute()->fetchAll();
$rows = [['Первое задание', $saved[0]['task_name']], ['Позиций первого задания', $saved[0]['items']],
['Второе задание', $saved[1]['task_name']], ['Позиций второго задания', $saved[1]['items']],
['Сохранено квитанций', count($saved)], ['Оба задания завершены', array_column($saved, 'state') === ['done', 'done']],
['Session возвращены после каждого задания', $releaseResults === [true, true]]];
$checks = ['saved_tasks'=>array_column($saved, 'task_name') === ['catalog-export', 'receipt-export'],
'item_counts'=>array_column($saved, 'items') === [2, 3], 'states'=>array_column($saved, 'state') === ['done', 'done'],
'receipt_readback'=>$receipts === $saved, 'released'=>$releaseResults === [true, true],
'two_checkouts'=>count($connectionIds) === 2 && $connectionIds[0] > 0 && $connectionIds[1] > 0,
'client_class'=>$client instanceof mysql_xdevapi\Client];
$observations = ['options'=>$options, 'jobs'=>$jobs, 'receipts'=>$receipts, 'saved_rows'=>$saved,
'connection_ids'=>$connectionIds, 'same_connection_observed'=>$connectionIds[0] === $connectionIds[1],
'release_results'=>$releaseResults, 'client_class'=>get_debug_type($client)];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x144Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x144Report('mysql144-x-pool-first-checkout', 'Взять соединение для двух последовательных заданий', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Первое задание — catalog-export
Позиций первого задания — 2
Второе задание — receipt-export
Позиций второго задания — 3
Сохранено квитанций — 2
Оба задания завершены — да
Session возвращены после каждого задания — да
Запустите отдельным процессом PHP по README. lab.php создаёт одну собственную таблицу; программа сохраняет и перечитывает строки, освобождает Session/Client и удаляет свою таблицу. result.html показывает результат, result.json — реальные данные и безопасную диагностику, metrics.json — проверки.
Таблица сохранённого запуска · Прочитанные строки и наблюдения.
Границы и ошибки
Session::close возвращает соединение в пул, а не обязательно закрывает сетевой канал. Фактические CONNECTION_ID записаны в JSON, но успешность урока не зависит от их совпадения: переиспользование зависит от состояния сервера и коннектора. maxSize относится к этому пулу, а не ко всем PHP-процессам сервиса. Две сохранённые строки подтверждают журнал выполнения, но не доказывают существование файлов экспорта.
Попробуйте изменить
Добавьте третье задание price-export с четырьмя позициями. Нужно ли увеличивать maxSize до 3?
Ожидаемое решение
Нет, если задания выполняются последовательно и каждое возвращает Session в finally. Журнал вырастет до трёх строк. Размер пула связан с одновременной потребностью в соединениях, а не с общим количеством заданий за день.
Увидеть две независимые транзакционные Session
Кладовщик пересчитал светильники: вместо 4 на складе 9. Пока исправление не подтверждено, другой участник должен видеть прежнее сохранённое значение. Возьмём две Session одновременно и покажем, когда новый остаток становится доступен второму участнику.
- Таблица хранит LAMP: stock = 4
- Client выдаёт first и second с разными CONNECTION_ID
- first начинает транзакцию и записывает stock = 9
- first читает 9, а second отдельным запросом ещё читает 4
- first выполняет commit; следующий запрос second и контрольное чтение получают 9
Сначала короткое действие
$first->startTransaction();
$first->sql('UPDATE '.$qualified.' SET stock = 9 WHERE id = 1')->execute();
// До commit другой участник читает сохранённые 4.
$first->commit(); // После подтверждения новый запрос прочитает 9.Транзакция объединяет действия одного соединения до commit или rollback. Здесь first и second одновременно взяты из пула, поэтому это два участника. UPDATE меняет только id = 1; последующий SELECT берёт только sku и stock той же строки. second явно работает с autocommit = 1 без длинной открытой транзакции, чтобы каждый его запрос не продолжал старый снимок.
Что получится
| Наблюдение | Результат |
|---|---|
| Товар | LAMP |
| Исходный остаток | 4 |
| Своё изменение до commit | 9 |
| Другой участник до commit | 4 |
| Новый запрос другого участника после commit | 9 |
| Сохранённый остаток | 9 |
| Два одновременно выданных соединения различаются | да |
Самостоятельный файл и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x144Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
$schema = $session->getSchema($config['schema']);
if (!$schema->existsInDatabase()) {
throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
}
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
'host'=>$config['host'], 'port'=>$config['port']];
$name = x144OwnTable($session, $schema, $owned, 'independent_stock',
'(id INT PRIMARY KEY, sku VARCHAR(20) NOT NULL, stock INT NOT NULL) ENGINE=InnoDB');
$qualified = x144Qualified($schema, $name);
$session->sql('INSERT INTO '.$qualified." (id, sku, stock) VALUES (1, 'LAMP', 4)")->execute();
$options = ['enabled'=>true, 'maxSize'=>2, 'queueTimeOut'=>250];
$client = mysql_xdevapi\getClient($config['uri'], json_encode($options, JSON_THROW_ON_ERROR));
$first = $second = null;
$active = false;
try {
$first = $client->getSession();
$second = $client->getSession();
$ids = [$first->sql('SELECT CONNECTION_ID() AS id')->execute()->fetchOne()['id'],
$second->sql('SELECT CONNECTION_ID() AS id')->execute()->fetchOne()['id']];
$second->sql('SET autocommit = 1')->execute();
$initial = $second->sql('SELECT sku, stock FROM '.$qualified.' WHERE id = 1')->execute()->fetchOne();
$first->startTransaction();
$active = true;
$first->sql('UPDATE '.$qualified.' SET stock = ? WHERE id = ?')->bind(9)->bind(1)->execute();
$own = $first->sql('SELECT sku, stock FROM '.$qualified.' WHERE id = 1')->execute()->fetchOne();
$otherBefore = $second->sql('SELECT sku, stock FROM '.$qualified.' WHERE id = 1')->execute()->fetchOne();
$first->commit();
$active = false;
$otherAfter = $second->sql('SELECT sku, stock FROM '.$qualified.' WHERE id = 1')->execute()->fetchOne();
} finally {
try { if ($active && $first !== null) { $first->rollback(); } }
finally {
try { if ($second !== null) { $second->close(); } }
finally { try { if ($first !== null) { $first->close(); } } finally { $client->close(); } }
}
}
$saved = $session->sql('SELECT sku, stock FROM '.$qualified.' WHERE id = 1')->execute()->fetchOne();
$rows = [['Товар', $saved['sku']], ['Исходный остаток', $initial['stock']], ['Своё изменение до commit', $own['stock']],
['Другой участник до commit', $otherBefore['stock']], ['Новый запрос другого участника после commit', $otherAfter['stock']],
['Сохранённый остаток', $saved['stock']], ['Два одновременно выданных соединения различаются', $ids[0] !== $ids[1]]];
$checks = ['initial_stock'=>$initial === ['sku'=>'LAMP','stock'=>4], 'own_write'=>$own['stock'] === 9,
'uncommitted_hidden'=>$otherBefore['stock'] === 4, 'commit_visible'=>$otherAfter['stock'] === 9,
'persistent_readback'=>$saved === $otherAfter, 'independent_connections'=>$ids[0] !== $ids[1]];
$observations = ['options'=>$options, 'connection_ids'=>$ids, 'initial'=>$initial, 'own_uncommitted'=>$own,
'other_before_commit'=>$otherBefore, 'other_after_commit'=>$otherAfter, 'saved_row'=>$saved,
'second_session_autocommit'=>true];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x144Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x144Report('mysql144-x-pool-independent-transactions', 'Увидеть две независимые транзакционные Session', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Товар — LAMP
Исходный остаток — 4
Своё изменение до commit — 9
Другой участник до commit — 4
Новый запрос другого участника после commit — 9
Сохранённый остаток — 9
Два одновременно выданных соединения различаются — да
Запустите отдельным процессом PHP по README. lab.php создаёт одну собственную таблицу; программа сохраняет и перечитывает строки, освобождает Session/Client и удаляет свою таблицу. result.html показывает результат, result.json — реальные данные и безопасную диагностику, metrics.json — проверки.
Таблица сохранённого запуска · Прочитанные строки и наблюдения.
Границы и ошибки
При REPEATABLE READ участник с уже открытой транзакцией и созданным снимком может продолжать видеть старое состояние после чужого commit. Это не означает, что подтверждение потерялось. Пример показывает обычные согласованные чтения без блокировки; задачу одновременного резервирования последней единицы решают транзакцией с locking read или условным UPDATE. Table::getSession из предыдущей главы возвращал обёртку общего соединения; повторный checkout Client здесь имеет иной смысл.
Попробуйте изменить
Замените commit на rollback, оставив контрольное чтение. Какой остаток должен увидеть второй участник?
Ожидаемое решение
4: собственное незавершённое чтение first успело показать 9, но rollback отменяет эту запись. После него второй участник и контрольный SELECT видят исходные 4. Таблицу следует создавать до этой транзакции, а не включать DDL в откатываемую работу.
Освободить единственный слот после конечного отказа
У worker есть только одно подключение. Он уже обрабатывает каталог, а другая часть программы пытается одновременно взять ещё одно. Нельзя бесконечно ждать ресурс, который этот же поток пока не возвращает. Зададим конечное ожидание, увидим точный отказ и завершим второе задание после возврата первого слота.
- maxSize = 1, queueTimeOut = 250 миллисекунд
- held занимает единственный слот и записывает working
- Второй checkout получает ошибку очереди 10055
- held завершает первое задание и выполняет close
- next получает свободный слот и сохраняет второе done
Сначала короткое действие
$options = json_encode(['enabled'=>true, 'maxSize'=>1, 'queueTimeOut'=>250]);
$client = mysql_xdevapi\getClient($uri, $options);
$held = $client->getSession();
// Пока held не закрыта, следующему checkout придётся ждать свободный слот.Здесь $uri — настроенная строка подключения, которую не выводят посетителю. Для PECL mysql_xdevapi 8.0.30 опции передаются плоской JSON-строкой; точное имя queueTimeOut содержит заглавные T и O. Единица — миллисекунда. В полном файле x144Failure выполняет переданную функцию, сохраняет класс, код и безопасное сообщение ожидаемого исключения. Само ограниченное ожидание выполняет коннектор; hrtime только измеряет его.
Что получится
| Наблюдение | Результат |
|---|---|
| Лимит одновременных Session | 1 |
| Отказ занятого пула распознан | да |
| Первый слот возвращён | да |
| Первое задание | done |
| Следующее задание | done |
| Сохранено завершённых заданий | 2 |
| URI исключён из диагностики | да |
Самостоятельный файл и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x144Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
$schema = $session->getSchema($config['schema']);
if (!$schema->existsInDatabase()) {
throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
}
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
'host'=>$config['host'], 'port'=>$config['port']];
$name = x144OwnTable($session, $schema, $owned, 'bounded_queue',
'(id INT PRIMARY KEY, task_name VARCHAR(40) NOT NULL, state VARCHAR(12) NOT NULL) ENGINE=InnoDB');
$qualified = x144Qualified($schema, $name);
$options = ['enabled'=>true, 'maxSize'=>1, 'queueTimeOut'=>250];
$client = mysql_xdevapi\getClient($config['uri'], json_encode($options, JSON_THROW_ON_ERROR));
$held = $next = null;
try {
$held = $client->getSession();
$held->sql('INSERT INTO '.$qualified." (id, task_name, state) VALUES (1, 'catalog', 'working')")->execute();
$started = hrtime(true);
$failure = x144Failure(static function () use ($client): void {
$unexpected = $client->getSession();
try { $unexpected->sql('SELECT 1 AS alive')->execute()->fetchOne(); }
finally { $unexpected->close(); }
});
$elapsedMs = (hrtime(true) - $started) / 1000000;
$held->sql('UPDATE '.$qualified." SET state = 'done' WHERE id = 1")->execute();
$released = $held->close();
$held = null;
$next = $client->getSession();
$next->sql('INSERT INTO '.$qualified." (id, task_name, state) VALUES (2, 'receipts', 'done')")->execute();
$saved = $next->sql('SELECT id, task_name, state FROM '.$qualified.' ORDER BY id')->execute()->fetchAll();
} finally {
try { if ($next !== null) { $next->close(); } }
finally { try { if ($held !== null) { $held->close(); } } finally { $client->close(); } }
}
$codes = array_column($failure ?? [], 'code');
$messages = array_column($failure ?? [], 'message');
$redacted = count($messages) > 0;
foreach ($messages as $message) {
$redacted = $redacted && !str_contains($message, $config['uri']) && !str_contains($message, 'mysqlx://');
}
$rows = [['Лимит одновременных Session', $options['maxSize']], ['Отказ занятого пула распознан', in_array(10055, $codes, true)],
['Первый слот возвращён', $released], ['Первое задание', $saved[0]['state']], ['Следующее задание', $saved[1]['state']],
['Сохранено завершённых заданий', count($saved)], ['URI исключён из диагностики', $redacted]];
$checks = ['queue_rejected'=>in_array(10055, $codes, true), 'session_returned'=>$released === true,
'recovered_work'=>$saved === [['id'=>1,'task_name'=>'catalog','state'=>'done'],['id'=>2,'task_name'=>'receipts','state'=>'done']],
'diagnostic_redacted'=>$redacted, 'finite_measurement'=>is_finite($elapsedMs) && $elapsedMs >= 0];
$observations = ['options'=>$options, 'failure_chain'=>$failure, 'elapsed_ms'=>$elapsedMs, 'release_result'=>$released, 'saved_rows'=>$saved];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x144Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x144Report('mysql144-x-pool-bounded-queue', 'Освободить единственный слот после конечного отказа', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Лимит одновременных Session — 1
Отказ занятого пула распознан — да
Первый слот возвращён — да
Первое задание — done
Следующее задание — done
Сохранено завершённых заданий — 2
URI исключён из диагностики — да
Запустите отдельным процессом PHP по README. lab.php создаёт одну собственную таблицу; программа сохраняет и перечитывает строки, освобождает Session/Client и удаляет свою таблицу. result.html показывает результат, result.json — реальные данные и безопасную диагностику, metrics.json — проверки.
Таблица сохранённого запуска · Прочитанные строки и наблюдения.
Границы и ошибки
250 мс задаёт ожидание свободного слота, а не предельное время SQL-запроса, установления TCP-соединения или всего задания. Мы не требуем точного значения секундомера: планировщик и окружение добавляют время. Нулевой queueTimeOut в выбранной реализации допускает неограниченное ожидание, поэтому этот пример его не использует. Ошибка 10055 может содержать URI с паролем: helper удаляет его до сохранения. Значение maxIdleTime не возвращает занятую заданием Session.
Пул постепенно исчерпывается: часть путей исполнения не возвращает взятое соединение. Возникает вопрос, вернётся ли занятый слот сам по тайм-ауту. Реальная задача на Stack Overflow. Различаем ожидание свободного слота, простой уже возвращённого соединения и окончание задания. На собственном пуле показываем конечный отказ и успешное следующее задание после close; семантику PHP проверяем отдельно от Python-коннектора.
Попробуйте изменить
Сохраните maxSize = 1, но верните held до попытки следующего checkout. Должна ли появиться ошибка 10055?
Ожидаемое решение
Нет: второй вызов уже сможет получить свободный слот и выполнить SQL. Увеличивать пул — не единственное средство; сначала убедитесь, что каждое задание возвращает соединение. Увеличение лимита не исправляет утечку, а только отодвигает исчерпание.
Откатить частичное задание и вернуть Session следующему
Импорт успел сохранить первую служебную строку, но следующая запись столкнулась с уже существующим первичным ключом. Нужно отменить только незавершённую работу, сохранить прежние данные и освободить единственное подключение следующему заданию. Простого сообщения об ошибке недостаточно: покажем итоговые строки базы.
- До задания существует id = 1: existing
- Транзакция записывает id = 2: partial
- Повторный id = 1 вызывает ошибку уникального ключа
- rollback удаляет частичную запись id = 2; close возвращает Session
- Новое задание сохраняет id = 3: next-job; итоговые ID — 1 и 3
Сначала короткое действие
$worker->startTransaction();
try {
$worker->sql('INSERT INTO '.$qualified." (id, task_name, state) VALUES (2, 'partial', 'working')")->execute();
// Остальные действия задания...
$worker->commit();
} catch (mysql_xdevapi\Exception $error) {
$worker->rollback();
echo x144SafeMessage($error->getMessage());
} finally {
$worker->close();
}PRIMARY KEY требует уникального id. try выполняет работу; catch принимает исключение базы; rollback отменяет незавершённые изменения той же Session; finally возвращает ресурс независимо от исхода. В коротком фрагменте путь успеха завершается commit. Полная программа намеренно вызывает дубликат и всегда откатывает этот учебный импорт; x144Failure сохраняет безопасную цепочку getPrevious, чтобы найти серверный код даже под общей ошибкой обёртки.
Что получится
| Наблюдение | Результат |
|---|---|
| Частичная строка была видна внутри задания | да |
| Конфликт первичного ключа распознан | да |
| Частичная строка исчезла после rollback | да |
| Session возвращена | да |
| Исходная запись сохранена | existing |
| Следующее задание сохранено | next-job |
| Строк в итоговом журнале | 2 |
Самостоятельный файл и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x144Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
$schema = $session->getSchema($config['schema']);
if (!$schema->existsInDatabase()) {
throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
}
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
'host'=>$config['host'], 'port'=>$config['port']];
$name = x144OwnTable($session, $schema, $owned, 'failed_job',
'(id INT PRIMARY KEY, task_name VARCHAR(40) NOT NULL, state VARCHAR(12) NOT NULL) ENGINE=InnoDB');
$qualified = x144Qualified($schema, $name);
$session->sql('INSERT INTO '.$qualified." (id, task_name, state) VALUES (1, 'existing', 'done')")->execute();
$options = ['enabled'=>true, 'maxSize'=>1, 'queueTimeOut'=>250];
$client = mysql_xdevapi\getClient($config['uri'], json_encode($options, JSON_THROW_ON_ERROR));
$worker = $next = null;
$active = false;
try {
$worker = $client->getSession();
$worker->startTransaction();
$active = true;
try {
$worker->sql('INSERT INTO '.$qualified." (id, task_name, state) VALUES (2, 'partial', 'working')")->execute();
$inside = $worker->sql('SELECT id, state FROM '.$qualified.' WHERE id = 2')->execute()->fetchOne();
$failure = x144Failure(static function () use ($worker, $qualified): void {
$worker->sql('INSERT INTO '.$qualified." (id, task_name, state) VALUES (1, 'duplicate', 'working')")->execute();
});
} finally {
try { if ($active) { $worker->rollback(); $active = false; } }
finally { $released = $worker->close(); $worker = null; }
}
$next = $client->getSession();
$missing = $next->sql('SELECT id, state FROM '.$qualified.' WHERE id = 2')->execute()->fetchOne();
$next->sql('INSERT INTO '.$qualified." (id, task_name, state) VALUES (3, 'next-job', 'done')")->execute();
$saved = $next->sql('SELECT id, task_name, state FROM '.$qualified.' ORDER BY id')->execute()->fetchAll();
} finally {
try { if ($next !== null) { $next->close(); } }
finally { try { if ($worker !== null) { $worker->close(); } } finally { $client->close(); } }
}
$codes = array_column($failure ?? [], 'code');
$rows = [['Частичная строка была видна внутри задания', $inside === ['id'=>2,'state'=>'working']],
['Конфликт первичного ключа распознан', in_array(1062, $codes, true)],
['Частичная строка исчезла после rollback', $missing === null], ['Session возвращена', $released],
['Исходная запись сохранена', $saved[0]['task_name']], ['Следующее задание сохранено', $saved[1]['task_name']],
['Строк в итоговом журнале', count($saved)]];
$checks = ['partial_existed'=>$inside === ['id'=>2,'state'=>'working'], 'duplicate_detected'=>in_array(1062, $codes, true),
'partial_rolled_back'=>$missing === null, 'slot_returned'=>$released === true,
'final_records'=>$saved === [['id'=>1,'task_name'=>'existing','state'=>'done'],['id'=>3,'task_name'=>'next-job','state'=>'done']]];
$observations = ['options'=>$options, 'partial_inside_transaction'=>$inside, 'failure_chain'=>$failure,
'partial_after_rollback'=>$missing, 'release_result'=>$released, 'saved_rows'=>$saved];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x144Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x144Report('mysql144-x-pool-failure-release', 'Откатить частичное задание и вернуть Session следующему', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Частичная строка была видна внутри задания — да
Конфликт первичного ключа распознан — да
Частичная строка исчезла после rollback — да
Session возвращена — да
Исходная запись сохранена — existing
Следующее задание сохранено — next-job
Строк в итоговом журнале — 2
Запустите отдельным процессом PHP по README. lab.php создаёт одну собственную таблицу; программа сохраняет и перечитывает строки, освобождает Session/Client и удаляет свою таблицу. result.html показывает результат, result.json — реальные данные и безопасную диагностику, metrics.json — проверки.
Таблица сохранённого запуска · Прочитанные строки и наблюдения.
Границы и ошибки
CREATE TABLE выполнен до startTransaction: обычные DDL в MySQL могут делать неявный commit и не становятся откатываемыми записями InnoDB. Ошибка отдельного INSERT не означает автоматического отката всей уже выполненной работы: решение rollback принимает обработчик задания. При разрыве соединения сам rollback тоже может завершиться ошибкой; пример не моделирует сетевой обрыв и не обещает безопасный автоматический повтор любого импорта. Не выводите необработанное сообщение посетителю: оно может содержать служебные сведения.
Разработчик хочет откатить серию действий в пуле, но среди действий есть CREATE TABLE и ALTER TABLE. Реальная задача на Stack Overflow. Таблицу создаём до предметной транзакции. В транзакции находятся только записи InnoDB, и после ошибки явно выполняется rollback на той же Session. Правило DDL подтверждено руководством MySQL, а API другого языка не переносится в PHP.
Попробуйте изменить
После ошибки удалите rollback, но оставьте close. Можно ли назвать такой код доказательством корректного отката приложения?
Ожидаемое решение
Нет. Даже если текущий коннектор сбросит соединение при возврате в пул, приложение уже не задаёт явно судьбу транзакции. Сохраните rollback до close и проверяйте отсутствие частичной записи. Для успешного пути, наоборот, нужен commit: возврат Session сам по себе не является подтверждением задания.
Найти квитанцию по UUID, созданному до записи
Сервис принял задание синхронизации семи товаров. Номер операции нужен уже до INSERT: под ним можно связать запрос, журнал и квитанцию. Сгенерируем UUID один раз, сохраним его вместе с результатом и через следующую Session прочитаем ровно нужную квитанцию.
- generateUUID создаёт operation_id до INSERT
- request содержит тот же ID и catalog-sync: 7 позиций
- bind передаёт ID отдельным SQL-значением
- Первое задание возвращает Session; другое получает её для чтения
- WHERE operation_id = ? находит квитанцию с тем же ID, items = 7 и state = done
Сначала короткое действие
$operationId = $worker->generateUUID();
echo strlen($operationId); // 32 в проверенной версии
// Дальше передаём тот же operationId в INSERT, журнал и поиск квитанции.Метод generateUUID не принимает аргументов и возвращает строку. Она присваивается operationId один раз. В полной программе первичный ключ CHAR(32) хранит эти 32 символа, а bind передаёт значение без склеивания SQL. WHERE выбирает одну операцию. Регулярное выражение используется только для проверки фактического формата генератора; для применения самого UUID писать регулярное выражение не требуется.
Что получится
| Наблюдение | Результат |
|---|---|
| UUID получен до записи | да |
| Длина UUID | 32 |
| Формат текущего генератора: 32 заглавных hex-символа | да |
| Найдена квитанция той же операции | да |
| Задание | catalog-sync |
| Позиций синхронизации | 7 |
| Состояние | done |
Самостоятельный файл и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x144Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
$schema = $session->getSchema($config['schema']);
if (!$schema->existsInDatabase()) {
throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
}
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
'host'=>$config['host'], 'port'=>$config['port']];
$name = x144OwnTable($session, $schema, $owned, 'operation_receipt',
'(operation_id CHAR(32) CHARACTER SET ascii COLLATE ascii_bin PRIMARY KEY, task_name VARCHAR(40) NOT NULL, items INT NOT NULL, state VARCHAR(12) NOT NULL) ENGINE=InnoDB');
$qualified = x144Qualified($schema, $name);
$options = ['enabled'=>true, 'maxSize'=>1, 'queueTimeOut'=>250];
$client = mysql_xdevapi\getClient($config['uri'], json_encode($options, JSON_THROW_ON_ERROR));
$worker = $reader = null;
try {
$worker = $client->getSession();
$operationId = $worker->generateUUID();
$request = ['operation_id'=>$operationId, 'task_name'=>'catalog-sync', 'items'=>7];
$worker->sql('INSERT INTO '.$qualified.' (operation_id, task_name, items, state) VALUES (?, ?, ?, ?)')
->bind($request['operation_id'])->bind($request['task_name'])->bind($request['items'])->bind('done')->execute();
$worker->close();
$worker = null;
$reader = $client->getSession();
$receipt = $reader->sql('SELECT operation_id, task_name, items, state FROM '.$qualified.' WHERE operation_id = ?')
->bind($operationId)->execute()->fetchOne();
} finally {
try { if ($reader !== null) { $reader->close(); } }
finally { try { if ($worker !== null) { $worker->close(); } } finally { $client->close(); } }
}
$same = $receipt !== null && $receipt['operation_id'] === $request['operation_id'];
$format = is_string($operationId) && preg_match('/^[0-9A-F]{32}$/D', $operationId) === 1;
$rows = [['UUID получен до записи', $request['operation_id'] === $operationId], ['Длина UUID', strlen($operationId)],
['Формат текущего генератора: 32 заглавных hex-символа', $format], ['Найдена квитанция той же операции', $same],
['Задание', $receipt['task_name']], ['Позиций синхронизации', $receipt['items']], ['Состояние', $receipt['state']]];
$checks = ['uuid_format'=>$format, 'correlated_receipt'=>$same, 'saved_task'=>$receipt['task_name'] === 'catalog-sync',
'saved_items'=>$receipt['items'] === 7, 'saved_state'=>$receipt['state'] === 'done'];
$observations = ['options'=>$options, 'request'=>$request, 'actual_uuid'=>$operationId,
'uuid_type'=>get_debug_type($operationId), 'uuid_length'=>strlen($operationId), 'saved_receipt'=>$receipt];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x144Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x144Report('mysql144-x-session-correlation-uuid', 'Найти квитанцию по UUID, созданному до записи', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
UUID получен до записи — да
Длина UUID — 32
Формат текущего генератора: 32 заглавных hex-символа — да
Найдена квитанция той же операции — да
Задание — catalog-sync
Позиций синхронизации — 7
Состояние — done
Запустите отдельным процессом PHP по README. lab.php создаёт одну собственную таблицу; программа сохраняет и перечитывает строки, освобождает Session/Client и удаляет свою таблицу. result.html показывает результат, result.json — реальные данные и безопасную диагностику, metrics.json — проверки.
Таблица сохранённого запуска · Прочитанные строки и наблюдения.
Границы и ошибки
Конкретный UUID меняется между запусками и сохранён в result.json; ожидаемый результат сравнивает связь ID с прочитанной квитанцией, а не одну зашитую строку. Это не AUTO_INCREMENT и не generated ID добавленного документа. Manual связывает формат с RFC 4122, но данный метод возвращает 32 символа без дефисов, с принятой X DevAPI перестановкой компонентов. Не объявляйте его стандартной строкой UUIDv4, секретным токеном или доказательством криптографической случайности. Один успешный запуск не доказывает отсутствие коллизий; PRIMARY KEY остаётся защитой от дубликатов.
У страницы и последующего AJAX-запроса разные идентификаторы; автор ожидает, что они автоматически обозначат одну операцию. Реальная задача на Stack Overflow. Разделяем ID соединения, отдельного запроса и бизнес-операции. Один UUID создаётся до записи квитанции, передаётся как значение и используется для точечного чтения; новый вызов генератора не восстанавливает старую связь.
Попробуйте изменить
Перед SELECT снова вызовите generateUUID и используйте новую строку в WHERE. Почему квитанция не найдётся?
Ожидаемое решение
Чтение должно использовать ранее сохранённый идентификатор операции. Новый вызов создаёт другой ID, а не восстанавливает прежний. Для повторного HTTP-запроса ID передают явно и проверяют право пользователя на эту операцию; знание ID само по себе не предоставляет доступ к её данным.
Завершить worker и различить close пула и запрет новой работы
Worker закончил последнюю выгрузку и должен остановиться. Нужно сохранить её квитанцию, закрыть управляемые соединения и не принять новое бизнес-задание. Проверим отдельно завершённую запись, судьбу старой Session и реальное поведение того же Client после close.
- Первое задание сохраняет catalog-export: 2 позиции, done
- Приложение меняет acceptingJobs на false
- Client::close закрывает текущие соединения и здесь возвращает null
- Старый объект Session получает ошибку 10056
- Новый checkout этого Client работает, но флаг приложения запрещает новое задание
Сначала короткое действие
$acceptingJobs = false; // Сначала прекращаем принимать новую работу.
$client->close(); // После завершения активных заданий закрываем соединения.
// Не используем старые Session и не считаем Client необратимо уничтоженным.close без аргументов относится ко всему Client. В выбранной реализации он закрывает уже управляемые соединения, включая ещё выданную Session. Поэтому программа сначала проверяет, что работа закончена, затем закрывает пул. Флаг acceptingJobs — обычное логическое значение приложения, а не свойство Client. Дополнительный checkout в полном файле нужен только для демонстрации контракта; обычный завершающийся worker после close больше работу не начинает.
Что получится
| Наблюдение | Результат |
|---|---|
| Завершённое задание | catalog-export |
| Сохранённых позиций | 2 |
| Состояние квитанции | done |
| Фактический тип возврата Client::close | null |
| Старая Session отклоняет запрос с 10056 | да |
| Новая Session того же Client прочитала квитанцию | да |
| Приложение прекратило приём новых заданий | да |
| Лишняя запись не создана | да |
Самостоятельный файл и архив для запуска
<?php
declare(strict_types=1);
require __DIR__.'/lab.php';
$config = x144Config();
$session = mysql_xdevapi\getSession($config['uri']);
$schema = null;
$owned = [];
$removed = [];
$rows = $checks = $observations = [];
try {
$schema = $session->getSchema($config['schema']);
if (!$schema->existsInDatabase()) {
throw new RuntimeException('Учебная схема не существует: подготовьте её отдельно.');
}
$runtime = ['php'=>PHP_VERSION, 'extension'=>phpversion('mysql_xdevapi'),
'server_version'=>$session->getServerVersion(), 'schema'=>$config['schema'],
'host'=>$config['host'], 'port'=>$config['port']];
$name = x144OwnTable($session, $schema, $owned, 'shutdown_receipt',
'(id INT PRIMARY KEY, task_name VARCHAR(40) NOT NULL, items INT NOT NULL, state VARCHAR(12) NOT NULL) ENGINE=InnoDB');
$qualified = x144Qualified($schema, $name);
$options = ['enabled'=>true, 'maxSize'=>1, 'queueTimeOut'=>250];
$client = mysql_xdevapi\getClient($config['uri'], json_encode($options, JSON_THROW_ON_ERROR));
$worker = $fresh = null;
$acceptingJobs = true;
$blockedNewJob = false;
try {
$worker = $client->getSession();
if ($acceptingJobs) {
$worker->sql('INSERT INTO '.$qualified." (id, task_name, items, state) VALUES (1, 'catalog-export', 2, 'done')")->execute();
}
$completed = $worker->sql('SELECT task_name, items, state FROM '.$qualified.' WHERE id = 1')->execute()->fetchOne();
$acceptingJobs = false;
$closeResult = $client->close();
$oldFailure = x144Failure(static function () use ($worker, $qualified): void {
$worker->sql('SELECT state FROM '.$qualified.' WHERE id = 1')->execute()->fetchOne();
});
// Диагностика фактического контракта: тот же Client способен выдать новую Session.
$fresh = $client->getSession();
$saved = $fresh->sql('SELECT task_name, items, state FROM '.$qualified.' WHERE id = 1')->execute()->fetchOne();
if ($acceptingJobs) {
$fresh->sql('INSERT INTO '.$qualified." (id, task_name, items, state) VALUES (2, 'unexpected', 1, 'done')")->execute();
} else {
$blockedNewJob = true;
}
$newJob = $fresh->sql('SELECT id FROM '.$qualified.' WHERE id = 2')->execute()->fetchOne();
} finally {
try { if ($fresh !== null) { $fresh->close(); } }
finally { $client->close(); }
}
$codes = array_column($oldFailure ?? [], 'code');
$rows = [['Завершённое задание', $saved['task_name']], ['Сохранённых позиций', $saved['items']], ['Состояние квитанции', $saved['state']],
['Фактический тип возврата Client::close', get_debug_type($closeResult)],
['Старая Session отклоняет запрос с 10056', in_array(10056, $codes, true)],
['Новая Session того же Client прочитала квитанцию', $saved === $completed],
['Приложение прекратило приём новых заданий', !$acceptingJobs && $blockedNewJob],
['Лишняя запись не создана', $newJob === null]];
$checks = ['completed_receipt'=>$saved === ['task_name'=>'catalog-export','items'=>2,'state'=>'done'],
'close_actual_null'=>$closeResult === null, 'old_session_closed'=>in_array(10056, $codes, true),
'client_reopens'=>$saved === $completed, 'application_stopped'=>!$acceptingJobs && $blockedNewJob,
'no_new_work'=>$newJob === null];
$observations = ['options'=>$options, 'completed_before_close'=>$completed, 'client_close_return'=>$closeResult,
'client_close_type'=>get_debug_type($closeResult), 'old_session_failure'=>$oldFailure, 'fresh_session_receipt'=>$saved,
'accepting_jobs'=>$acceptingJobs, 'new_job'=>$newJob];
} finally {
try {
if ($schema instanceof mysql_xdevapi\Schema) {
$removed = x144Cleanup($schema, $owned);
}
} finally {
$session->close();
}
}
x144Report('mysql144-x-client-shutdown', 'Завершить worker и различить close пула и запрет новой работы', $rows, $checks, $observations, $runtime, $owned, $removed);
Ожидаемый результат
Завершённое задание — catalog-export
Сохранённых позиций — 2
Состояние квитанции — done
Фактический тип возврата Client::close — null
Старая Session отклоняет запрос с 10056 — да
Новая Session того же Client прочитала квитанцию — да
Приложение прекратило приём новых заданий — да
Лишняя запись не создана — да
Запустите отдельным процессом PHP по README. lab.php создаёт одну собственную таблицу; программа сохраняет и перечитывает строки, освобождает Session/Client и удаляет свою таблицу. result.html показывает результат, result.json — реальные данные и безопасную диагностику, metrics.json — проверки.
Таблица сохранённого запуска · Прочитанные строки и наблюдения.
Границы и ошибки
PHP Manual описывает bool и true, но PECL mysql_xdevapi 8.0.30 на PHP 8.3.35 здесь фактически возвращает null. При этом старый запрос получает 10056, а новый checkout того же Client работает: close не следует выдавать за постоянный запрет открытия. Во время незавершённой транзакции закрытие Client лишило бы задание рабочего соединения; сначала явно commit/rollback и завершение заданий. Пример не является многопоточным диспетчером: для настоящих параллельных worker нужно согласовать остановку и дождаться их завершения.
Попробуйте изменить
После close значение acceptingJobs случайно снова стало true. Гарантирует ли сам Client, что новая запись не появится?
Ожидаемое решение
Нет. Проверенный Client способен открыть новую Session после close. За прекращение приёма новых задач отвечает жизненный цикл приложения. Не используйте возвращаемое значение close как единственное доказательство остановки; проверяйте состояние worker и завершение уже начатых заданий.
Найти класс, метод или свободную функцию
| API | Для чего |
|---|---|
| Client | Ограничить число одновременно выданных подключений и повторно использовать их между заданиями. |
| Client::__construct | Понять, откуда берётся настроенный Client, когда обычный new недоступен. |
| Client::getSession | Взять одно рабочее соединение из Client, выполнить своё задание и вернуть его. |
| Client::close | Завершить управляемые соединения после последнего задания и проверить реальный эффект закрытия. |
| Session::__construct | Получить пригодный для SQL сеанс через фабрику вместо создания пустого объекта. |
| Exception | Разобрать отказ базы так, чтобы откатить работу, вернуть ресурс и сохранить полезную безопасную причину. |
| Session::generateUUID | Заранее присвоить операции идентификатор и связать с ним запись, журнал и последующее чтение. |
| Функции и фабрики расширения | Выбрать свободную функцию расширения: подключение, SQL-выражение или фабрику пула. |
От независимых Session к согласованной работе с таблицей
Теперь можно ограничить выдачу подключений, сохранить квитанцию, откатить частичное задание и вернуть ресурс. Следующий шаг — правильные фабрики табличных команд и блокировки для нескольких участников. Если два заказа претендуют на один остаток, одного пула недостаточно: нужно согласовать чтение и запись. Для SQL-фильтрации, выбранных столбцов и параметров используйте реляционный каталог и запросы по задаче.
Функции и методы этой темы
Откройте карточку, чтобы увидеть договор функции, примеры, ограничения и связанные инструменты.
mysql_xdevapi\ClientОграничить число одновременно выданных подключений и повторно использовать их между заданиями.
Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13. · mysql_xdevapi
class mysql_xdevapi\Client {
private function __construct();
public function getSession(): mysql_xdevapi\Session;
public function close(): bool; // Сигнатура Manual; фактическое отличие разобрано ниже.
}Ограничить число одновременно выданных подключений и повторно использовать их между заданиями.
- getClient получает URI и конфигурацию пула
- getSession выдаёт рабочую Session
- Задание записывает и читает нужные строки
- Session::close возвращает ресурс; Client::close завершает текущие соединения
Синтаксис, вход и результат
class mysql_xdevapi\Client {
private function __construct();
public function getSession(): mysql_xdevapi\Session;
public function close(): bool; // Сигнатура Manual; фактическое отличие разобрано ниже.
}| Параметр / часть контракта | Значение |
|---|---|
| Создание | Client получают фабрикой mysql_xdevapi\getClient с URI и опциями, а не публичным new. |
| getSession() | Выдать Session для конкретного задания; этот метод не принимает URI. |
| close() | Закрыть соединения, которыми управляет Client. Поведение старой и новой Session показано отдельно. |
| Отношение к Session | Client управляет выдачей ресурса; SQL и транзакции выполняются через выданную Session. |
Возвращаемое значение. Сам класс не является квитанцией записи. Фабрика возвращает Client, checkout возвращает Session; SQL и его результат получают следующими вызовами. bool в synopsis close не совпадает с null выбранной реализации.
Два практических применения
Для последовательных выгрузок один Client ограничивает одновременно занятую ёмкость и обслуживает разные задания. Полная программа сохраняет две квитанции.
$client = mysql_xdevapi\getClient($uri, '{"enabled":true,"maxSize":1,"queueTimeOut":250}');
$worker = $client->getSession(); // SQL выполняется через worker.Исходные данные, полный запуск и результат.
Два одновременно выданных соединения позволяют отдельно показать незавершённую запись кладовщика и чтение другого участника.
$first = $client->getSession();
$second = $client->getSession();
// При maxSize = 2 обе Session одновременно выполняют собственную работу.Исходные данные, полный запуск и результат.
Условия, ошибки и альтернативы
Пул не добавляет параллельности обычному последовательному PHP-коду и не является общесистемным лимитом всех worker. В выбранном расширении внутреннее состояние Client связано с URI внутри процесса: повторное создание с тем же URI может использовать уже имеющееся состояние и прежние опции. Создавайте Client один раз для жизненного цикла обработчика; каждый учебный архив запускайте отдельным процессом.
Полные контракты: Client::__construct, Client::getSession, Client::close. Настройки фабрики разобраны в каталоге функций и фабрик.
Версии. Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13.
- PHP Manual — Client
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
mysql_xdevapi\Client::__constructПонять, откуда берётся настроенный Client, когда обычный new недоступен.
Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13. · mysql_xdevapi
private mysql_xdevapi\Client::__construct()Понять, откуда берётся настроенный Client, когда обычный new недоступен.
- Приложение выбирает URI и лимит 1
- getClient получает эти аргументы
- Внутреннее создание подготавливает Client
- Приложение вызывает getSession для своей задачи
Синтаксис, вход и результат
private mysql_xdevapi\Client::__construct()| Параметр / часть контракта | Значение |
|---|---|
| Параметры | У приватного конструктора в Manual нет параметров. |
| private | Внешний код не вызывает этот конструктор; строку подключения и опции принимает фабрика getClient. |
| Подготовка | URI берётся из конфигурации. JSON-строка опций описывает пул, не произвольные свойства объекта. |
Возвращаемое значение. Конструктор не имеет отдельного возвращаемого значения и не является публичным способом получения Client. Результат фабрики — настроенный mysql_xdevapi\Client.
Два практических применения
Первый обработчик получает Client через фабрику, затем сохраняет квитанции двух последовательных заданий.
$client = mysql_xdevapi\getClient($uri, '{"maxSize":1,"queueTimeOut":250}');
$worker = $client->getSession();Исходные данные, полный запуск и результат.
Когда лимит задаёт конфигурация сервиса, сначала формируем понятный PHP-массив, затем передаём корректную JSON-строку фабрике. Занятый слот даёт конечный отказ.
$settings = ['enabled'=>true, 'maxSize'=>1, 'queueTimeOut'=>250];
$client = mysql_xdevapi\getClient($uri, json_encode($settings, JSON_THROW_ON_ERROR));Исходные данные, полный запуск и результат.
Условия, ошибки и альтернативы
new mysql_xdevapi\Client не является заменой фабрики. Приватный конструктор не выдаёт прикладному коду возможность вручную настроить незавершённый объект. В Manual у страницы конструктора нет собственного описания всех опций getClient; ниже они отдельно восстановлены по matching PECL, а не выданы за параметры __construct.
Сравните с получением Session и фабриками команд и результатов.
Версии. Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13.
- PHP Manual — Client::__construct
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
mysql_xdevapi\Client::getSessionВзять одно рабочее соединение из Client, выполнить своё задание и вернуть его.
Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13. · mysql_xdevapi
public mysql_xdevapi\Client::getSession(): mysql_xdevapi\SessionВзять одно рабочее соединение из Client, выполнить своё задание и вернуть его.
- Задание вызывает client->getSession()
- Свободное соединение или разрешённое новое выдаётся заданию
- Session выполняет SQL и читает результат
- finally возвращает Session; ожидающее задание получает возможность продолжить
Синтаксис, вход и результат
public mysql_xdevapi\Client::getSession(): mysql_xdevapi\Session| Параметр / часть контракта | Значение |
|---|---|
| Параметры | Нет. URI, enabled и лимиты уже заданы при создании Client. |
| Занятый пул | При включённом пуле и исчерпанном maxSize вызов ожидает свободное соединение в пределах queueTimeOut. |
| Завершение задания | Session::close возвращает взятое соединение. Незавершённую транзакцию сначала явно commit/rollback. |
Возвращаемое значение. mysql_xdevapi\Session для SQL, коллекций, таблиц и транзакций. При ошибке подключения или истечении ожидания возникает исключение; это не пустая строка результата и не false вместо Session.
Два практических применения
Короткое чтение количества затем расширяется до записи и чтения реальной квитанции одного задания.
$worker = $client->getSession();
try {
$row = $worker->sql('SELECT 2 AS items')->execute()->fetchOne();
} finally {
$worker->close();
}Исходные данные, полный запуск и результат.
В сервисе с ограничением подключений важна последовательность возврата. Полная программа показывает отказ до возврата и успешное задание после него.
$held = $client->getSession();
// При maxSize = 1 следующий checkout ждёт, пока held вернут.
$held->close();
$next = $client->getSession();Исходные данные, полный запуск и результат.
Условия, ошибки и альтернативы
В заголовке официальной страницы есть имя Client::getClient, но synopsis и реальный метод называются Client::getSession. Здесь используется именно getSession. Нельзя подменять два одновременно выданных подключения двумя обёртками getSession от Table/Schema: последние могут владеть тем же соединением. Для долговременного снимка транзакции правила чтения остаются правилами InnoDB.
Один и тот же термин Session встречается в навигации по связанным объектам; сравните модель владения. Завершение checkout — Session::close в PHP Manual.
Версии. Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13.
- PHP Manual — Client::getSession
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
mysql_xdevapi\Client::closeЗавершить управляемые соединения после последнего задания и проверить реальный эффект закрытия.
Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13. · mysql_xdevapi
public mysql_xdevapi\Client::close(): bool // Официальная сигнатура.Завершить управляемые соединения после последнего задания и проверить реальный эффект закрытия.
- Квитанция catalog-export уже сохранена
- Приложение прекращает принимать задания
- close закрывает существующие соединения Client
- Старый запрос получает 10056; новый checkout того же Client возможен
Синтаксис, вход и результат
public mysql_xdevapi\Client::close(): bool // Официальная сигнатура.| Параметр / часть контракта | Значение |
|---|---|
| Параметры | Нет. |
| Момент вызова | После завершения работ, commit/rollback и остановки приёма новых заданий. |
| Область действия | Закрываются соединения Client, в том числе выданные активным Session; это шире, чем возврат одного checkout. |
Возвращаемое значение. PHP Manual обещает true после закрытия. В проверенной связке PHP 8.3.35 / PECL mysql_xdevapi 8.0.30 фактическое значение null: wrapper не устанавливает return. Закрытие доказывается состоянием старой Session, а не сравнением результата с true.
Два практических применения
Окончание ограниченной серии экспортов освобождает ресурсы после того, как квитанции каждого задания уже сохранены.
try {
// Последовательно завершаем и освобождаем Session заданий.
} finally {
$client->close();
}Исходные данные, полный запуск и результат.
Для остановки worker отдельно задаём запрет новой работы и наблюдаем судьбу прежнего соединения. Полный пример проверяет квитанцию и отсутствие лишнего задания.
$acceptingJobs = false;
$result = $client->close();
echo get_debug_type($result); // null в проверенной реализацииИсходные данные, полный запуск и результат.
Условия, ошибки и альтернативы
Это не необратимое уничтожение объекта: в наблюдаемой версии тот же Client после close снова выдаёт Session. Не используйте close как контроль доступа, распределённый сигнал остановки или автоматический commit. Если несколько объектов Client разделяют внутреннее состояние одного URI, завершение общего пула надо координировать на уровне приложения. Отличие возврата и повторного открытия явно ограничено исследованной версией.
Возврат одного соединения выполняет Session::close; ошибочное задание сначала откатывается.
Версии. Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13.
- PHP Manual — Client::close
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
mysql_xdevapi\Session::__constructПолучить пригодный для SQL сеанс через фабрику вместо создания пустого объекта.
Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13. · mysql_xdevapi
private mysql_xdevapi\Session::__construct()Получить пригодный для SQL сеанс через фабрику вместо создания пустого объекта.
- getSession(uri) даёт отдельную Session для подготовки учебных данных
- getClient(uri, options) готовит пул для заданий
- client->getSession() выдаёт рабочую Session
- Все запросы и транзакции выполняются через конкретную полученную Session
Синтаксис, вход и результат
private mysql_xdevapi\Session::__construct()| Параметр / часть контракта | Значение |
|---|---|
| Параметры | Нет в официальной сигнатуре приватного конструктора. |
| Прямое подключение | mysql_xdevapi\getSession($uri) создаёт Session по настройкам подключения. |
| Подключение через Client | $client->getSession() получает Session из настроенного пула без повторной передачи URI. |
Возвращаемое значение. Конструктор не возвращает отдельное значение и недоступен извне. Оба показанных публичных маршрута дают объект Session; механизм владения и закрытия соединения у маршрутов отличается.
Два практических применения
Для одноразовой подготовки или короткого чтения подходит непосредственный сеанс. Полный архив использует такой сеанс для собственной таблицы и уборки.
$setup = mysql_xdevapi\getSession($uri);
try {
$row = $setup->sql('SELECT 2 AS items')->execute()->fetchOne();
} finally {
$setup->close();
}Исходные данные, полный запуск и результат.
Через Client можно одновременно получить два реальных участника пересчёта остатка. Нужна проверка данных, а не только создание двух PHP-переменных.
$first = $client->getSession();
$second = $client->getSession();
$first->startTransaction();
// Работа first не является транзакцией second.Исходные данные, полный запуск и результат.
Условия, ошибки и альтернативы
Не вызывайте new Session и не путайте эту Session с HTTP-сессией PHP и массивом $_SESSION: она относится к соединению с MySQL. Если получаете дополнительную обёртку через Table::getSession, держите её живой до окончания работы: в выбранной версии временная обёртка может закрыть общее соединение при уничтожении. Эта глава использует явно удерживаемые Session.
Начальная настройка URI и X Protocol — подключение к учебной базе.
Версии. Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13.
- PHP Manual — Session::__construct
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
mysql_xdevapi\ExceptionРазобрать отказ базы так, чтобы откатить работу, вернуть ресурс и сохранить полезную безопасную причину.
PHP Manual указывает PECL mysql-xdevapi >= 8.0.11. Поведение конкретных отказов проверяется на mysql_xdevapi 8.0.30. · mysql_xdevapi
class mysql_xdevapi\Exception extends \RuntimeException implements \Throwable {}Разобрать отказ базы так, чтобы откатить работу, вернуть ресурс и сохранить полезную безопасную причину.
- INSERT пытается повторить первичный ключ id = 1
- Расширение создаёт исключение вместо успешной квитанции
- Обработчик сохраняет безопасные code/message и при необходимости previous
- rollback отменяет partial; finally возвращает Session следующему заданию
Синтаксис, вход и результат
class mysql_xdevapi\Exception extends \RuntimeException implements \Throwable {}| Параметр / часть контракта | Значение |
|---|---|
| Собственные методы | В официальном synopsis класс не объявляет дополнительных методов. |
| getMessage() / getCode() | Унаследованные сообщение и код. Сообщение сначала очищают от URI и пароля. |
| getPrevious() | Предыдущее исключение либо null. Серверная причина иногда находится под общей ошибкой wrapper. |
| getFile(), getLine(), getTrace(), getTraceAsString() | Унаследованная диагностика места возникновения; её не следует публиковать посетителям. |
| __toString() | Унаследованное строковое представление включает сообщение и служебную диагностику, в том числе трассировку. Не выводите объект ошибки напрямую посетителю. |
| catch | catch (mysql_xdevapi\Exception $error) обрабатывает исключение расширения. Ошибки PHP и собственные исключения приложения могут иметь другие типы. |
Возвращаемое значение. Это объект ошибки, а не SQL-результат. Исключение прерывает обычную последовательность до подходящего catch. Чтение getPrevious позволяет исследовать цепочку, но наличие внутреннего исключения не гарантируется.
Два практических применения
При неудачном импорте важно не только показать ошибку, но и убрать частичную запись. Полный пример проверяет старую строку, отсутствие partial и успешное следующее задание.
try {
$statement->execute();
} catch (mysql_xdevapi\Exception $error) {
$worker->rollback();
$safeMessage = x144SafeMessage($error->getMessage());
}Исходные данные, полный запуск и результат.
При исчерпанном пуле полезно отличить отказ выдачи соединения от отсутствия строки в таблице. В данном профиле сохраняется код 10055, а URI редактируется до записи файла.
$failure = x144Failure(static function () use ($client): void {
$checkout = $client->getSession();
$checkout->close();
});
// failure содержит безопасную цепочку или null при успехе.Исходные данные, полный запуск и результат.
Условия, ошибки и альтернативы
Не ищите SQLSTATE через несуществующий здесь специальный метод и не классифицируйте все ошибки только текстовым совпадением. В цепочке могут быть общий код обёртки и серверный код. Для дубликата пример ищет 1062 в цепочке; неожиданный код не выдаётся за ожидаемый конфликт. Полный stack trace или исходный getMessage может раскрывать конфигурацию. x144Failure — обычная функция lab.php, не метод расширения; она ловит только ожидаемый тип, а не молча подавляет любые Throwable.
До этой карточки полезно разобрать исключения, try/catch и finally. Наследуемый контракт опубликован в RuntimeException и Exception.
Версии. PHP Manual указывает PECL mysql-xdevapi >= 8.0.11. Поведение конкретных отказов проверяется на mysql_xdevapi 8.0.30.
- PHP Manual — Exception
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
mysql_xdevapi\Session::generateUUIDЗаранее присвоить операции идентификатор и связать с ним запись, журнал и последующее чтение.
Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13. · mysql_xdevapi
public mysql_xdevapi\Session::generateUUID(): stringЗаранее присвоить операции идентификатор и связать с ним запись, журнал и последующее чтение.
- Session генерирует operation_id
- Один и тот же ID помещается в задание и INSERT
- Квитанция сохраняется под этим первичным ключом
- Следующий запрос WHERE operation_id = ? находит именно её
Синтаксис, вход и результат
public mysql_xdevapi\Session::generateUUID(): string| Параметр / часть контракта | Значение |
|---|---|
| Параметры | Нет. |
| Время генерации | До INSERT, когда идентификатор нужен уже для задания или журнала. |
| Сохранение | Полученную строку передают дальше без повторной генерации; в примере она хранится в CHAR(32) PRIMARY KEY. |
Возвращаемое значение. Manual описывает UUID длиной 32 символа и ссылается на RFC 4122. Наблюдаемый результат — строка из 32 заглавных шестнадцатеричных символов без дефисов. Фактический ID конкретного запуска сохраняется в JSON.
Два практических применения
Создаём номер до записи, чтобы журнал старта и сохранённая квитанция обозначали одну синхронизацию.
$operationId = $worker->generateUUID();
$request = ['operation_id'=>$operationId, 'task_name'=>'catalog-sync'];Исходные данные, полный запуск и результат.
Переданный клиенту номер позволяет позднее получить состояние ровно одной операции. Генератор на этапе чтения не вызывается; состояние связано с сохранённым значением.
$receipt = $reader->sql('SELECT operation_id, state FROM '.$qualified.' WHERE operation_id = ?')
->bind($operationId)->execute()->fetchOne();Исходные данные, полный запуск и результат.
Условия, ошибки и альтернативы
Метод не сохраняет запись сам, не создаёт AUTO_INCREMENT и не выдаёт ID результата CollectionAdd. Реализация использует собственную компоновку UUID X DevAPI; не подменяйте её обещанием UUIDv4 или секретного криптографического токена. Генерация двух разных строк не доказывает математическую уникальность всех будущих результатов. Публичный ID не заменяет проверку пользователя и доступа к записи. На закрытой Session работа может завершиться ошибкой: сначала нужен живой сеанс.
Обработка повторного запроса требует отдельной логики идентичности и обновления; UUID сам по себе не делает произвольный импорт идемпотентным.
Версии. Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13.
- PHP Manual — Session::generateUUID
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
mysql_xdevapi: функции и фабрикиВыбрать свободную функцию расширения: подключение, SQL-выражение или фабрику пула.
Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13. · mysql_xdevapi
// Два пункта официального каталога функций:
mysql_xdevapi\getSession(string $uri): mysql_xdevapi\Session
mysql_xdevapi\expression(string $expression): mysql_xdevapi\Expression
// Дополнительная фабрика, подтверждённая исходником PECL 8.0.30:
mysql_xdevapi\getClient($uri, $options = null) // Возвращает Client.
// $uri — строка; $options — плоская JSON-строка либо null.Выбрать свободную функцию расширения: подключение, SQL-выражение или фабрику пула.
- Нужно одно подключение → getSession(uri)
- Нужно выражение вычислить на сервере → expression(text)
- Нужна управляемая выдача подключений → getClient(uri, options)
- Client выдаёт Session; Session выполняет SQL и возвращает данные
Синтаксис, вход и результат
// Два пункта официального каталога функций:
mysql_xdevapi\getSession(string $uri): mysql_xdevapi\Session
mysql_xdevapi\expression(string $expression): mysql_xdevapi\Expression
// Дополнительная фабрика, подтверждённая исходником PECL 8.0.30:
mysql_xdevapi\getClient($uri, $options = null) // Возвращает Client.
// $uri — строка; $options — плоская JSON-строка либо null.| Параметр / часть контракта | Значение |
|---|---|
| getSession: uri | Строка параметров непосредственного подключения. Возвращается Session; подробный разбор URI находится в начальной главе. |
| expression: expression | Строка выражения в языке запроса X DevAPI. Возвращается Expression, а не вычисленное PHP-значение и не безопасная подстановка пользовательского текста. |
| getClient: uri | Обязательная строка подключения. Не выводите её в результат и не храните секреты в скачиваемом примере. |
| getClient: options | Необязательная JSON-строка или null; matching 8.0.30 принимает настройки на верхнем уровне JSON-объекта. |
| enabled | Логическое значение: включён ли пул; по умолчанию true. При false выдача соединений не использует ограничение пула. |
| maxSize | Положительное целое, по умолчанию 25: предел одновременно открытых управляемых соединений пула. |
| maxIdleTime | Неотрицательное целое число миллисекунд простоя возвращённого соединения; по умолчанию 0. Это не время удержания Session заданием. |
| queueTimeOut | Неотрицательное целое число миллисекунд ожидания свободного слота; по умолчанию 0 — неограниченное ожидание в выбранной реализации. |
Возвращаемое значение. getSession → Session; expression → Expression; getClient → Client. Фабрика Client не выполняет прикладной INSERT и не возвращает строки базы. Недопустимая конфигурация и отказ подключения сообщаются отдельно от результата SQL.
Два практических применения
Фоновый обработчик берёт ограниченный Client для серии заданий и сохраняет две настоящие квитанции.
$client = mysql_xdevapi\getClient($uri, '{"enabled":true,"maxSize":1,"queueTimeOut":250}');
$worker = $client->getSession();Исходные данные, полный запуск и результат.
Когда задача требует двух независимых участников, одна конфигурация Client выдаёт две одновременно удерживаемые Session, а запросы показывают независимость транзакций.
$first = $client->getSession();
$second = $client->getSession();
// С maxSize = 2 проверяем видимость записи до и после commit.Исходные данные, полный запуск и результат.
Условия, ошибки и альтернативы
Текущий официальный каталог содержит только expression и getSession. Отсутствие getClient в этом списке и отсутствие отдельной страницы функции не доказывают отсутствия реализации: она зарегистрирована в PECL 8.0.30. Сигнатура фабрики и опции здесь взяты из соответствующего исходника и фактического запуска. В общем руководстве X DevAPI встречаются вложенный объект pooling и имя queueTimeout: их нельзя буквально переносить в выбранное PHP-расширение с flat options и queueTimeOut. Нулевые тайм-ауты и maxIdleTime не следует трактовать как тайм-аут выполнения SQL.
Полные базовые карточки: getSession: URI и соединение; expression: выражение и bind. Общий справочник класса — Client.
Версии. Официальная страница не сообщает номер первой версии. Примеры относятся к PECL mysql_xdevapi 8.0.30, PHP 8.3.35 и MySQL 8.4.13.
- PHP Manual — Functions
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
Источники и обсуждения
- PHP Manual — Client
- PHP Manual — Client::__construct
- PHP Manual — Client::getSession
- PHP Manual — Client::close
- PHP Manual — Session::__construct
- PHP Manual — Exception
- PHP Manual — Session::generateUUID
- PHP Manual — Functions
- MySQL X DevAPI: модель пула подключений
- PHP Manual: закрытие Session
- PHP Manual: непосредственное подключение getSession
- MySQL 8.4: согласованное чтение InnoDB
- MySQL 8.4: неявный commit при DDL
- MySQL X DevAPI: управление транзакциями
- PHP Manual: RuntimeException
- PECL mysql_xdevapi 8.0.30: исходники Client и Session
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


