PDO支持多个查询(PDO_MYSQL,PDO_MYSQLND)


102

我确实知道PDO不支持在一条语句中执行多个查询。我一直在Google搜索,发现很少有关于PDO_MYSQL和PDO_MYSQLND的帖子。

PDO_MySQL比其他任何传统MySQL应用程序都更加危险。传统的MySQL仅允许一个SQL查询。在PDO_MySQL中没有这种限制,但是您可能会被注入多个查询。

来自:使用PDO和Zend Framework防止SQL注入(2010年6月;朱利安(Julian))

似乎PDO_MYSQL和PDO_MYSQLND确实提供了对多个查询的支持,但我无法找到有关它们的更多信息。这些项目被中止了吗?现在有什么方法可以使用PDO运行多个查询。


4
使用SQL事务。
tereško

您为什么要使用多个查询?它们不会被处理,就像您一次又一次执行它们一样。恕我直言,没有优点,只有缺点。如果使用SQLInjection,则允许攻击者执行他想做的任何事情。
mleko

现在是2020年,PDO确实支持这一点-请参阅下面的答案。
Andris

Answers:


141

据我所知,已在PHP 5.3中PDO_MYSQLND替换PDO_MYSQL。令人困惑的是,名字仍然是PDO_MYSQL。因此,现在ND是MySQL + PDO的默认驱动程序。

总体而言,一次执行多个查询需要:

  • PHP 5.3以上
  • mysqlnd
  • 模拟准备好的语句。确保PDO::ATTR_EMULATE_PREPARES设置为1(默认)。另外,您可以避免使用准备好的语句而$pdo->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年

9
此答案中的代码很糟糕,并且会推广一些非常有害的做法(将仿真用于prepares语句,这会使代码容易受到SQL注入漏洞的侵害)。不要使用它。
tereško

17
这个答案没有问题,尤其是仿真模式。默认情况下在pdo_mysql中启用了该功能,如果有任何问题,则已经有数千次注入。但是还没有人接近。这样吧。
您的常识2014年

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;",返回true并认为一切运行良好。

我希望它会在上出错,"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 之后执行时却不能
杰夫·普基特

不,不是。那是PDO的错误。
Sai Phaninder Reddy J

1
不好意思,那三个$pdo->exec("")是彼此独立的。我现在将它们拆分开来表明它们不必按顺序出现问题。这3种配置是在一个exec语句中运行多个查询的3种配置。
赛潘纳德·雷迪J

有趣。您有机会看到我发布的问题吗?我想知道这是否已部分打补丁,因为如果它是exec页面上唯一的错误,我可以抛出该错误,但是如果我exec每个命令都运行多个SQL语句,那么我将在此处重现相同的错误。但是,如果它是exec页面上的唯一内容,那么我将无法重现。
杰夫·普基特

exec您页面上的那个有多个语句吗?
赛潘纳德·雷迪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 '16

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
您应该在提到这个问题时提出一个新问题
您的常识

由于我做了什么,没有太多的问题。和我的结论。最初的问题很旧,目前可能还不实际。
Andris 2014年

不知道这与问题中的任何内容有什么关系。
cHao 2014年

问题是的话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年)。只需像往常一样对PDO对象执行query()调用即可,用;分隔查询;如果有多个,则nextRowset()转到下一个SELECT结果。结果集与查询的顺序相同。显然考虑一下安全隐患-因此不要接受用户提供的查询,使用参数等。例如,我将其与代码生成的查询一起使用。

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

我从不理解这种推理,“这里的代码是安全性中的一个大漏洞,忽略了所有建议的良好实践,因此您需要考虑安全性隐患。” 谁应该考虑?他们何时应该考虑-在使用此代码之前或被黑客入侵之后?在编写此功能或将其提供给其他人之前,您为什么不首先考虑它?
您的常识

亲爱的@YourCommonSense一次性运行多个查询有助于提高性能,减少网络流量,服务器可以优化相关查询。我的(简化的)示例仅旨在介绍使用它的方法。仅当您不使用所指的良好做法时,这才是安全漏洞。顺便说一句,我怀疑有人说“我永远不会理解...”,而他们
Andris
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.