Рекурсия в подготовленных операторах

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

Основная логика кода такова.

function someFunction($something) {
  global $pdo;

  $array = array();

  static $handle = null;
  if (!$handle) {
    $handle = $pdo->prepare("A STATEMENT WITH :a_param");
  }

  $handle->bindValue(":a_param", $something);
  if ($handle->execute()) {
    while ($row = $handle->fetch()) {
      $array[] = someFunction($row['blah']);
    }
  }

  return $array;
}

Это выглядело хорошо для меня, но в нем пропустили много строк. В конце концов я понял, что дескриптор оператора был изменен (выполнен с другим параметром), что означает, что вызов fetch в цикле while будет работать только один раз, затем функция снова вызывает себя, и результирующий набор изменяется.

Поэтому мне интересно, как лучше всего использовать подготовленные операторы PDO рекурсивным способом.

Одним из способов может быть использование fetchAll(), но в руководстве сказано, что это сопряжено со значительными накладными расходами. Весь смысл в том, чтобы сделать его более эффективным.

Еще одна вещь, которую я мог бы сделать, это не использовать статический дескриптор повторно, а вместо этого каждый раз создавать новый. Я полагаю, что, поскольку строка запроса та же, внутренне драйвер MySQL все равно будет использовать подготовленный оператор, поэтому есть только небольшие накладные расходы на создание нового дескриптора для каждого рекурсивного вызова. Лично я думаю, что это побеждает суть.

Или есть способ переписать это?


person Rob    schedule 26.04.2010    source источник
comment
Я только что провел некоторое (не сверхнаучное) тестирование моего очень похожего кода рекурсивной функции и пришел к выводу, что для моих целей подготовленные операторы в целом медленнее. Какой смысл составлять выписку, если вам приходится заново готовить ее перед каждым использованием? Я должен что-то упустить.   -  person Joel Mellon    schedule 14.11.2012


Ответы (3)


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

На самом деле, PDO делает это автоматически, когда вы запускаете новую подготовку.

Когда вы вызываете функцию рекурсивно:

  • Начальный дескриптор выделяется (1)
  • Первая запись извлекается из (1)
  • Функция вызывается рекурсивно. Значение (1) находится в стеке рекурсии.
  • Новый дескриптор выделяется (2), аннулирует (1)
  • Первая запись извлекается из (2)
  • Функция возвращает
  • Вы пытаетесь получить следующую запись (1) и терпите неудачу, поскольку она недействительна.

Таким образом, MySQL не поддерживает рекурсию на своей стороне, а это значит, что вам придется делать это на стороне PHP, используя fetchAll.

person Quassnoi    schedule 26.04.2010
comment
Какую ручку вы имеете в виду? У вас может быть много дескрипторов подготовленных операторов в течение одного сеанса (я полагаю, что существует ограничение примерно в 500 подготовленных операторов на стороне сервера за сеанс и общее количество операторов `max_prepared_stmt_count`, но это не относится к подготовленным операторам, созданным с использованием клиентской библиотеки). ). Результаты не получают собственную обработку. - person outis; 28.04.2010

Настоящая проблема в том, что $handle является статическим. Статические переменные проблематичны для рекурсии, когда необходимо сохранить состояние при рекурсивном вызове, а не только для подготовленных операторов. В этом случае рекурсивный вызов выполняет новый запрос, отбрасывая предыдущее состояние. PDO::fetchAll действительно единственный вариант, если вам нужен один подготовленный запрос.

В зависимости от того, что представляет собой оператор, вы потенциально можете переписать его, чтобы возвращать все результаты сразу, после чего построить дерево.

person outis    schedule 26.04.2010
comment
@outis: $handle является статическим по причине: в сеансе может быть только один активный оператор. - person Quassnoi; 27.04.2010
comment
@Quassnoi: я понимаю, что есть причина, по которой $handle является статичным. Однако статические переменные просто не будут работать так, как хочет Роб, в рекурсивных вызовах. - person outis; 28.04.2010
comment
@Quassnoi: и что ты имеешь в виду под активным? В одном сеансе может быть много подготовленных операторов, хотя каждый из них будет содержать только один набор результатов. - person outis; 28.04.2010

Если вы используете одни и те же переменные (из-за значения привязки pdo), каждый раз значение совпадает с первым. Так что это будет НЕУДАЧНО:

foreach ($bind_params as $key => $value) {
    $stmt->bindParam(":$key", $value);
}

результат:

$key[0] = $value[0];
$key[1] = $value[0];
$key[2] = $value[0];
$key[3] = $value[0];

Итак, вы хотите сделать уродливый трюк, тогда:

        $i = 0;
        foreach ($bind_params as $key => $value) {
            $i++;
            $$i = $value;
            $stmt->bindParam(":$key", $$i);
        }

результат:

$key[0] = $value[0];
$key[1] = $value[1];
$key[2] = $value[2];
$key[3] = $value[3];
person hasandz    schedule 07.12.2014