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

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

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

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

Глава 131 · После подключения, реляционных таблиц и основ транзакций

MySQL X DevAPI: пул подключений и жизненный цикл Session

От двух последовательных заданий до независимых транзакций, конечного ожидания пула, отката ошибки, UUID квитанции и честного контракта Client::close.

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

Фоновый обработчик закончил выгрузку каталога, затем выгрузку квитанций. Для каждого задания нужно сохранить результат в базе. Если каждый путь выполнения забирает соединение и забывает вернуть его, спустя некоторое время следующему заданию нечем работать. В этой главе мы получим две сохранённые квитанции, затем разберём отказ занятого пула, откат неудачного задания и корректное завершение worker.

idЗаданиеПозицийСостояние
1catalog-export2done
2receipt-export3done

Это ожидаемый журнал работы, а не обещание сгенерировать файлы экспорта. Полные программы действительно записывают такие строки, перечитывают их и показывают HTML-таблицу результата. SQL выбирает только нужные поля и строки: работа с пулом не требует выгружать весь каталог в PHP.

Одно подключение для двух последовательных заданий
  1. getClient создаёт диспетчер с maxSize = 1
  2. Задание catalog-export вызывает getSession и получает соединение
  3. Session сохраняет items = 2, state = done и читает квитанцию
  4. Session::close возвращает соединение диспетчеру
  5. Следующее задание получает Session и сохраняет items = 3
  6. После последнего задания 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 и не превращают неожиданную ошибку в успешный результат.

Взять соединение для двух последовательных заданий

Фоновый обработчик закончил экспорт каталога из двух позиций, затем экспорт трёх квитанций. Здесь мы не создаём сам экспорт: сохраняем журнал его завершения. Одновременно работающему заданию достаточно одного подключения, но после каждого задания это подключение нужно вернуть следующему.

Путь данных программы
  1. Client допускает одно выданное соединение
  2. Задание 1 берёт Session и сохраняет catalog-export: 2 позиции
  3. close возвращает соединение пулу
  4. Задание 2 берёт Session и сохраняет receipt-export: 3 позиции
  5. Точечное чтение подтверждает каждую квитанцию; общий журнал содержит две строки

Сначала короткое действие

$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 одновременно и покажем, когда новый остаток становится доступен второму участнику.

Путь данных программы
  1. Таблица хранит LAMP: stock = 4
  2. Client выдаёт first и second с разными CONNECTION_ID
  3. first начинает транзакцию и записывает stock = 9
  4. first читает 9, а second отдельным запросом ещё читает 4
  5. 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
Своё изменение до commit9
Другой участник до commit4
Новый запрос другого участника после commit9
Сохранённый остаток9
Два одновременно выданных соединения различаютсяда
Самостоятельный файл и архив для запуска
Увидеть две независимые транзакционные 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, '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 есть только одно подключение. Он уже обрабатывает каталог, а другая часть программы пытается одновременно взять ещё одно. Нельзя бесконечно ждать ресурс, который этот же поток пока не возвращает. Зададим конечное ожидание, увидим точный отказ и завершим второе задание после возврата первого слота.

Путь данных программы
  1. maxSize = 1, queueTimeOut = 250 миллисекунд
  2. held занимает единственный слот и записывает working
  3. Второй checkout получает ошибку очереди 10055
  4. held завершает первое задание и выполняет close
  5. 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 только измеряет его.

Что получится

НаблюдениеРезультат
Лимит одновременных Session1
Отказ занятого пула распознанда
Первый слот возвращёнда
Первое задание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 следующему

Импорт успел сохранить первую служебную строку, но следующая запись столкнулась с уже существующим первичным ключом. Нужно отменить только незавершённую работу, сохранить прежние данные и освободить единственное подключение следующему заданию. Простого сообщения об ошибке недостаточно: покажем итоговые строки базы.

Путь данных программы
  1. До задания существует id = 1: existing
  2. Транзакция записывает id = 2: partial
  3. Повторный id = 1 вызывает ошибку уникального ключа
  4. rollback удаляет частичную запись id = 2; close возвращает Session
  5. Новое задание сохраняет 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
Самостоятельный файл и архив для запуска
Откатить частичное задание и вернуть 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, '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 прочитаем ровно нужную квитанцию.

Путь данных программы
  1. generateUUID создаёт operation_id до INSERT
  2. request содержит тот же ID и catalog-sync: 7 позиций
  3. bind передаёт ID отдельным SQL-значением
  4. Первое задание возвращает Session; другое получает её для чтения
  5. 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 получен до записида
Длина UUID32
Формат текущего генератора: 32 заглавных hex-символада
Найдена квитанция той же операциида
Заданиеcatalog-sync
Позиций синхронизации7
Состояниеdone
Самостоятельный файл и архив для запуска
Найти квитанцию по UUID, созданному до записиСамостоятельный пример
<?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.

Путь данных программы
  1. Первое задание сохраняет catalog-export: 2 позиции, done
  2. Приложение меняет acceptingJobs на false
  3. Client::close закрывает текущие соединения и здесь возвращает null
  4. Старый объект Session получает ошибку 10056
  5. Новый checkout этого Client работает, но флаг приложения запрещает новое задание

Сначала короткое действие

$acceptingJobs = false; // Сначала прекращаем принимать новую работу.
$client->close();       // После завершения активных заданий закрываем соединения.
// Не используем старые Session и не считаем Client необратимо уничтоженным.

close без аргументов относится ко всему Client. В выбранной реализации он закрывает уже управляемые соединения, включая ещё выданную Session. Поэтому программа сначала проверяет, что работа закончена, затем закрывает пул. Флаг acceptingJobs — обычное логическое значение приложения, а не свойство Client. Дополнительный checkout в полном файле нужен только для демонстрации контракта; обычный завершающийся worker после close больше работу не начинает.

Что получится

НаблюдениеРезультат
Завершённое заданиеcatalog-export
Сохранённых позиций2
Состояние квитанцииdone
Фактический тип возврата Client::closenull
Старая Session отклоняет запрос с 10056да
Новая Session того же Client прочитала квитанциюда
Приложение прекратило приём новых заданийда
Лишняя запись не созданада
Самостоятельный файл и архив для запуска
Завершить worker и различить close пула и запрет новой работыСамостоятельный пример
<?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; фактическое отличие разобрано ниже.
}

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

Как применяется инструмент
  1. getClient получает URI и конфигурацию пула
  2. getSession выдаёт рабочую Session
  3. Задание записывает и читает нужные строки
  4. 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 показано отдельно.
Отношение к SessionClient управляет выдачей ресурса; 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.

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 недоступен.

Как применяется инструмент
  1. Приложение выбирает URI и лимит 1
  2. getClient получает эти аргументы
  3. Внутреннее создание подготавливает Client
  4. Приложение вызывает 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.

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, выполнить своё задание и вернуть его.

Как применяется инструмент
  1. Задание вызывает client->getSession()
  2. Свободное соединение или разрешённое новое выдаётся заданию
  3. Session выполняет SQL и читает результат
  4. 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.

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 // Официальная сигнатура.

Завершить управляемые соединения после последнего задания и проверить реальный эффект закрытия.

Как применяется инструмент
  1. Квитанция catalog-export уже сохранена
  2. Приложение прекращает принимать задания
  3. close закрывает существующие соединения Client
  4. Старый запрос получает 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.

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 сеанс через фабрику вместо создания пустого объекта.

Как применяется инструмент
  1. getSession(uri) даёт отдельную Session для подготовки учебных данных
  2. getClient(uri, options) готовит пул для заданий
  3. client->getSession() выдаёт рабочую Session
  4. Все запросы и транзакции выполняются через конкретную полученную 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.

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 {}

Разобрать отказ базы так, чтобы откатить работу, вернуть ресурс и сохранить полезную безопасную причину.

Как применяется инструмент
  1. INSERT пытается повторить первичный ключ id = 1
  2. Расширение создаёт исключение вместо успешной квитанции
  3. Обработчик сохраняет безопасные code/message и при необходимости previous
  4. rollback отменяет partial; finally возвращает Session следующему заданию

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

class mysql_xdevapi\Exception extends \RuntimeException implements \Throwable {}
Параметр / часть контрактаЗначение
Собственные методыВ официальном synopsis класс не объявляет дополнительных методов.
getMessage() / getCode()Унаследованные сообщение и код. Сообщение сначала очищают от URI и пароля.
getPrevious()Предыдущее исключение либо null. Серверная причина иногда находится под общей ошибкой wrapper.
getFile(), getLine(), getTrace(), getTraceAsString()Унаследованная диагностика места возникновения; её не следует публиковать посетителям.
__toString()Унаследованное строковое представление включает сообщение и служебную диагностику, в том числе трассировку. Не выводите объект ошибки напрямую посетителю.
catchcatch (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.

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

Заранее присвоить операции идентификатор и связать с ним запись, журнал и последующее чтение.

Как применяется инструмент
  1. Session генерирует operation_id
  2. Один и тот же ID помещается в задание и INSERT
  3. Квитанция сохраняется под этим первичным ключом
  4. Следующий запрос 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.

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-выражение или фабрику пула.

Как применяется инструмент
  1. Нужно одно подключение → getSession(uri)
  2. Нужно выражение вычислить на сервере → expression(text)
  3. Нужна управляемая выдача подключений → getClient(uri, options)
  4. 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.

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

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

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

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

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