Hỗ trợ PDO cho nhiều truy vấn (PDO_MYSQL, PDO_MYSQLND)


102

Tôi biết rằng PDO không hỗ trợ nhiều truy vấn được thực thi trong một câu lệnh. Tôi đang sử dụng Google và tìm thấy một vài bài đăng nói về PDO_MYSQL và PDO_MYSQLND.

PDO_MySQL là một ứng dụng nguy hiểm hơn bất kỳ ứng dụng MySQL truyền thống nào khác. MySQL truyền thống chỉ cho phép một truy vấn SQL duy nhất. Trong PDO_MySQL không có giới hạn như vậy, nhưng bạn có nguy cơ bị chèn nhiều truy vấn.

Từ: Bảo vệ chống lại SQL Injection sử dụng PDO và Zend Framework (tháng 6 năm 2010; bởi Julian)

Có vẻ như PDO_MYSQL và PDO_MYSQLND cung cấp hỗ trợ cho nhiều truy vấn, nhưng tôi không thể tìm thêm thông tin về chúng. Các dự án này có bị ngừng không? Có cách nào bây giờ để chạy nhiều truy vấn bằng PDO không.


4
Sử dụng các giao dịch SQL.
tereško

Tại sao bạn muốn sử dụng nhiều truy vấn? Chúng không được giao dịch, nó giống như việc bạn thực hiện chúng lần lượt. IMHO không có ưu, chỉ có khuyết điểm. Trong trường hợp SQLInjection, bạn cho phép kẻ tấn công làm bất cứ điều gì anh ta muốn.
mleko

Bây giờ là năm 2020 và PDO có hỗ trợ điều này - hãy xem câu trả lời của tôi ở bên dưới.
Andris

Câu trả lời:


141

Như tôi biết, được PDO_MYSQLNDthay thế PDO_MYSQLtrong PHP 5.3. Phần khó hiểu là cái tên vẫn còn PDO_MYSQL. Vì vậy, bây giờ ND là trình điều khiển mặc định cho MySQL + PDO.

Nhìn chung, để thực hiện nhiều truy vấn cùng một lúc, bạn cần:

  • PHP 5.3+
  • mysqlnd
  • Các câu lệnh đã chuẩn bị được mô phỏng. Đảm bảo PDO::ATTR_EMULATE_PREPARESđược đặt thành 1(mặc định). Ngoài ra, bạn có thể tránh sử dụng các câu lệnh đã soạn sẵn và sử dụng $pdo->exectrực tiếp.

Sử dụng thực thi

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

Sử dụng câu lệnh

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

Một lưu ý:

Khi sử dụng các câu lệnh được chuẩn bị mô phỏng, hãy đảm bảo rằng bạn đã đặt mã hóa phù hợp (phản ánh mã hóa dữ liệu thực tế) trong DSN (khả dụng kể từ 5.3.6). Nếu không, có thể có một chút khả năng xảy ra việc chèn SQL nếu một số mã hóa kỳ lạ được sử dụng .


37
Không có gì sai với chính câu trả lời. Nó giải thích cách thực thi nhiều truy vấn. Giả định rằng câu trả lời của bạn là thiếu sót xuất phát từ giả định rằng truy vấn chứa đầu vào của người dùng. Có những trường hợp sử dụng hợp lệ trong đó gửi qua nhiều truy vấn cùng một lúc có thể mang lại lợi ích cho hiệu suất. Bạn có thể đề xuất sử dụng các thủ tục như một câu trả lời thay thế cho câu hỏi này, nhưng điều đó không làm cho câu trả lời này trở nên tồi tệ.
Gajus

9
Mã trong câu trả lời này là xấu và khuyến khích một số thực hành rất có hại (sử dụng mô phỏng để chuẩn bị các câu lệnh, khiến mã mở ra lỗ hổng SQL injection ). Đừng sử dụng nó.
tereško

17
Không có gì sai với câu trả lời này và chế độ giả lập nói riêng. Nó được bật theo mặc định trong pdo_mysql và nếu có bất kỳ sự cố nào, sẽ có hàng nghìn lần tiêm. Nhưng vẫn chưa có ai. Vì vậy, nó đi.
Common Sense của bạn

3
Trên thực tế, chỉ có người cố gắng cung cấp không chỉ cảm xúc mà còn cả một số lý lẽ, là ircmaxell. Tuy nhiên, các liên kết mà anh ta mang lại khá không liên quan. Đầu tiên là không thể áp dụng được vì nó nói rõ ràng "PDO luôn miễn nhiễm với lỗi này." Trong khi cái thứ hai có thể giải quyết đơn giản bằng cách đặt mã hóa thích hợp. Vì vậy, nó xứng đáng được ghi chú, không phải cảnh báo và ít hấp dẫn hơn.
Your Common Sense

6
Nói như một người đang viết một công cụ di chuyển sử dụng SQL mà chỉ các nhà phát triển của chúng tôi mới viết (tức là việc chèn SQL không phải là một vấn đề), điều này đã giúp tôi rất nhiều và bất kỳ nhận xét nào chỉ ra rằng mã này có hại, tôi không hiểu đầy đủ tất cả bối cảnh cho việc sử dụng nó.
Luke

17

Sau nửa ngày loay hoay với cái này, phát hiện ra rằng PDO có một lỗi ở ...

-

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

Nó sẽ thực thi "valid-stmt1;", dừng lại "non-sense;"và không bao giờ báo lỗi. Sẽ không chạy "valid-stmt3;", trả lại sự thật và nói dối rằng mọi thứ chạy tốt.

Tôi hy vọng nó sẽ xảy ra lỗi "non-sense;"nhưng nó không.

Đây là nơi tôi tìm thấy thông tin này: Truy vấn PDO không hợp lệ không trả lại lỗi

Đây là lỗi: https://bugs.php.net/bug.php?id=61613


Vì vậy, tôi đã thử làm điều này với mysqli và chưa thực sự tìm thấy bất kỳ câu trả lời chắc chắn nào về cách hoạt động của nó vì vậy tôi nghĩ rằng tôi chỉ để nó ở đây cho những người muốn sử dụng nó ..

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

Nó có hoạt động không nếu bạn chỉ chạy $pdo->exec("valid-stmt1; non-sense; valid-stmt3;");mà không có hai thực thi trước đó? Tôi có thể làm cho nó để ném lỗi ở giữa, nhưng không phải khi được thực thi sau khi thực thi thành công .
Jeff Puckett

Không, nó không. Đó là lỗi với PDO.
Sai Phaninder Reddy J

1
Tệ của tôi, 3 cái đó $pdo->exec("")độc lập với nhau. Bây giờ tôi chia chúng ra để chỉ ra rằng chúng không cần phải theo một trình tự để vấn đề phát sinh. 3 đó là 3 cấu hình chạy nhiều truy vấn trong một câu lệnh thực thi.
Sai Phaninder Reddy J

Hấp dẫn. Bạn có cơ hội xem câu hỏi đã đăng của tôi không? Tôi tự hỏi liệu điều này đã được vá một phần chưa vì tôi có thể gặp lỗi nếu nó là lỗi duy nhất exectrên trang, nhưng nếu tôi chạy nhiều lỗi execvới nhiều câu lệnh SQL trong đó, thì tôi tạo lại cùng một lỗi ở đây. Nhưng nếu nó là duy nhất exectrên trang, thì tôi không thể sao chép nó.
Jeff Puckett

Cái đó exectrên trang của bạn có nhiều câu lệnh không?
Sai Phaninder Reddy J

3

Một cách tiếp cận nhanh chóng và hiệu quả:

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

Tách ở các điểm cuối câu lệnh SQL hợp lý. Không có lỗi kiểm tra, không có bảo vệ tiêm. Hiểu công dụng của bạn trước khi sử dụng. Cá nhân tôi sử dụng nó để bắt nguồn các tệp di chuyển thô để kiểm tra tích hợp.


1
Điều này không thành công nếu tệp SQL của bạn chứa bất kỳ lệnh nội trang mysql nào ... Nó có thể sẽ làm mất giới hạn bộ nhớ PHP của bạn, nếu tệp SQL lớn ... Tách khi ;ngắt nếu SQL của bạn chứa định nghĩa thủ tục hoặc trình kích hoạt ... Rất nhiều lý do tại sao nó không tốt.
Bill Karwin

1

Giống như hàng nghìn người, tôi đang tìm kiếm câu hỏi này:
Có thể chạy nhiều truy vấn cùng một lúc, và nếu có một lỗi, không ai sẽ chạy tôi đã đến trang này ở khắp mọi nơi
Nhưng mặc dù bạn bè ở đây cho câu trả lời tốt, những câu trả lời là không tốt cho vấn đề của tôi
Vì vậy, tôi đã viết một hàm hoạt động tốt và hầu như không có vấn đề gì với sql Injection.
Nó có thể hữu ích cho những người đang tìm kiếm câu hỏi tương tự vì vậy tôi đặt chúng ở đây để sử dụng

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

để sử dụng (ví dụ):

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

và kết nối của tôi:

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

Lưu ý:
Giải pháp này giúp bạn chạy nhiều câu lệnh cùng nhau,
Nếu một câu lệnh sai xảy ra, nó sẽ không thực hiện bất kỳ câu lệnh nào khác


0

Đã thử mã sau

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

Sau đó

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

Và có

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

Nếu thêm $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);sau$db = ...

Sau đó, có trang trống

Nếu thay vào đó SELECTđã thử DELETE, thì trong cả hai trường hợp đều gặp lỗi như

 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

Vì vậy, kết luận của tôi rằng không thể tiêm ...


3
Bạn nên đã làm cho nó một câu hỏi mới, nơi tham khảo với trang này
bạn Common Sense

Không có quá nhiều câu hỏi do kết quả của những gì tôi đã cố gắng. Và kết luận của tôi. Câu hỏi ban đầu là cũ, có thể không thực tế vào lúc này.
Andris

Không chắc điều này có liên quan như thế nào đến bất kỳ điều gì trong câu hỏi.
cHao

trong câu hỏi là những từ but you risk to be injected with multiple queries.câu trả lời của tôi là về tiêm
Andris

0

Hãy thử chức năng này: truy vấn nhiều ml và chèn nhiều giá trị.

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 hỗ trợ điều này (tính đến năm 2020). Chỉ cần thực hiện một cuộc gọi truy vấn () trên một đối tượng PDO như bình thường, tách các truy vấn bằng; và sau đó nextRowset () để chuyển đến kết quả CHỌN tiếp theo, nếu bạn có nhiều. Các tập kết quả sẽ theo thứ tự như các truy vấn. Rõ ràng là hãy nghĩ về các tác động bảo mật - vì vậy không chấp nhận các truy vấn do người dùng cung cấp, sử dụng các tham số, v.v. Tôi sử dụng nó với các truy vấn được tạo bởi mã chẳng hạn.

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

Tôi sẽ không bao giờ hiểu được kiểu lý luận này, "Đây là một đoạn mã là một lỗ hổng bảo mật lớn khi bỏ qua tất cả các phương pháp hay được khuyến nghị, vì vậy bạn cần phải suy nghĩ về các tác động bảo mật." Ai nên nghĩ về nó? Khi nào họ nên suy nghĩ - trước khi sử dụng mã này hoặc sau khi họ bị tấn công? Tại sao bạn không nghĩ về nó trước, trước khi viết chức năng này hoặc cung cấp nó cho người khác?
Your Common Sense

Kính gửi @YourCommonSense chạy nhiều truy vấn trong một lần giúp tăng hiệu suất, ít lưu lượng mạng hơn + máy chủ có thể tối ưu hóa các truy vấn liên quan. Ví dụ (đơn giản hóa) của tôi chỉ có nghĩa là giới thiệu phương pháp cần thiết để sử dụng nó. Đó chỉ là một lỗ hổng bảo mật nếu bạn không sử dụng những phương pháp hay mà bạn đang đề cập đến. BTW, tôi nghi ngờ những người nói "Tôi sẽ không bao giờ hiểu ..." khi họ có thể dễ dàng ... :-)
Andris
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.