Поддержка 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.


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();
    }

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


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, выполнение нескольких запросов за один раз помогает повысить производительность, меньший сетевой трафик + сервер может оптимизировать связанные запросы. Мой (упрощенный) пример предназначен только для ознакомления с методом, необходимым для его использования. Это дыра в безопасности, только если вы не используете те передовые методы, о которых говорите. Кстати, я с подозрением отношусь к людям, которые говорят: «Я никогда не пойму ...», хотя они легко могли бы ... :-)
— Андрис
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.