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

Posted

技术标签:

【中文标题】PDO 支持多个查询(PDO_MYSQL、PDO_MYSQLND)【英文标题】:PDO support for multiple queries (PDO_MYSQL, PDO_MYSQLND) 【发布时间】:2011-09-14 20:34:05 【问题描述】:

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

PDO_MySQL 更危险 应用比任何其他传统 MySQL 应用程序。传统的 MySQL 只允许单个 SQL 查询。在 PDO_MySQL 没有这个限制, 但你冒着被注射的风险 多个查询。

发件人:Protection against SQL Injection using PDO and Zend Framework (June 2010; by Julian)

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

【问题讨论】:

使用 SQL 事务。 为什么要使用多个查询?它们没有被交易,就像你一个接一个地执行它们一样。恕我直言,没有优点,只有缺点。在 SQLInjection 的情况下,您允许攻击者为所欲为。 现在是 2020 年,PDO 确实支持这一点 - 请参阅下面的答案。 【参考方案1】:

据我所知,PDO_MYSQLNDphp 5.3 中取代了 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 起可用)中设置正确的编码(反映实际数据编码)。否则there can be a slight possibility for SQL injection if some odd encoding is used.

【讨论】:

答案本身没有问题。它解释了如何执行多个查询。您认为答案有缺陷的假设来自查询包含用户输入的假设。在一些有效的用例中,一次发送多个查询可以提高性能。您可以建议使用程序作为这个问题的替代答案,但这并不会使这个答案变得糟糕。 这个答案中的代码很糟糕,并且促进了一些非常有害的做法(对准备语句使用仿真,这使得代码容易受到 SQL 注入漏洞)。不要使用它。 这个答案没有错,尤其是仿真模式。它在 pdo_mysql 中默认启用,如果有任何问题,已经有数千次注入。但是还没有人接近。就这样。 事实上,只有 ircmaxell 不仅能提供情感,还能提供一些争论。但是,他带来的链接完全无关紧要。第一个根本不适用,因为它明确表示 “PDO 始终不受此错误的影响。” 而第二个可以通过设置适当的编码简单地解决。因此,它值得一提,而不是警告,也不那么吸引人。 作为一个正在编写迁移工具的人发言,该工具使用只有我们的开发人员编写的 SQL(即 SQL 注入不是问题),这对我有很大帮助,任何表明此代码的 cmets有害不完全了解其使用的所有上下文。【参考方案2】:

折腾了半天,发现PDO有一个bug...

--

//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;" 上出错,但它不会。

这是我找到此信息的地方: Invalid PDO query does not return an error

这是错误: 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-&gt;exec("valid-stmt1; non-sense; valid-stmt3;"); 而没有前两个执行人员,它是否有效?我可以把它发给throw errors in the middle, but not when executed after successful execs。 不,它没有。这就是 PDO 的错误。 我的错,这 3 个 $pdo-&gt;exec("") 是相互独立的。我现在将它们分开以表明它们不必按顺序出现问题。这 3 种是在一个 exec 语句中运行多个查询的 3 种配置。 有趣。你有机会看到我发布的问题吗?我想知道这是否已被部分修补,因为如果它是页面上唯一的exec,我会抛出错误,但如果我运行多个exec,每个exec 都包含多个SQL 语句,那么我会在这里重现相同的错误。但如果它是页面上唯一的exec,那么我无法复制它。 你页面上的那个exec有多个语句吗?【参考方案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 语句端点处拆分。没有错误检查,没有注入保护。使用前了解您的用途。就个人而言,我使用它来为集成测试播种原始迁移文件。

【讨论】:

如果您的 SQL 文件包含任何 mysql 内置命令,这将失败...如果 SQL 文件很大,它也可能会超出您的 PHP 内存限制...如果您的SQL 包含过程或触发器定义......它不好的原因有很多。【参考方案4】:

像成千上万的人一样,我正在寻找这个问题:可以同时运行多个查询,如果出现一个错误,则不会运行 我到处都去这个页面 但是虽然这里的朋友给出了很好的答案,但是这些答案对我的问题没有好处 于是我写了一个运行良好的函数,使用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();
    

注意: 此解决方案可帮助您同时运行多个语句, 如果出现不正确的a语句,它不会执行任何其他语句

【讨论】:

【参考方案5】:

试过下面的代码

 $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 = ... 之后添加$db-&gt;setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

然后得到空白页

如果改为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

所以我的结论是不可能注射...

【讨论】:

你应该把它作为一个新问题来引用这个 我尝试过的结果并没有太多问题。还有我的结论。最初的问题很老,目前可能不是实际问题。 不确定这与问题中的任何内容有什么关系。 有问题的是单词but you risk to be injected with multiple queries.我的回答是关于注射【参考方案6】:

试试这个功能:多重查询和多值插入。

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

【讨论】:

【参考方案7】:

PDO 确实支持这一点(截至 2020 年)。只需像往常一样对 PDO 对象执行 query() 调用,用 ; 分隔查询。然后 nextRowset() 进入下一个 SELECT 结果,如果你有多个。结果集将与查询的顺序相同。显然要考虑安全隐患——所以不要接受用户提供的查询、使用参数等。例如,我将它与代码生成的查询一起使用。

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

【讨论】:

我永远不会理解这种推理,“这是一个安全漏洞,忽略了所有推荐的良好实践,因此您需要考虑安全隐患。”谁应该考虑一下?他们应该在什么时候思考——在使用此代码之前还是在他们被黑客入侵之后?在编写此功能或将其提供给其他人之前,您为什么不先考虑一下? 亲爱的@YourCommonSense 一次性运行多个查询有助于提高性能,减少网络流量+服务器可以优化相关查询。我的(简化的)示例只是为了介绍使用它所需的方法。仅当您不使用您所指的那些良好做法时,这才是一个安全漏洞。顺便说一句,我怀疑那些说“我永远不会理解...”的人,而他们很容易... :-)

以上是关于PDO 支持多个查询(PDO_MYSQL、PDO_MYSQLND)的主要内容,如果未能解决你的问题,请参考以下文章

安装 PDO_MYSQL

PHP中PDO_MYSQL扩展安装问题error: mysql configure failed.

pdo_mysql安装

已加载 PDO 和 pdo_mysql 但未找到类

php中的Mysqlnd、PDO和PDO_Mysql扩展有啥区别?

PHP之pdo_mysql扩展安装步骤