Поддержка PDO для нескольких запросов (PDO_MYSQL, PDO_MYSQLND)

102

Я знаю, что PDO не поддерживает выполнение нескольких запросов в одном операторе. Я поискал в Google и нашел несколько сообщений о PDO_MYSQL и PDO_MYSQLND.

PDO_MySQL - более опасное приложение, чем любые другие традиционные приложения MySQL. Традиционный MySQL допускает только один SQL-запрос. В PDO_MySQL такого ограничения нет, но вы рискуете получить несколько запросов.

Источник: Защита от внедрения SQL с помощью PDO и Zend Framework (июнь 2010 г .; автор Джулиан)

Кажется, что PDO_MYSQL и PDO_MYSQLND действительно поддерживают несколько запросов, но я не могу найти о них больше информации. Эти проекты были прекращены? Есть ли способ запустить несколько запросов с использованием PDO.

Gajus
источник
4
Используйте транзакции SQL.
tereško
Почему вы хотите использовать несколько запросов? Они не совершаются, это все равно, как если бы вы выполняли их одну за другой. ИМХО никаких плюсов, только минусы. В случае SQLInjection вы позволяете злоумышленнику делать все, что он хочет.
Mleko
Сейчас 2020 год, и PDO поддерживает это - см. Мой ответ ниже.
Андрис

Ответы:

141

Насколько я знаю, PDO_MYSQLNDзаменено PDO_MYSQLв PHP 5.3. Смущает то, что имя все ещеPDO_MYSQL . Итак, теперь ND является драйвером по умолчанию для MySQL + PDO.

В целом, для одновременного выполнения нескольких запросов вам необходимо:

  • PHP 5.3+
  • mysqlnd
  • Эмулировал подготовленные заявления. Убедитесь, что PDO::ATTR_EMULATE_PREPARESустановлено значение 1(по умолчанию). В качестве альтернативы вы можете избежать использования подготовленных операторов и использовать $pdo->execнапрямую.

Использование exec

$db = new PDO("mysql:host=localhost;dbname=test", 'root', '');

// works regardless of statements emulation
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

$db->exec($sql);

Использование операторов

$db = new PDO("mysql:host=localhost;dbname=test", 'root', '');

// works not with the following set to 0. You can comment this line as 1 is default
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 1);

$sql = "
DELETE FROM car; 
INSERT INTO car(name, type) VALUES ('car1', 'coupe'); 
INSERT INTO car(name, type) VALUES ('car2', 'coupe');
";

$stmt = $db->prepare($sql);
$stmt->execute();

Записка:

При использовании эмулированных подготовленных операторов убедитесь, что вы установили правильную кодировку (которая отражает фактическую кодировку данных) в DSN (доступно с версии 5.3.6). В противном случае может быть небольшая вероятность SQL-инъекции, если используется некоторая нечетная кодировка .

Сэм Дарк
источник
38
В самом ответе нет ничего плохого. В нем объясняется, как выполнять несколько запросов. Ваше предположение о том, что ответ ошибочен, исходит из предположения, что запрос содержит пользовательский ввод. Существуют допустимые варианты использования, когда одновременная отправка нескольких запросов может повысить производительность. Вы можете предложить использовать процедуры в качестве альтернативного ответа на этот вопрос, но это не делает этот ответ плохим.
Gajus
9
Код в этом ответе плохой и продвигает некоторые очень вредные практики (использование эмуляции для операторов подготовки, которые делают код открытым для уязвимости SQL-инъекций ). Не используйте это.
tereško
17
Ничего плохого в этом ответе, и в режиме эмуляции в частности. По умолчанию он включен в pdo_mysql, и если бы возникла проблема, уже были бы тысячи инъекций. Но никого еще не было. Такие дела.
Ваш здравый смысл
3
Фактически, ircmaxell был единственным, кому удалось вызвать не только эмоции, но и аргументы. Однако приведенные им ссылки совершенно неуместны. Первый вообще неприменим, поскольку в нем прямо говорится: «PDO всегда защищен от этой ошибки». А второй решаем просто, установив правильную кодировку. Так что это заслуживает примечания, а не предупреждения и менее привлекательного.
Ваш здравый смысл
6
Как человек, который пишет инструмент миграции, который использует SQL, написанный только нашими разработчиками (т.е. SQL-инъекция не является проблемой), это очень помогло мне, и любые комментарии, указывающие на то, что этот код вреден, не полностью понимают все контексты для его использования.
Люк
17

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

-

//This would run as expected:
$pdo->exec("valid-stmt1; valid-stmt2;");

-

//This would error out, as expected:
$pdo->exec("non-sense; valid-stmt1;");

-

//Here is the bug:
$pdo->exec("valid-stmt1; non-sense; valid-stmt3;");

Он выполнит "valid-stmt1;", остановится "non-sense;"и никогда не выдаст ошибку. Не буду запускать "valid-stmt3;", вернуть истину и солгать, что все прошло хорошо.

Я ожидал, что он выйдет из строя, "non-sense;"но это не так.

Вот где я нашел эту информацию: Недействительный запрос PDO не возвращает ошибку

Вот ошибка: https://bugs.php.net/bug.php?id=61613


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

try{
    // db connection
    $mysqli = new mysqli("host", "user" , "password", "database");
    if($mysqli->connect_errno){
        throw new Exception("Connection Failed: [".$mysqli->connect_errno. "] : ".$mysqli->connect_error );
        exit();
    }

    // read file.
    // This file has multiple sql statements.
    $file_sql = file_get_contents("filename.sql");

    if($file_sql == "null" || empty($file_sql) || strlen($file_sql) <= 0){
        throw new Exception("File is empty. I wont run it..");
    }

    //run the sql file contents through the mysqli's multi_query function.
    // here is where it gets complicated...
    // if the first query has errors, here is where you get it.
    $sqlFileResult = $mysqli->multi_query($file_sql);
    // this returns false only if there are errros on first sql statement, it doesn't care about the rest of the sql statements.

    $sqlCount = 1;
    if( $sqlFileResult == false ){
        throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], [".$mysqli->errno."]: '".$mysqli->error."' }");
    }

    // so handle the errors on the subsequent statements like this.
    // while I have more results. This will start from the second sql statement. The first statement errors are thrown above on the $mysqli->multi_query("SQL"); line
    while($mysqli->more_results()){
        $sqlCount++;
        // load the next result set into mysqli's active buffer. if this fails the $mysqli->error, $mysqli->errno will have appropriate error info.
        if($mysqli->next_result() == false){
            throw new Exception("File: '".$fullpath."' , Query#[".$sqlCount."], Error No: [".$mysqli->errno."]: '".$mysqli->error."' }");
        }
    }
}
catch(Exception $e){
    echo $e->getMessage(). " <pre>".$e->getTraceAsString()."</pre>";
}
Сай Фаниндер Редди Дж.
источник
Это работает, если вы работаете только $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");без двух предыдущих исполнителей? Я могу заставить его выдавать ошибки посередине, но не при выполнении после успешного выполнения .
Джефф Пакетт,
Нет, это не так. Это ошибка PDO.
Sai Phaninder Reddy J,
1
Плохо, что эти 3 $pdo->exec("")независимы друг от друга. Теперь я разделил их, чтобы указать, что они не должны быть в последовательности, чтобы возникла проблема. Эти 3 представляют собой 3 конфигурации выполнения нескольких запросов в одном операторе exec.
Sai Phaninder Reddy J,
Интересный. У вас была возможность увидеть мой опубликованный вопрос? Интересно, было ли это частично исправлено, потому что я могу получить сообщение об ошибке, если это единственныйexec на странице, но если я запустил несколько, execкаждый с несколькими операторами SQL в них, я воспроизвожу здесь ту же ошибку. Но если он единственный execна странице, то я не могу его воспроизвести.
Джефф Пакетт,
Возможно , что одна execна вашей странице есть несколько операторов?
Sai Phaninder Reddy J,
3

Быстрый и грязный подход:

function exec_sql_from_file($path, PDO $pdo) {
    if (! preg_match_all("/('(\\\\.|.)*?'|[^;])+/s", file_get_contents($path), $m))
        return;

    foreach ($m[0] as $sql) {
        if (strlen(trim($sql)))
            $pdo->exec($sql);
    }
}

Разбивается в разумных конечных точках оператора SQL. Нет ни проверки ошибок, ни защиты от впрыска. Перед тем, как использовать, изучите свое использование. Лично я использую его для заполнения необработанных файлов миграции для тестирования интеграции.

епископ
источник
1
Это не сработает, если ваш файл SQL содержит какие-либо встроенные команды mysql ... Это, вероятно, приведет к сбою и вашего лимита памяти PHP, если файл SQL большой ... Разделение на ;разрывы, если ваш SQL содержит определения процедур или триггеров ... причины, почему это нехорошо.
Билл Карвин
1

Как и тысячи людей, я ищу этот вопрос:
может выполнять несколько запросов одновременно, и если была одна ошибка, ни одна из них не запустилась. Я заходил на эту страницу везде.
Но хотя друзья здесь давали хорошие ответы, эти ответы не подходили для моя проблема
Итак, я написал функцию, которая хорошо работает и почти не имеет проблем с sql Injection.
Это может быть полезно для тех, кто ищет похожие вопросы, поэтому я помещаю их здесь, чтобы использовать

function arrayOfQuerys($arrayQuery)
{
    $mx = true;
    $conn->beginTransaction();
    try {
        foreach ($arrayQuery AS $item) {
            $stmt = $conn->prepare($item["query"]);
            $stmt->execute($item["params"]);
            $result = $stmt->rowCount();
            if($result == 0)
                $mx = false;
         }
         if($mx == true)
             $conn->commit();
         else
             $conn->rollBack();
    } catch (Exception $e) {
        $conn->rollBack();
        echo "Failed: " . $e->getMessage();
    }
    return $mx;
}

для использования (пример):

 $arrayQuery = Array(
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("aa1", 1)
    ),
    Array(
        "query" => "UPDATE test SET title = ? WHERE test.id = ?",
        "params" => Array("bb1", 2)
    )
);
arrayOfQuerys($arrayQuery);

и моя связь:

    try {
        $options = array(
            //For updates where newvalue = oldvalue PDOStatement::rowCount()   returns zero. You can use this:
            PDO::MYSQL_ATTR_FOUND_ROWS => true
        );
        $conn = new PDO("mysql:host=$servername;dbname=$database", $username, $password, $options);
        $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) {
        echo "Error connecting to SQL Server: " . $e->getMessage();
    }

Примечание:
это решение помогает вам запускать несколько операторов вместе.
Если возникает неправильный оператор, он не выполняет никаких других операторов.

mirzaei.sajad
источник
0

Пробовал следующий код

 $db = new PDO("mysql:host={$dbhost};dbname={$dbname};charset=utf8", $dbuser, $dbpass, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

затем

 try {
 $db->query('SET NAMES gbk');
 $stmt = $db->prepare('SELECT * FROM 2_1_paidused WHERE NumberRenamed = ? LIMIT 1');
 $stmt->execute(array("\xbf\x27 OR 1=1 /*"));
 }
 catch (PDOException $e){
 echo "DataBase Errorz: " .$e->getMessage() .'<br>';
 }
 catch (Exception $e) {
 echo "General Errorz: ".$e->getMessage() .'<br>';
 }

И получил

DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '/*' LIMIT 1' at line 1

Если добавлено $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); после$db = ...

Затем появилась пустая страница

Если вместо этого SELECTпопытался DELETE, то в обоих случаях получил ошибку вроде

 DataBase Errorz: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '* FROM 2_1_paidused WHERE NumberRenamed = '¿\' OR 1=1 /*' LIMIT 1' at line 1

Итак, мой вывод, что инъекция невозможна ...

Андрис
источник
3
Вы должны были сделать новый вопрос, когда ссылаетесь на этот
Your Common Sense
Не так много вопросов в результате того, что я пробовал. И мой вывод. Первоначальный вопрос старый, возможно, не актуальный на данный момент.
Андрис
Не уверен, какое отношение это имеет ко всему в вопросе.
cHao
but you risk to be injected with multiple queries.Речь идет о словах. Мой ответ об инъекции
Андрис,
0

Попробуйте эту функцию: несколько запросов и вставка нескольких значений.

function employmentStatus($Status) {
$pdo = PDO2::getInstance();

$sql_parts = array(); 
for($i=0; $i<count($Status); $i++){
    $sql_parts[] = "(:userID, :val$i)";
}

$requete = $pdo->dbh->prepare("DELETE FROM employment_status WHERE userid = :userID; INSERT INTO employment_status (userid, status) VALUES ".implode(",", $sql_parts));
$requete->bindParam(":userID", $_SESSION['userID'],PDO::PARAM_INT);
for($i=0; $i<count($Status); $i++){
    $requete->bindParam(":val$i", $Status[$i],PDO::PARAM_STR);
}
if ($requete->execute()) {
    return true;
}
return $requete->errorInfo();
}
Хасан Б.
источник
0

PDO поддерживает это (по состоянию на 2020 год). Просто выполните вызов query () для объекта PDO как обычно, разделяя запросы по; а затем nextRowset () для перехода к следующему результату SELECT, если у вас их несколько. Наборы результатов будут в том же порядке, что и запросы. Очевидно, подумайте о последствиях для безопасности - поэтому не принимайте пользовательские запросы, не используйте параметры и т. Д. Я использую это, например, с запросами, сгенерированными кодом.

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());
Андрис
источник
Я никогда не смог бы понять такого рода рассуждения: «Вот код, который представляет собой большую дыру в безопасности, пренебрегая всеми рекомендуемыми передовыми методами, поэтому вам нужно подумать о последствиях для безопасности». Кому следует об этом думать? Когда им следует подумать - перед использованием этого кода или после того, как их взломают? Почему бы вам сначала не подумать об этом, прежде чем писать эту функцию или предлагать ее другим людям?
Ваш здравый смысл
2
Уважаемый @YourCommonSense, выполнение нескольких запросов за один раз помогает повысить производительность, меньший сетевой трафик + сервер может оптимизировать связанные запросы. Мой (упрощенный) пример предназначен только для ознакомления с методом, необходимым для его использования. Это дыра в безопасности, только если вы не используете те передовые методы, о которых говорите. Кстати, я с подозрением отношусь к людям, которые говорят: «Я никогда не пойму ...», хотя они легко могли бы ... :-)
Андрис