从 PDO 准备语句中检索(或模拟)完整查询

Posted

技术标签:

【中文标题】从 PDO 准备语句中检索(或模拟)完整查询【英文标题】:Retrieve (or simulate) full query from PDO prepared statement 【发布时间】:2011-04-14 20:21:51 【问题描述】:

两年前我偶然发现了this question。

有没有办法在准备好的语句上调用 PDOStatement::execute() 时执行原始 SQL 字符串?出于调试目的,这将非常有用。

获胜的答案表明

[...] 你也可以得到你想要的,如果你 设置 PDO 属性 PDO::ATTR_EMULATE_PREPARES。在这 模式,PDO 内插参数到 SQL 查询并发送整个 执行时查询()。

但它没有提到如何获取结果查询字符串。我知道这在性能方面是个坏主意,但这在调试模式下不会打扰我。有人知道怎么做吗?

PS 如果有什么方法可以让我重新打开/引起人们对原来两年前的话题而不是打开一个新话题的关注,请告诉我。

【问题讨论】:

接受的答案确实告诉您在哪里发现查询:在您的数据库服务器的日志中。 @Rob 是的,我也读过。它说:“mysql 的一般查询日志确实显示了在执行()之后插入值的最终 SQL”。但接着它继续说“如果......你也可以得到你想要的东西”,这表明还有第二种方法。这就是我想知道的。如何在不访问完整 mysql 服务器日志的情况下执行此操作。 我认为这意味着您可以让 php 在客户端,然后服务器日志中的 only 将是它执行的实际 SQL 查询。也就是说,答案是描述了在服务器日志中获取查询的两种不同方式。 +1 对于该评论!但我仍然希望你是错的:) 开启服务器日志是最好的方法:如果我没有等上几个小时,它会节省我几个小时:instructions for logging PDO prepared statements 【参考方案1】:

我相信在这个问题中提到的原始问题中提到了这一点。然而 实际上应该有一种检索这些数据的方法。

PDOStatement::debugDumpParams

但是,它目前并没有像记录的那样工作。这里有一个错误报告和补丁提交http://bugs.php.net/bug.php?id=52384,以防有人有兴趣投票。在它修复之前,您似乎只能使用查询日志记录或使用 PDO::ATTR_STATEMENT_CLASS 属性设置自定义语句类。

【讨论】:

谢谢!我已经放弃了对这个的希望:)【参考方案2】:

Afaik,PDO 并没有真正向您公开它。在开发服务器上,您可以启用 MySQL 的通用查询日志(如果您使用的是该日志),可能使用 sql_log_off 进行更多控制,这确实需要 SUPER 权限。

【讨论】:

【参考方案3】:

如果您无法从 PDO 本身获取它,请考虑为 PDOStatement::execute() 使用包装器类,它将记录 SQL 查询和值,然后在语句上调用 execute()。您必须重构代码才能使用新类。

作为旁注,我看到 PDOStatement 有一个类变量 $queryString 保存正在使用的查询。必须从传递给execute()bindParam() 的任何内容中检索这些值。

首先是一些用于记录的实用函数:

//removes newlines and extra spaces from print_r output
function str_squeeze($str) 

    if (is_array($str)) 
        $str = print_r($str, true);
    

    $str = preg_replace('/[(\r)?\n|\t]/', ' ', $str);
    $str = trim(ereg_replace(' +', ' ', $str));
    return $str;


function logger($str) 
    //log it somewhere

选项 1:围绕 PDOStatement 的包装类

class My_PDO_Utils 

    public static function execute(PDOStatement &$stm, $values = array()) 
        logger("QUERY: " . $stm->queryString . ", values = " . str_squeeze($values)) ;
        return $stm->execute($values) ;

    


那么你的代码必须是:

$stm = $db->prepare("SELECT * FROM table2 WHERE id = ?") ;

$res = My_PDO_Utils::execute($stm, array(79)) ;

而不是

$res = $stm->execute(array(79)) ;

再想一想,你可以更进一步:

选项 2:扩展 PDO 和 PDOStatement

如果你想冒险,你可以扩展 PDOStatement 来为你做日志记录,并且 PDO 来返回你扩展的 PDOStatement 类。这将需要尽可能少的重构,即只需将new PDO() 更改为new MY_PDO(),但在其实现中可能会变得棘手,因为您需要在 MY_PDOStatement 中明确定义所需的任何 PDOStatement 功能,以便正确调用它。

class My_PDO extends PDO 

    public function prepare($sql, $options = array()) 

        //do normal call
        $stm = parent::prepare($sql, $options) ;

        //encapsulate it in your pdostatement wrapper
        $myStm = new My_PDOStatement() ;
        $myStm->stm = $stm ;

        return $myStm ;

    



class My_PDOStatement extends PDOStatement 

    /**
     *
     * @var PDOStatement
     */
    public $stm ;

    public function execute($values) 

        logger("QUERY: " . $this->stm->queryString . ", values = " . str_squeeze($values)) ;
        return $this->stm->execute($values) ;

    

    public function fetchAll($fetch_style = PDO::FETCH_BOTH, $column_index = 0, $ctor_args = array()) 
        return $this->stm->fetchAll($fetch_style, $column_index, $ctor_args) ;
    



但现在你的代码可以是:

$db = new My_PDO($dsn, $user, $pass) ;

$stm = $db->prepare("SELECT * FROM table2 WHERE id = ?") ;

$res = $stm->execute(array(79)) ;    
$row = $stm->fetchAll() ;

【讨论】:

感谢所有的努力!我目前正在通过扩展 PDO 和 PDOStatement 做一些非常相似的事情(您可以使用一个命令告诉 PDO 使用您的语句类)。据我所知,反对这种方法有两个很好的论据: 1. 每次 PDO API 发生轻微变化时,您都必须更新您的类。 2. 通过重新创建准备好的语句(如果考虑到各种语法、处理引用等,这真的非常复杂),当使用 PDO::ATTR_EMULATE_PREPARES 时,您将复制 PDO 的功能 出于我自己的需要,我目前正在使用静态函数围绕 PEAR::DB 和 MDB2 进行适当的日志记录等,但我正在评估 PDO,所以这是一个足够好的借口玩那个:) (1) 是我主要关心的问题,需要维护扩展类以实现升级中所有必需的功能。对于 (2),它不需要重新创建任何语句,因为它只是围绕 PDOStatement 进行包装,除非我误解了这一点。 你可以用'?'编写查询语法和 ':var' 语法,您可以使用 bindValue() 或 bindParam() 添加值 - 其中之一使用引用。最后,您可以通过将参数添加到执行函数来在最后时刻添加参数。在您的查询日志功能中捕获所有这些将是相当多的工作,而且显然 PDO 已经可以做到这一点(如果您使用模拟准备功能)所以您将重写一些已经为您完成的困难在较低的级别... 啊,我明白你的意思了。对,是的。我的虚拟代码假设只使用execute($values),就像我在自己的数据库包装器中所做的那样。明白了。 ereg* 函数已弃用。你应该使用preg*函数:***.com/questions/6270004/…【参考方案4】:

我认为最好的方法是使用 mysql 日志来显示最后运行的查询,因为直接在 php 中获取它们是一个拖累。

从 How to show the last queries executed on MySQL?第一个答案:

此外,对于那些拥有 MySQL >= 5.1.12 的人:

SET GLOBAL log_output = 'TABLE';
SET GLOBAL general_log = 'ON';

看一下表mysql.general_log 如果您更喜欢输出到文件:

SET GLOBAL log_output = "FILE"; which is set by default.
SET GLOBAL general_log_file = "/path/to/your/logfile.log"
SET GLOBAL general_log = 'ON';

我更喜欢这种方法,因为:

您没有编辑 my.cnf 文件并可能永久开启日志记录 您不是在文件系统中四处寻找查询日志——或者更糟糕的是,因为需要完美的目的地而分心。 /var/log /var/data/log /opt /home/mysql_savior/var 重新启动服务器会将您留在您开始的位置(日志已关闭) 有关详细信息,请参阅 MySQL 5.1 参考手册 - 服务器系统变量 - general_log

【讨论】:

有了这篇文章中的解决方案数量,这只是一个解决方案。你是对的。【参考方案5】:

以下静态方法采用 PDO 查询模板(带有 ? 和/或 :name 占位符的 SQL 查询)并插入参数:

static public function getDebugFullQuery($query, $params = array())
    if(is_array($params) && count($params))

        $search = [];
        $replace = [];

        foreach($params as $k => $p)
            $pos = strpos($query, ":$k");
            if($pos !== false)
                $query = substr($query, 0, $pos) . "%!-!$k!-!%" . substr($query, $pos + strlen($k) + 1);
            
            else 
                $pos = strpos($query, "?");
                if($pos !== false)
                    $query = substr($query, 0, $pos) . "%!-!$k!-!%" . substr($query, $pos + 1);
                
                else 
                    break;
                
            

            $search[] = "%!-!$k!-!%";
            $replace[] = "'" . str_replace(array("\r", "\n", "'"), array("\\\\r", "\\\\n", "\\'"), $p) . "'";
        

        if(count($search))
            $query = str_replace($search, $replace, $query);
        
    

    return $query;

如方法名称所示,您应该仅将其用于调试目的。

【讨论】:

以上是关于从 PDO 准备语句中检索(或模拟)完整查询的主要内容,如果未能解决你的问题,请参考以下文章

带有位域的 PDO 准备好的语句

从 PDO 准备好的语句中获取原始 SQL 查询字符串

从 PDO 准备好的语句中获取原始 SQL 查询字符串

使用 PDO 准备语句从搜索字段中使用多个关键字进行 LIKE 查询

PDO 是不是仍在为 MySQL 模拟准备好的语句?

使用 PDO 准备好的语句 MySQL 错误