从 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 准备语句中检索(或模拟)完整查询的主要内容,如果未能解决你的问题,请参考以下文章