다중 쿼리에 대한 PDO 지원 (PDO_MYSQL, PDO_MYSQLND)


102

PDO는 하나의 문에서 실행되는 여러 쿼리를 지원하지 않는다는 것을 알고 있습니다. 나는 구글링을 해왔고 PDO_MYSQL 및 PDO_MYSQLND에 대해 이야기하는 게시물을 거의 찾지 못했습니다.

PDO_MySQL은 다른 기존 MySQL 애플리케이션보다 더 위험한 애플리케이션입니다. 기존 MySQL은 단일 SQL 쿼리 만 허용합니다. PDO_MySQL에는 이러한 제한이 없지만 여러 쿼리가 주입 될 위험이 있습니다.

From : PDO 및 Zend Framework를 사용한 SQL 주입 방지 (2010 년 6 월, Julian 작성)

PDO_MYSQL 및 PDO_MYSQLND가 여러 쿼리를 지원하는 것처럼 보이지만 이에 대한 자세한 정보를 찾을 수 없습니다. 이러한 프로젝트가 중단 되었습니까? 이제 PDO를 사용하여 여러 쿼리를 실행할 수있는 방법이 있습니까?


4
SQL 트랜잭션을 사용하십시오.
— tereško 2013-10-13

여러 쿼리를 사용하려는 이유는 무엇입니까? 그것들은 거래되지 않고, 당신이 그것들을 차례로 실행하는 것과 똑같습니다. IMHO 장점은없고 단점 만 있습니다. SQLInjection의 경우 공격자가 원하는대로 할 수 있습니다.
— mleko 2014

이제 2020 년이고 PDO는 이것을 지원합니다. 아래의 내 대답을 참조하십시오.
— Andris

답변:


141

아시다시피 PHP 5.3에서 PDO_MYSQLND대체 PDO_MYSQL되었습니다. 혼란스러운 부분은 이름이 여전히 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 삽입이 약간 발생할 수 있습니다 .


37
대답 자체에는 잘못된 것이 없습니다. 여러 쿼리를 실행하는 방법을 설명합니다. 답변에 결함이 있다는 가정은 쿼리에 사용자 입력이 포함되어 있다는 가정에서 비롯됩니다. 한 번에 여러 쿼리를 통해 전송하면 성능에 도움이 될 수있는 유효한 사용 사례가 있습니다. 이 질문에 대한 대안으로 절차 사용을 제안 할 수 있지만 그렇다고이 대답이 나쁘지는 않습니다.
— Gajus 2014-04-21

9
이 답변의 코드는 나쁘고 일부 매우 해로운 관행을 조장합니다 ( SQL 주입 취약점에 대한 코드를 열어주는 prepares 문에 에뮬레이션 사용 ). 그것을 사용하지 마십시오.
— tereško 2014

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;");이전 두 exec없이 만 실행하면 작동합니까 ? 중간에 오류 를 던질 수는 있지만 성공적인 execs 후에 실행될 때는 아닙니다 .
— Jeff Puckett

아니, 그렇지 않습니다. 이것이 PDO의 버그입니다.
— Sai Phaninder Reddy J

1
내 잘못은, 그 3 $pdo->exec("")은 서로 독립적입니다. 이제 문제가 발생하기 위해 순서대로있을 필요가 없음을 나타 내기 위해 분할했습니다. 이 세 가지는 하나의 exec 문에서 여러 쿼리를 실행하는 세 가지 구성입니다.
— Sai Phaninder Reddy J

흥미 롭군. 내 게시 된 질문을 볼 기회가 있었습니까? exec페이지 에만 있으면 오류가 발생할 수 있기 때문에 부분적으로 패치되었는지 궁금 하지만 exec여러 SQL 문을 사용하여 여러 개를 실행하면 여기에서 동일한 버그를 재현합니다. 하지만 exec페이지에 유일한 것이라면 재현 할 수 없습니다.
— Jeff Puckett

하나습니까 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 내장 명령이 포함되어 있으면 실패합니다 ... SQL 파일이 크면 PHP 메모리 제한도 날려 버릴 것입니다 ... ;SQL에 프로 시저 또는 트리거 정의가 포함 된 경우 중단시 분할 ... 좋지 않은 이유.
— Bill Karwin

1

수천 명의 사람들과 마찬가지로이 질문을 찾고
있습니다. 여러 쿼리를 동시에 실행할 수 있고 오류가 하나만 발생하면 아무 것도 실행되지 않습니다.이 페이지를 어디에서나 방문
했지만 여기에있는 친구들이 좋은 대답을했지만이 대답은 좋지 않았습니다. 내 문제
그래서 나는 잘 작동하고 SQL 주입에 거의 문제가없는 함수를 작성했습니다.
비슷한 질문을 찾는 분들에게 도움이 될 수 있으니 여기에 넣어서

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

내가 시도한 결과로 많은 질문이 아닙니다. 그리고 내 결론. 초기 질문은 오래되었으며 현재로서는 실제가 아닐 수 있습니다.
— Andris

이것이 질문의 어떤 것과 관련이 있는지 확실하지 않습니다.
— cHao

문제의 단어는 but you risk to be injected with multiple queries.내 대답은 주입에 관한 것입니다
— 안드리

0

이 기능을 시도하십시오 : mltiple 쿼리 및 다중 값 삽입.

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 년 기준). 평상시처럼 PDO 객체에 대해 query () 호출을 수행하여 쿼리를; 다음 SELECT 결과가 여러 개인 경우 nextRowset ()을 사용하여 다음 SELECT 결과로 이동합니다. 결과 집합은 쿼리와 동일한 순서입니다. 보안에 미치는 영향에 대해 분명히 생각해보십시오. 사용자가 제공 한 쿼리를 받아들이거나 매개 변수를 사용하지 마십시오. 예를 들어 코드에 의해 생성 된 쿼리와 함께 사용합니다.

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

나는 이런 종류의 추론을 결코 이해하지 못할 것이다. "여기에 권장되는 모든 모범 사례를 무시하는 보안에 큰 구멍이있는 코드가 있으므로 보안 관련 사항에 대해 생각해야합니다." 누가 그것에 대해 생각해야합니까? 이 코드를 사용하기 전이나 해킹당한 후에 생각해야 할 때 이 함수를 작성하거나 다른 사람에게 제공하기 전에 먼저 생각해 보는 것이 어떻습니까?
— 당신의 상식

친애하는 @YourCommonSense는 한 번에 여러 쿼리를 실행하여 성능을 높이고 네트워크 트래픽을 줄이고 서버는 관련 쿼리를 최적화 할 수 있습니다. 내 (간단한) 예제는 사용하는 데 필요한 방법을 소개하는 것을 의미했습니다. 당신이 언급하는 좋은 관행을 사용하지 않는 경우에만 보안 구멍입니다. BTW, 나는 그들이 쉽게 할 수있을 때 "나는 결코 이해할 수 없을 것이다 ..."라고 말하는 사람들을 의심한다. :-)
— Andris
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.