如何调试PDO数据库查询?

时间:2010-03-09 17:43:24

标签: php sql pdo

在迁移到PDO之前,我通过连接字符串在PHP中创建了SQL查询。如果我得到数据库语法错误,我可以回显最终的SQL查询字符串,在数据库上自己尝试,并调整它直到我修复错误,然后将其放回代码中。

准备好的PDO语句更快,更好,更安全,但有一件事困扰我:我从未看到最终查询,因为它被发送到数据库。当我在Apache日志或我的自定义日志文件中出现语法错误时(我在catch块中记录错误),我看不到导致它们的查询。

有没有办法捕获PDO发送到数据库的完整SQL查询并将其记录到文件中?

18 个答案:

答案 0 :(得分:95)

你这样说:

  

我从来没有看到最终的查询   发送到数据库

嗯,实际上,在使用预准备语句时,没有“最终查询

  • 首先,将一条语句发送给DB,并在那里准备
    • 数据库解析查询,并构建它的内部表示
  • 并且,当您绑定变量并执行该语句时,只会将变量发送到数据库
    • 数据库将值“注入”其语句的内部表示


所以,回答你的问题:

  

有没有办法捕获完整的   PDO发送到数据库的SQL查询   并将其记录到文件中?

否:由于任何地方都没有“完整的SQL查询”,因此无法捕获它。


出于调试目的,您可以做的最好的事情是通过将值注入语句的SQL字符串中来“重构”一个“真正的”SQL查询。

在这种情况下,我通常做的是:

  • 使用占位符
  • 回显与该语句对应的SQL代码
  • 并在之后使用var_dump (或等效的)来显示参数值
  • 这通常足以看到可能的错误,即使您没有可以执行的任何“真实”查询。

这在调试方面并不是很好 - 但这是准备好的陈述的价格及其带来的好处。

答案 1 :(得分:83)

查看数据库日志

虽然 Pascal MARTIN 是正确的,PDO不会立即将完整的查询发送到数据库, ryeguy 建议使用DB的日志记录功能实际允许我可以看到由数据库组装和执行的完整查询。

以下是: (这些说明适用于Windows机器上的MySQL - 您的里程可能会有所不同)

  • my.ini的{​​{1}}部分下,添加[mysqld]命令,例如log
  • 重启MySQL。
  • 它将开始记录该文件中的每个查询。

该文件将快速增长,因此请确保在完成测试后将其删除并关闭日志记录。

答案 2 :(得分:14)

您可能想要做的是使用debugDumParams() 它不会为您构建准备好的语句,但会显示您的参数。

答案 3 :(得分:14)

当然可以使用此模式进行调试{{ PDO::ATTR_ERRMODE }} 只需在查询之前添加新行,然后您将显示调试行。

$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$db->query('SELECT *******');  

答案 4 :(得分:12)

旧帖子,但也许有人会觉得这很有用;

function pdo_sql_debug($sql,$placeholders){
    foreach($placeholders as $k => $v){
        $sql = preg_replace('/:'.$k.'/',"'".$v."'",$sql);
    }
    return $sql;
}

答案 5 :(得分:9)

这是一个函数,可以看看有效的SQL是什么,改编自“{3}}的”Mark“评论:

function sql_debug($sql_string, array $params = null) {
    if (!empty($params)) {
        $indexed = $params == array_values($params);
        foreach($params as $k=>$v) {
            if (is_object($v)) {
                if ($v instanceof \DateTime) $v = $v->format('Y-m-d H:i:s');
                else continue;
            }
            elseif (is_string($v)) $v="'$v'";
            elseif ($v === null) $v='NULL';
            elseif (is_array($v)) $v = implode(',', $v);

            if ($indexed) {
                $sql_string = preg_replace('/\?/', $v, $sql_string, 1);
            }
            else {
                if ($k[0] != ':') $k = ':'.$k; //add leading colon if it was left out
                $sql_string = str_replace($k,$v,$sql_string);
            }
        }
    }
    return $sql_string;
}

答案 6 :(得分:8)

没有。 PDO查询不是在客户端准备的。 PDO只是将SQL查询和参数发送到数据库服务器。 数据库是替换(?的)。您有两种选择:

  • 使用您的数据库记录功能(但即使这样,它通常显示为两个单独的语句(即“非最终”),至少与Postgres一致)
  • 输出SQL查询和 参数和拼凑在一起 自己

答案 7 :(得分:5)

除了检查错误日志之外,几乎没有关于错误显示的内容, 但这是一个非常有用的功能:

<?php
/* Provoke an error -- bogus SQL syntax */
$stmt = $dbh->prepare('bogus sql');
if (!$stmt) {
    echo "\PDO::errorInfo():\n";
    print_r($dbh->errorInfo());
}
?>

source link

很明显,可以修改此代码以用作异常消息 或任何其他类型的错误处理

答案 8 :(得分:3)

例如,你有这个pdo语句:

$query="insert into tblTest (field1, field2, field3)
values (:val1, :val2, :val3)";
$res=$db->prepare($query);
$res->execute(array(
  ':val1'=>$val1,
  ':val2'=>$val2,
  ':val3'=>$val3,
));

现在您可以通过定义这样的数组来获取执行的查询:

$assoc=array(
  ':val1'=>$val1,
  ':val2'=>$val2,
  ':val3'=>$val3,
);
$exQuery=str_replace(array_keys($assoc), array_values($assoc), $query);
echo $exQuery;

答案 9 :(得分:2)

搜索互联网我发现这是一个可以接受的解决方案。使用不同的类而不是PDO,并通过魔术函数调用调用PDO函数。我不确定这会造成严重的性能问题。但它可以在PDO中添加合理的日志记录功能之前使用。

因此,根据此thread,您可以为PDO连接编写一个包装器,它可以在出现错误时记录并引发异常。

这是一个简单的例子:

class LoggedPDOSTatement extends PDOStatement    {

function execute ($array)    {
    parent::execute ($array);
    $errors = parent::errorInfo();
    if ($errors[0] != '00000'):
        throw new Exception ($errors[2]);
    endif;
  }

}

因此您可以使用该类而不是PDOStatement:

$this->db->setAttribute (PDO::ATTR_STATEMENT_CLASS, array ('LoggedPDOStatement', array()));

这里提到的PDO装饰器实现:

class LoggedPDOStatement    {

function __construct ($stmt)    {
    $this->stmt = $stmt;
}

function execute ($params = null)    {
    $result = $this->stmt->execute ($params); 
    if ($this->stmt->errorCode() != PDO::ERR_NONE):
        $errors = $this->stmt->errorInfo();
        $this->paint ($errors[2]);
    endif;
    return $result;
}

function bindValue ($key, $value)    {
    $this->values[$key] = $value;    
    return $this->stmt->bindValue ($key, $value);
}

function paint ($message = false)    {
    echo '<pre>';
    echo '<table cellpadding="5px">';
    echo '<tr><td colspan="2">Message: ' . $message . '</td></tr>';
    echo '<tr><td colspan="2">Query: ' . $this->stmt->queryString . '</td></tr>';
    if (count ($this->values) > 0):
    foreach ($this->values as $key => $value):
    echo '<tr><th align="left" style="background-color: #ccc;">' . $key . '</th><td>' . $value . '</td></tr>';
    endforeach;
    endif;
    echo '</table>';
    echo '</pre>';
}

function __call ($method, $params)    {
    return call_user_func_array (array ($this->stmt, $method), $params); 
}

}

答案 10 :(得分:2)

要在 WAMP 中记录MySQL,您需要编辑my.ini(例如在wamp \ bin \ mysql \ mysql5.6.17 \ my.ini下)

并添加到[mysqld]

general_log = 1
general_log_file="c:\\tmp\\mysql.log"

答案 11 :(得分:1)

这是我用&#34;已解决&#34;返回SQL查询的函数。参数。

function paramToString($query, $parameters) {
    if(!empty($parameters)) {
        foreach($parameters as $key => $value) {
            preg_match('/(\?(?!=))/i', $query, $match, PREG_OFFSET_CAPTURE);
            $query = substr_replace($query, $value, $match[0][1], 1);
        }
    }
    return $query;
    $query = "SELECT email FROM table WHERE id = ? AND username = ?";
    $values = [1, 'Super'];

    echo paramToString($query, $values);

假设你像这样执行

$values = array(1, 'SomeUsername');
$smth->execute($values);

此功能不会为查询添加引号,但可以为我完成工作。

答案 12 :(得分:0)

我在这里创建了一个现代的Composer加载的项目/存储库:

PDO-调试

找到项目的GitHub home here,查看blog post explaining it here。在composer.json中添加一行,然后就可以像这样使用它:

echo debugPDO($sql, $parameters);

$ sql是原始SQL语句,$ parameters是您的参数数组:键是占位符名称(“:user_id”)或未命名参数的编号(“?”),值为..好吧,价值。

背后的逻辑:这个脚本只是简化参数并将它们替换为提供的SQL字符串。 99%的用例非常简单但超级有效。注意:这只是一个基本的仿真,而不是真正的PDO调试(因为这是不可能的,因为PHP将原始SQL和参数分离到MySQL服务器)。

非常感谢来自StackOverflow线程Getting raw SQL query string from PDO bigwebguy Mike ,用于编写此脚本背后的整个主要功能。大了!

答案 13 :(得分:0)

我在解决PDO豁免的解决方案中遇到的问题是它只捕获PDO豁免(duh),但没有捕获被注册为php错误的语法错误(我不知道为什么这是,但“为什么”与解决方案无关)。我所有的PDO调用来自单个表模型类,我扩展了所有与所有表的交互...当我尝试调试代码时这个复杂的事情,因为错误将注册我执行调用的PHP代码行叫,但没有告诉我这个电话是从哪里来的。我使用以下代码来解决这个问题:

/**
 * Executes a line of sql with PDO.
 * 
 * @param string $sql
 * @param array $params
 */
class TableModel{
    var $_db; //PDO connection
    var $_query; //PDO query

    function execute($sql, $params) { 
        //we're saving this as a global, so it's available to the error handler
        global $_tm;
        //setting these so they're available to the error handler as well
        $this->_sql = $sql;
        $this->_paramArray = $params;            

        $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->_query = $this->_db->prepare($sql);

        try {
            //set a custom error handler for pdo to catch any php errors
            set_error_handler('pdoErrorHandler');

            //save the table model object to make it available to the pdoErrorHandler
            $_tm = $this;
            $this->_query->execute($params);

            //now we restore the normal error handler
            restore_error_handler();
        } catch (Exception $ex) {
            pdoErrorHandler();
            return false;
        }            
    }
}

因此,上面的代码捕获了两个PDO异常和php语法错误,并以同样的方式处理它们。我的错误处理程序看起来像这样:

function pdoErrorHandler() {
    //get all the stuff that we set in the table model
    global $_tm;
    $sql = $_tm->_sql;
    $params = $_tm->_params;
    $query = $tm->_query;

    $message = 'PDO error: ' . $sql . ' (' . implode(', ', $params) . ") \n";

    //get trace info, so we can know where the sql call originated from
    ob_start();
    debug_backtrace(); //I have a custom method here that parses debug backtrace, but this will work as well
    $trace = ob_get_clean();

    //log the error in a civilized manner
    error_log($message);

    if(admin(){
        //print error to screen based on your environment, logged in credentials, etc.
        print_r($message);
    }
}

如果有人对如何将相关信息提供给我的错误处理程序比将表模型设置为全局变量有任何更好的想法,我会很高兴听到它并编辑我的代码。

答案 14 :(得分:0)

这段代码非常适合我:

echo str_replace(array_keys($data), array_values($data), $query->queryString);

不要忘记用你的名字替换$ data和$ query

答案 15 :(得分:0)

我使用此类来调试PDO(使用Log4PHP

<?php

/**
 * Extends PDO and logs all queries that are executed and how long
 * they take, including queries issued via prepared statements
 */
class LoggedPDO extends PDO
{

    public static $log = array();

    public function __construct($dsn, $username = null, $password = null, $options = null)
    {
        parent::__construct($dsn, $username, $password, $options);
    }

    public function query($query)
    {
        $result = parent::query($query);
        return $result;
    }

    /**
     * @return LoggedPDOStatement
     */
    public function prepare($statement, $options = NULL)
    {
        if (!$options) {
            $options = array();
        }
        return new \LoggedPDOStatement(parent::prepare($statement, $options));
    }
}

/**
 * PDOStatement decorator that logs when a PDOStatement is
 * executed, and the time it took to run
 * @see LoggedPDO
 */
class LoggedPDOStatement
{

    /**
     * The PDOStatement we decorate
     */
    private $statement;
    protected $_debugValues = null;

    public function __construct(PDOStatement $statement)
    {
        $this->statement = $statement;
    }

    public function getLogger()
    {
        return \Logger::getLogger('PDO sql');
    }

    /**
     * When execute is called record the time it takes and
     * then log the query
     * @return PDO result set
     */
    public function execute(array $params = array())
    {
        $start = microtime(true);
        if (empty($params)) {
            $result = $this->statement->execute();
        } else {
            foreach ($params as $key => $value) {
                $this->_debugValues[$key] = $value;
            }
            $result = $this->statement->execute($params);
        }

        $this->getLogger()->debug($this->_debugQuery());

        $time = microtime(true) - $start;
        $ar = (int) $this->statement->rowCount();
        $this->getLogger()->debug('Affected rows: ' . $ar . ' Query took: ' . round($time * 1000, 3) . ' ms');
        return $result;
    }

    public function bindValue($parameter, $value, $data_type = false)
    {
        $this->_debugValues[$parameter] = $value;
        return $this->statement->bindValue($parameter, $value, $data_type);
    }

    public function _debugQuery($replaced = true)
    {
        $q = $this->statement->queryString;

        if (!$replaced) {
            return $q;
        }

        return preg_replace_callback('/:([0-9a-z_]+)/i', array($this, '_debugReplace'), $q);
    }

    protected function _debugReplace($m)
    {
        $v = $this->_debugValues[$m[0]];

        if ($v === null) {
            return "NULL";
        }
        if (!is_numeric($v)) {
            $v = str_replace("'", "''", $v);
        }

        return "'" . $v . "'";
    }

    /**
     * Other than execute pass all other calls to the PDOStatement object
     * @param string $function_name
     * @param array $parameters arguments
     */
    public function __call($function_name, $parameters)
    {
        return call_user_func_array(array($this->statement, $function_name), $parameters);
    }
}

答案 16 :(得分:0)

如何在Ubuntu中调试PDO mysql数据库查询

TL; DR记录所有查询并尾随mysql日志。

这些说明适用于我安装的Ubuntu 14.04。发出命令lsb_release -a以获取您的版本。您的安装可能会有所不同。

打开mysql中的日志记录

  1. 转到您的dev服务器cmd行
  2. 更改目录cd /etc/mysql。您应该看到一个名为my.cnf的文件。那是我们要改变的文件。
  3. 点击cat my.cnf | grep general_log确认您在正确的位置。这会为您过滤my.cnf文件。您应该看到两个条目:#general_log_file = /var/log/mysql/mysql.log&amp;&amp; #general_log = 1
  4. 取消注释这两行并通过您选择的编辑保存。
  5. 重启mysql:sudo service mysql restart
  6. 您可能还需要重新启动网络服务器。 (我不记得我用过的顺序)。对于我的安装,那是nginx:sudo service nginx restart
  7. 干得好!你们都准备好了。现在您所要做的就是拖尾日志文件,以便您可以实时查看应用程序的PDO查询。

    关闭日志以查看您的查询

    输入此cmd tail -f /var/log/mysql/mysql.log

    您的输出将如下所示:

    73 Connect  xyz@localhost on your_db
    73 Query    SET NAMES utf8mb4
    74 Connect  xyz@localhost on your_db
    75 Connect  xyz@localhost on your_db
    74 Quit 
    75 Prepare  SELECT email FROM customer WHERE email=? LIMIT ?
    75 Execute  SELECT email FROM customer WHERE email='a@b.co' LIMIT 5
    75 Close stmt   
    75 Quit 
    73 Quit 
    

    只要您继续拖尾日志,您的应用所做的任何新查询都会自动进入视图。要退出尾部,请点击cmd/ctrl c

    备注

    1. 小心:这个日志文件可能变得很大。我只在我的开发服务器上运行它。
    2. 日志文件太大了?截断它。这意味着文件保留,但内容将被删除。 truncate --size 0 mysql.log
    3. 很酷,日志文件列出了mysql连接。我知道其中一个来自我正在转换的遗留mysqli代码。第三个来自我的新PDO连接。但是,不确定第二个来自哪里。如果您知道快速找到它的方法,请告诉我。
    4. 信用与优惠感谢

      Nathan Long’s answer above发出巨大的呼喊,要求在Ubuntu上解决这个问题。也是dikirill对Nathan的帖子的评论,它引导我找到这个解决方案。

      爱你stackoverflow!

答案 17 :(得分:0)

在Debian NGINX环境中,我执行了以下操作。

如果发现/etc/mysql/mysql.conf.d,请转到mysqld.cnf编辑log-error = /var/log/mysql/error.log,在其后添加以下两行。

general_log_file        = /var/log/mysql/mysql.log
general_log             = 1

要查看日志转到/var/log/mysqltail -f mysql.log

如果要在生产环境中进行调试,请记住将这些行注释掉,因为此日志文件将快速增长并且可能很大,因此请删除mysql.log