Obsługa PDO dla wielu zapytań (PDO_MYSQL, PDO_MYSQLND)

102

Wiem, że PDO nie obsługuje wielu zapytań wykonywanych w jednej instrukcji. Szukałem w Google i znalazłem kilka postów dotyczących PDO_MYSQL i PDO_MYSQLND.

PDO_MySQL jest bardziej niebezpieczną aplikacją niż jakiekolwiek inne tradycyjne aplikacje MySQL. Tradycyjny MySQL dopuszcza tylko jedno zapytanie SQL. W PDO_MySQL nie ma takiego ograniczenia, ale istnieje ryzyko wstrzyknięcia wielu zapytań.

Od: Protection against SQL Injection using PDO and Zend Framework (czerwiec 2010; Julian)

Wygląda na to, że PDO_MYSQL i PDO_MYSQLND zapewniają obsługę wielu zapytań, ale nie jestem w stanie znaleźć więcej informacji na ich temat. Czy te projekty zostały przerwane? Czy jest teraz jakikolwiek sposób na uruchamianie wielu zapytań przy użyciu PDO.

Gajus
źródło
4
Użyj transakcji SQL.
tereško
Dlaczego chcesz używać wielu zapytań? Nie są zawierane, to tak samo, jak wykonywałbyś je jeden po drugim. IMHO nie ma zalet, tylko wady. W przypadku SQLInjection pozwalasz napastnikowi robić co tylko zechce.
mleko
Teraz jest rok 2020, a PDO to obsługuje - zobacz moją odpowiedź poniżej.
Andris

Odpowiedzi:

141

Jak wiem, PDO_MYSQLNDzastąpiony PDO_MYSQLw PHP 5.3. Mylące jest to, że imię wciąż jest PDO_MYSQL. Teraz ND jest domyślnym sterownikiem MySQL + PDO.

Ogólnie rzecz biorąc, aby wykonać wiele zapytań jednocześnie, potrzebujesz:

  • PHP 5.3+
  • mysqlnd
  • Emulowane przygotowane wyciągi. Upewnij się, że PDO::ATTR_EMULATE_PREPARESjest ustawione na 1(domyślne). Alternatywnie możesz uniknąć używania przygotowanych wyciągów i używać ich $pdo->execbezpośrednio.

Korzystanie z 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);

Korzystanie z instrukcji

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

Notka:

Korzystając z emulowanych przygotowanych instrukcji, upewnij się, że ustawiłeś odpowiednie kodowanie (odzwierciedlające faktyczne kodowanie danych) w DSN (dostępne od 5.3.6). W przeciwnym razie może wystąpić niewielka możliwość wstrzyknięcia kodu SQL, jeśli zostanie użyte dziwne kodowanie .

Sam Dark
źródło
37
Nie ma nic złego w samej odpowiedzi. Wyjaśnia, jak wykonać wiele zapytań. Twoje założenie, że odpowiedź jest błędna, pochodzi z założenia, że ​​zapytanie zawiera dane wejściowe użytkownika. Istnieją ważne przypadki użycia, w których wysyłanie za pośrednictwem wielu zapytań jednocześnie może zwiększyć wydajność. Możesz zasugerować użycie procedur jako alternatywnej odpowiedzi na to pytanie, ale to nie oznacza, że ​​odpowiedź ta jest zła.
Gajus
9
Kod w tej odpowiedzi jest zły i promuje pewne bardzo szkodliwe praktyki (użycie emulacji do przygotowania instrukcji, które otwierają kod na podatność na iniekcję SQL ). Nie używaj tego.
tereško
17
Nie ma nic złego w tej odpowiedzi, a zwłaszcza w trybie emulacji. Jest domyślnie włączona w pdo_mysql, a gdyby był jakiś problem, byłoby już tysiące zastrzyków. Ale nikt jeszcze nie był bliski. Tak to idzie.
Twój zdrowy rozsądek
3
Właściwie jedynym, któremu udało się dostarczyć nie tylko emocji, ale i argumentację, był ircmaxell. Jednak linki, które przyniósł, są zupełnie nieistotne. Pierwszy jest w ogóle niemożliwy do zastosowania, ponieważ wyraźnie mówi, że „PDO jest zawsze odporne na ten błąd”. Podczas gdy drugi można po prostu rozwiązać, ustawiając odpowiednie kodowanie. Zasługuje więc na uwagę, a nie ostrzeżenie i mniej pociągające.
Twój zdrowy rozsądek
6
Mówiąc jak ktoś, kto pisze narzędzie do migracji, które używa SQL, które napisali tylko nasi programiści (tj. Wstrzyknięcie SQL nie jest problemem), bardzo mi to pomogło, a wszelkie komentarze wskazujące, że ten kod jest szkodliwy, nie rozumieją w pełni wszystkich konteksty jego użycia.
Luke
17

Po pół dnia bawiąc się tym, okazało się, że PDO ma błąd, w którym ...

-

//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;");

Wykona "valid-stmt1;", zatrzyma się "non-sense;"i nigdy nie zgłosi błędu. Nie uruchomię "valid-stmt3;", powróci prawda i kłamie, że wszystko poszło dobrze.

Spodziewałbym się, że wystąpi błąd, "non-sense;"ale tak się nie dzieje.

Oto gdzie znalazłem te informacje: Nieprawidłowe zapytanie PDO nie zwraca błędu

Oto błąd: https://bugs.php.net/bug.php?id=61613


Więc próbowałem to zrobić z mysqli i tak naprawdę nie znalazłem żadnej solidnej odpowiedzi na temat tego, jak to działa, więc pomyślałem, że zostawię to tutaj dla tych, którzy chcą go używać.

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>";
}
Sai Phaninder Reddy J.
źródło
Czy to działa, jeśli uruchamiasz tylko $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");bez dwóch poprzednich plików exec? Mogę sprawić, że wyrzuci błędy w środku, ale nie podczas wykonywania po pomyślnym wykonaniu .
Jeff Puckett
Nie, tak nie jest. To jest błąd związany z PDO.
Sai Phaninder Reddy J
1
Moja wina, te 3 $pdo->exec("")są od siebie niezależne. Teraz podzielę je, aby wskazać, że nie muszą być ułożone w kolejności, aby pojawił się problem. Te 3 to 3 konfiguracje uruchamiania wielu zapytań w jednej instrukcji exec.
Sai Phaninder Reddy J
Ciekawy. Czy miałeś okazję zobaczyć moje opublikowane pytanie? Zastanawiam się, czy zostało to częściowo załatane, ponieważ mogę otrzymać błąd, jeśli jest to jedyny execna stronie, ale jeśli uruchomię wiele execz wieloma instrukcjami SQL w nich, wtedy odtwarzam tutaj ten sam błąd. Ale jeśli to jedyna execstrona, to nie mogę jej odtworzyć.
Jeff Puckett
Czy to execna twojej stronie zawierało wiele stwierdzeń?
Sai Phaninder Reddy J
3

Szybkie i brudne podejście:

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

Dzieli się w rozsądnych punktach końcowych instrukcji SQL. Nie ma kontroli błędów, nie ma ochrony wtrysku. Zrozum swoje zastosowanie przed użyciem. Osobiście używam go do wysyłania surowych plików migracyjnych do testów integracyjnych.

biskup
źródło
1
Błąd kończy się niepowodzeniem, jeśli plik SQL zawiera jakiekolwiek wbudowane polecenia mysql ... Prawdopodobnie spowoduje to również przekroczenie limitu pamięci PHP, jeśli plik SQL jest duży ... Dzielenie w ;przypadku przerw, jeśli SQL zawiera definicje procedur lub wyzwalaczy ... powody, dla których to nie jest dobre.
Bill Karwin,
1

Podobnie jak tysiące ludzi, szukam tego pytania:
Mogę uruchomić wiele zapytań jednocześnie, a jeśli byłby jeden błąd, żaden nie byłby uruchomiony Wszedłem na tę stronę wszędzie
Ale chociaż znajomi tutaj udzielili dobrych odpowiedzi, te odpowiedzi nie były dobre dla mój problem
Więc napisałem funkcję, która działa dobrze i prawie nie ma problemu z wtryskiem sql.
Może to być pomocne dla tych, którzy szukają podobnych pytań, więc umieściłem je tutaj do wykorzystania

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

do użytku (przykład):

 $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);

i moje połączenie:

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

Uwaga:
To rozwiązanie ułatwia uruchamianie wielu instrukcji jednocześnie.
Jeśli wystąpi niepoprawna instrukcja, nie zostanie wykonana żadna inna instrukcja

mirzaei.sajad
źródło
0

Wypróbowałem następujący kod

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

Następnie

 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>';
 }

I dostał

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

Jeśli zostanie dodany $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);po$db = ...

Potem dostałem pustą stronę

Jeśli zamiast tego SELECTspróbowano DELETE, w obu przypadkach pojawił się błąd, taki jak

 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

Więc mój wniosek, że żaden zastrzyk nie jest możliwy ...

Andris
źródło
3
Powinieneś był uczynić to nowe pytanie, odnosząc się do tego
Your Common Sense
Nie tyle pytanie, ile wynik tego, co próbowałem. I mój wniosek. Pierwsze pytanie jest stare, prawdopodobnie nieaktualne w tej chwili.
Andris
Nie wiem, jak to się ma do czegokolwiek w pytaniu.
cHao
w pytaniu są słowa but you risk to be injected with multiple queries.Moja odpowiedź dotyczy zastrzyku
Andris
0

Wypróbuj tę funkcję: mltiple queries i wstawianie wielu wartości.

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();
}
hassan b.
źródło
0

ChNP to obsługuje (od 2020 r.). Po prostu wykonaj wywołanie query () na obiekcie PDO jak zwykle, oddzielając zapytania; a następnie nextRowset (), aby przejść do następnego wyniku SELECT, jeśli masz wiele. Zestawy wyników będą w tej samej kolejności co zapytania. Oczywiście pomyśl o implikacjach dla bezpieczeństwa - więc nie akceptuj zapytań dostarczonych przez użytkownika, używaj parametrów itp. Używam go na przykład do zapytań generowanych przez kod.

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());
Andris
źródło
Nigdy bym nie zrozumiał tego rodzaju rozumowania: „Oto kod, który jest dużą dziurą w zabezpieczeniach, pomijając wszystkie zalecane dobre praktyki, więc musisz pomyśleć o implikacjach dla bezpieczeństwa”. Kto powinien o tym pomyśleć? Kiedy powinni pomyśleć - przed użyciem tego kodu, czy po włamaniu? Dlaczego nie pomyślisz o tym najpierw, zanim napiszesz tę funkcję lub nie zaoferujesz jej innym osobom?
Twój zdrowy rozsądek
Drogi @YourCommonSense uruchamianie wielu zapytań za jednym razem pomaga zwiększyć wydajność, mniejszy ruch w sieci + serwer może zoptymalizować powiązane zapytania. Mój (uproszczony) przykład miał na celu jedynie wprowadzenie metody wymaganej do jego użycia. Jest to luka w zabezpieczeniach tylko wtedy, gdy nie stosujesz tych dobrych praktyk, o których się odnosisz. Przy okazji, jestem podejrzliwy wobec ludzi, którzy mówią „Nigdy bym nie zrozumiał…”, kiedy mogliby z łatwością ... :-)
Andris