Birden fazla sorgu için PDO desteği (PDO_MYSQL, PDO_MYSQLND)


102

PDO'nun tek bir ifadede yürütülen birden fazla sorguyu desteklemediğini biliyorum. Google'ı araştırdım ve PDO_MYSQL ve PDO_MYSQLND hakkında konuşan birkaç gönderi buldum.

PDO_MySQL, diğer geleneksel MySQL uygulamalarından daha tehlikeli bir uygulamadır. Geleneksel MySQL yalnızca tek bir SQL sorgusuna izin verir. PDO_MySQL'de böyle bir sınırlama yoktur, ancak birden çok sorgu enjekte edilme riskiniz vardır.

Kimden: PDO ve Zend Framework kullanarak SQL Enjeksiyonuna karşı koruma (Haziran 2010; Julian tarafından)

PDO_MYSQL ve PDO_MYSQLND birden çok sorgu için destek sağlıyor gibi görünüyor, ancak onlar hakkında daha fazla bilgi bulamıyorum. Bu projeler durduruldu mu? PDO kullanarak birden fazla sorgu çalıştırmanın bir yolu var mı?


4
SQL işlemlerini kullanın.
tereško

Neden birden çok sorgu kullanmak istersiniz? İşlem görmüyorlar, sadece onları birbiri ardına yürüttüğünüz gibi. IMHO artı yok, sadece eksiler. SQLInjection durumunda, saldırganın istediğini yapmasına izin verirsiniz.
mleko

Şimdi 2020 ve PDO bunu destekliyor - cevabımı aşağıya bakın.
Andris

Yanıtlar:


141

Bildiğim gibi, PHP 5.3'te PDO_MYSQLNDdeğiştirildi PDO_MYSQL. Kafa karıştırıcı kısmı, ismin hala olmasıdır PDO_MYSQL. Yani şimdi ND, MySQL + PDO için varsayılan sürücüdür.

Genel olarak, aynı anda birden fazla sorgu yürütmek için ihtiyacınız olan:

  • PHP 5.3+
  • mysqlnd
  • Öykünülmüş hazırlanmış ifadeler. (Varsayılan) PDO::ATTR_EMULATE_PREPARESolarak ayarlandığından emin olun 1. Alternatif olarak hazırlanmış ifadeleri kullanmaktan kaçınabilir ve $pdo->execdoğrudan kullanabilirsiniz .

Exec kullanma

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

İfadeleri kullanma

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

Bir not:

Öykünülmüş hazırlanmış ifadeleri kullanırken, DSN'de (5.3.6'dan beri mevcuttur ) doğru kodlamayı (gerçek veri kodlamasını yansıtan) ayarladığınızdan emin olun . Aksi takdirde , bazı garip kodlamalar kullanılırsa SQL enjeksiyonu için küçük bir olasılık olabilir .


38
Cevabın kendisinde yanlış bir şey yok. Birden çok sorgunun nasıl yürütüleceğini açıklar. Cevabın hatalı olduğu varsayımınız, sorgunun kullanıcı girdisi içerdiği varsayımından gelmektedir. Tek seferde birden çok sorgu göndermenin performansa fayda sağlayabileceği geçerli kullanım durumları vardır. Bu soruya alternatif bir yanıt olarak prosedürleri kullanmayı önerebilirsiniz, ancak bu, bu yanıtı kötü yapmaz.
Gajus

9
Bu yanıttaki kod kötüdür ve bazı çok zararlı uygulamaları teşvik eder (kodu SQL enjeksiyon güvenlik açığına açık hale getiren ifadeler için öykünme kullanımı ). Bunu kullanma.
tereško

17
Bu cevapta ve özellikle öykünme modunda yanlış bir şey yok. Pdo_mysql'de varsayılan olarak etkindir ve herhangi bir sorun olsaydı, zaten binlerce enjeksiyon olurdu. Ama henüz hiç kimse yok. O zaman o gider.
Sizin Common Sense

3
Aslında, sadece duyguları değil, bazı tartışmaları da sağlamayı başaran tek kişi ircmaxell'di. Ancak, getirdiği bağlantılar oldukça alakasız. Birincisi, açıkça "PDO bu hatadan her zaman muaftır" dediği için hiç uygulanamaz . İkincisi, uygun kodlamayı ayarlayarak basitçe çözülebilir. Bu nedenle, bir notu hak ediyor, uyarıyı değil ve daha az çekici olanı.
Sizin Common Sense

6
Yalnızca geliştiricilerimizin yazdığı SQL kullanan bir taşıma aracı yazan biri olarak konuşmak (yani SQL enjeksiyonu bir sorun değildir), bu bana büyük ölçüde yardımcı oldu ve bu kodun zararlı olduğunu belirten herhangi bir yorum, hepsini tam olarak anlamıyor kullanım bağlamları.
Luke

17

Bununla uğraştıktan yarım gün sonra, PDO'nun bir hatası olduğunu öğrendim ...

-

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

Çalıştırır "valid-stmt1;", durur "non-sense;"ve asla hata yapmaz. Koşmayacak "valid-stmt3;", gerçeğe dönmeyecek ve her şeyin yolunda gittiğini yalanlayacak

Ben hata yapmasını beklerdim "non-sense;"ama olmuyor.

İşte bu bilgiyi bulduğum yer: Geçersiz PDO sorgusu bir hata vermiyor

İşte hata: https://bugs.php.net/bug.php?id=61613


Bu yüzden, bunu mysqli ile yapmayı denedim ve nasıl çalıştığına dair kesin bir cevap bulamadım, bu yüzden onu kullanmak isteyenler için burada bırakmam gerektiğini düşündüm.

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

Yalnızca $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");önceki iki exec olmadan çalıştırırsanız çalışır mı? Ortada hata atmasını sağlayabilirim , ancak başarılı yürütmelerden sonra yürütüldüğünde değil .
Jeff Puckett

Hayır, öyle değil. PDO'daki hata budur.
Sai Phaninder Reddy J

1
Benim hatam, bu 3 $pdo->exec("")tanesi birbirinden bağımsız. Şimdi, sorunun ortaya çıkması için sıralı olmaları gerekmediğini belirtmek için onları ayırıyorum. Bu 3, tek bir exec deyiminde birden çok sorgu çalıştırmanın 3 yapılandırmasıdır.
Sai Phaninder Reddy J

İlginç. Gönderdiğim sorumu görme şansın oldu mu? Bunun kısmen düzeltilip düzeltilmediğini merak ediyorum çünkü yalnızca sayfadaysa hata atılabilir exec, ancak execher birinde birden çok SQL ifadesi ile birden çok çalıştırırsam , aynı hatayı burada yeniden oluşturuyorum. Ancak execsayfadaki tek buysa, onu yeniden üretemem.
Jeff Puckett

Bu bir mü execsayfanızda birden ifadeler var?
Sai Phaninder Reddy J

3

Hızlı ve kirli bir yaklaşım:

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

Makul SQL ifadesi uç noktalarında böler. Hata kontrolü yok, enjeksiyon koruması yok. Kullanmadan önce kullanımınızı anlayın. Şahsen, entegrasyon testi için ham geçiş dosyalarını tohumlamak için kullanıyorum.


1
SQL dosyanız herhangi bir mysql yerleşik komutu içeriyorsa bu başarısız olur ... SQL dosyası büyükse muhtemelen PHP bellek limitinizi de aşacaktır ... SQL'iniz ;prosedür veya tetik tanımları içeriyorsa molalarda bölme ... Çok fazla iyi olmadığının nedenleri.
Bill Karwin

1

Binlerce insan gibi ben de şu soruyu arıyorum:
Aynı anda birden fazla sorgu çalıştırabilirim ve bir hata olsaydı hiçbiri çalışmazdı, her yerde bu sayfaya gittim
Ama buradaki arkadaşlar iyi cevaplar verse de , bu cevaplar için iyi değildi benim problemim
Bu yüzden iyi çalışan ve sql Injection ile neredeyse hiç problemi olmayan bir fonksiyon yazdım.
Benzer sorular arayanlar için faydalı olabilir, bu yüzden onları kullanmak için buraya koyuyorum

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

kullanım için (örnek):

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

ve bağlantım:

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

Not:
Bu çözüm, birden çok ifadeyi birlikte çalıştırmanıza yardımcı olur,
Yanlış bir ifade ortaya çıkarsa, başka herhangi bir ifade yürütmez.


0

Aşağıdaki kodu denedim

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

Sonra

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

Ve var

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);Sonra eklenirse$db = ...

Sonra boş sayfa var

Bunun yerine SELECTdenendiyse DELETE, her iki durumda da şöyle bir hata var

 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

Dolayısıyla, enjeksiyonun mümkün olmadığı sonucuna varıyorum


3
Sen bunu şu birine başvuran yeni bir soru yapılmış olmalıdır
Sizin Common Sense

Denediğim şeyin bir sonucu olarak çok fazla soru yok. Ve benim sonucum. İlk soru eski, muhtemelen şu anda gerçek değil.
Andris

Bunun söz konusu herhangi bir şeyle ne kadar alakalı olduğundan emin değilim.
cHao

söz konusu sözler but you risk to be injected with multiple queries.Cevabım enjeksiyonla ilgili
Andris

0

Bu işlevi deneyin: birden çok sorgu ve birden çok değer ekleme.

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 bunu desteklemektedir (2020 itibariyle). Bir PDO nesnesinde her zamanki gibi bir query () çağrısı yapın, sorguları şu şekilde ayırın; ve sonra nextRowset () ile birden çok seçeneğiniz varsa sonraki SEÇİM sonucuna gidin. Sonuç kümeleri sorgularla aynı sırada olacaktır. Açıkçası güvenlik etkilerini düşünün - bu nedenle kullanıcı tarafından sağlanan sorguları kabul etmeyin, parametreleri kullanın, vb. Bunu örneğin kod tarafından oluşturulan sorgularda kullanırım.

$statement = $connection->query($query);
do {
  $data[] = $statement->fetchAll(PDO::FETCH_ASSOC);
} while ($statement->nextRowset());

Bu tür bir muhakemeyi asla anlayamam, "Burada, önerilen tüm iyi uygulamaları ihmal eden güvenlikte büyük bir delik olan bir kod var, bu yüzden güvenlik sonuçlarını düşünmeniz gerekiyor." Bunu kim düşünmeli? Ne zaman düşünmeleri gerekir - bu kodu kullanmadan önce mi yoksa saldırıya uğradıktan sonra mı? Neden bu işlevi yazmadan veya başkalarına sunmadan önce bunu düşünmüyorsunuz?
Sizin Common Sense

2
Sevgili @YourCommonSense, tek seferde birden fazla sorgu çalıştırmak performansa yardımcı olur, daha az ağ trafiği + sunucu ilgili sorguları optimize edebilir. Benim (basitleştirilmiş) örneğim, sadece onu kullanmak için gerekli yöntemi tanıtmak içindir. Bu sadece bahsettiğiniz iyi uygulamaları kullanmazsanız bir güvenlik açığıdır. BTW, kolayca anlayabildikleri zaman "Asla anlamadım ..." diyen insanlardan şüpheleniyorum ... :-)
Andris
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.