Подготовка операторов SQL с PDO

Мой код выглядит так:

// Connect to SQLite DB
DB('/path/to/sqlite.db');

DB('BEGIN TRANSACTION;');

// These loops are just examples.
for ($i = 1; $i <= 10000; $i++)
{
    for ($j = 1; $j <= 100; $j++)
    {
        DB('INSERT INTO "test" ("id", "name") VALUES (?, ?);', $i, 'Testing ' . $j);
    }
}

DB('END TRANSACTION;');

А вот функция DB():

function DB($query)
{
    static $db = null;

    if (is_file($query) === true)
    {
        $db = new PDO('sqlite:' . $query, null, null, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING));
    }

    else if (is_a($db, 'PDO') === true)
    {
        $result = $db->prepare($query);

        if (is_a($result, 'PDOStatement') === true)
        {
            if ($result->execute(array_slice(func_get_args(), 1)) === true)
            {
                if (stripos($query, 'INSERT') === 0)
                {
                    return $db->lastInsertId();
                }

                if (stripos($query, 'SELECT') === 0)
                {
                    return $result->fetchAll(PDO::FETCH_ASSOC);
                }

                return $result->rowCount();
            }
        }

        return false;
    }

    return true;
}

Проблема в том, что вызов DB() внутри внутреннего цикла выполняется слишком долго, я думал, что выполнение чего-то подобного может значительно ускорить выполнение:

DB('BEGIN TRANSACTION;');

for ($i = 1; $i <= 10000; $i++)
{
    $queries = array();

    for ($j = 1; $j <= 100; $j++)
    {
        $queries[] = 'INSERT INTO "test" ("id", "name") VALUES (?, ?);' /*, $i, 'Testing ' . $j;*/
    }

    DB(implode("\n", $queries));
}

DB('END TRANSACTION;');

Проблема в том, что я не знаю, как я могу подготовить (заменить и экранировать) все вопросительные знаки с соответствующими переменными, есть ли способ сделать это?


person Alix Axel    schedule 10.08.2009    source источник


Ответы (4)


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

$query = 'INSERT INTO "test" ("id", "name") VALUES ';
$data = array();
for ($i = 1; $i <= 10000; $i++) {
  for ($j = 1; $j <= 100; $j++) {
    $query .= '(?,?),';
    $data[] = $i;
    $data[] = 'Testing '.$j;
  }
}

$query = substr($query, 0, -1);
DB($query, $data);

Это должно устранить накладные расходы с одиночными запросами на вставку. Однако существует ограничение на длину запроса, если у вас есть проблемы с длиной запроса, попробуйте чаще выполнять вызовы DB() в цикле for.

person mike    schedule 10.08.2009
comment
Это было то, что я искал, однако я получаю странную ошибку с запросом INSERT OR REPLACE INTO test VALUES (?,?), (?,?), (?,?); = Предупреждение: PDO::prepare(): SQLSTATE[HY000]: Общая ошибка: 1 около ,: синтаксическая ошибка есть идеи, почему? - person Alix Axel; 10.08.2009
comment
PS: То же самое происходит с тестом INSERT INTO (id, name) VALUES (?, ?), (?, ?), (?, ?); - person Alix Axel; 10.08.2009
comment
Правильный SQL-это INSERT INTO test (id, name) SELECT ?, ? СОЮЗ ВЫБЕРИТЕ ?, ? ОБЪЕДИНЕНИЕ ВЫБРАТЬ?,?; - person Alix Axel; 10.08.2009

Почему вы используете подготовленные операторы, если вы «подготавливаете» их в цикле? (в функции БД)

Сделайте подготовку перед циклом, а в цикле просто введите значения и выполните. Конечно, это потребует перезаписи вашего метода БД.

О, и кстати. является ли ваш столбец идентификатора первичным ключом? если это так, у вас есть другая проблема, потому что «i» будет на 100 «j» таким же :)

Например:

$sth = $dbh->prepare('INSERT INTO "test" ("id", "name") VALUES (:id, :name)');
$j=0;
for ($i = 1; $i <= 10000; $i++){
   $j = ($j==100) ? 0 : $j++;
   $sth->execute(array(':id' => $i, ':name' => 'Testing ' . $j));     
}
person Rufinus    schedule 10.08.2009

К сожалению, я думаю, что проблема может быть в структуре вашего кода.

В вашем цикле операторов INSERT все операторы идентичны, и нет необходимости каждый раз вызывать $db->prepare. Идея подготовленных операторов заключается в том, что вы вызываете $db->prepare() один раз, а execute() можно вызывать несколько раз для одного и того же объекта оператора. Вы вызываете $db->prepare() каждый раз, что вызывает накладные расходы при анализе инструкции SQL и создании нового объекта.

Попробуйте переписать свою функцию DB() следующим образом:

function do_query($db, $pdo_statement, $query, $args)
{
    if ($pdo_statement->execute($args) === true)
    {
        if (stripos($query, 'INSERT') === 0)
        {
          return $db->lastInsertId();
        }
        if (stripos($query, 'SELECT') === 0)
        {
          return $result->fetchAll(PDO::FETCH_ASSOC);
        }
        return $result->rowCount();
    }
}

function DB($query)
{
    static $db = null;

    if (is_file($query) === true)
    {
      $db = new PDO('sqlite:' . $query, null, null, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING));
    }

    else if (is_a($db, 'PDO') === true)
    {
      $result = $db->prepare($query);

      if (is_a($result, 'PDOStatement') === true)
      {
        $args = func_get_args();
        if (is_array($args[1])) {
            $ret = array();
            foreach ($args[1] as $args) {
                $ret[] = do_query($db, $query, $result, $args);
            }
            return $ret;
        }

        return do_query($db, $query, $result, array_slice(func_get_args(), 1));
      }

      return false;
    }

    return true;
}

Итак, если вы хотите запустить тот же запрос с большим количеством значений, вы создаете двумерный массив значений, которые хотите вставить, и вызываете DB('INSERT INTO....', $values). Функция DB() проверяет, является ли второй параметр функции (после $query) массивом, и если это так, она выполняет циклический запуск $query со значениями в массиве. Таким образом, цикл не требует повторной подготовки оператора SQL каждый раз, а просто повторно выполняет его с другими значениями. Возвращаемым значением функции будет массив результатов каждого запроса.

person Rob Knight    schedule 10.08.2009

Первоначально опубликованная функция БД выдает системный вызов файловой системы stat() каждый раз, когда она запускается, чтобы проверить, является ли строка запроса файлом. Хотя это не является единственной причиной медленного выполнения, оно способствует этому.

person Wez Furlong    schedule 22.08.2009
comment
Я понимаю это, однако я закодировал эту функцию с учетом переносимости (одной функции, способной выполнять все распространенные операции с БД), а не скорости. Я решил эту проблему, используя предложение michal kralik, а также добавил все уже подготовленные запросы в статический массив, что сэкономило много времени на выполнение. - person Alix Axel; 22.08.2009