准备好的语句句柄可以存储在成员变量中吗?


Can prepared statement handles be stored in member variables?

我有一个PHP类来处理数据并将其存储在MySQL数据库中。出于安全原因,我在保存数据时使用预准备语句,但由于类很大,这些预准备语句是在对象生命周期内(从一分钟到三十分钟)调用数千次的不同函数中创建的。

我想知道的是,是否有任何原因我无法在类构造函数中准备语句并将句柄保存在成员变量中以避免多次准备语句。

有什么理由不行吗?我不明白为什么不这样做,但我以前从未见过这样做,这让我想知道这样做是否出于某种原因是一种不好的做法。


即像这样:

Class MyClass {
    private stmt1;
    function __construct($dbh) {
        $this->stmt1 = $dbh->prepare('SELECT foo FROM bar WHERE foobar = :foobar');
    }
    private function doFoo() {
        $this->stmt1->execute(...)
        ...
    }
}
出于

安全原因,我在保存数据时使用预准备语句,但由于类很大,这些预准备语句是在对象生命周期内(从一分钟到三十分钟)调用数千次的不同函数中创建的。

每当我看到赏金问题时,我总是问自己,"他们甚至解决了正确的问题吗?在此对象的生存期内,使用不同参数执行数千次相同的查询真的是最好的方法吗?

  • 如果您正在执行多个SELECT,那么一次获取更多信息的更好查询可能会更好。
  • 如果您正在执行多个INSERT,那么批量插入可能会更好地为您服务。

如果在评估上述选项后,您决定在对象的生命周期中仍然需要调用这些语句数千次,那么是的,您可以缓存预准备语句的结果:

  1. 衡量当前性能。
  2. 关闭模拟准备。
  3. 衡量性能影响。
  4. 使用
  5. 一种称为记忆或延迟加载的技术来缓存准备,但仅在实际使用时才准备查询。
  6. 再次衡量性能影响。

这使您可以查看更改的每个部分的影响。我怀疑,如果您真的调用这些查询数千次,那么这些更改中的部分或全部将对您有所帮助,但您必须在测量之前和之后进行测量才能知道。

将语句存储为变量可以在纸上工作。不过要警惕性能。

特别是,真实准备(MySQL默认关闭)或模拟准备(MySQL默认使用PDO::ATTR_EMULATE_PREPARES)之间存在天壤之别。

模拟的预准备语句将在本地分析查询。执行后,它们将用参数的值替换参数,并将最终的 SQL 字符串传送到客户端。收到它后,数据库将解析查询,提出查询计划,执行它并返回行。

真正的预准备语句会将要准备的查询直接传送到数据库。后者将解析它,根据查询和未知变量准备一个通用查询计划,并返回一个准备好的语句供 PHP 使用。当 PDO 执行语句时,它会将准备好的语句与参数一起发回。然后,数据库执行准备好的查询计划并返回行。

您可能已经注意到,一个真正的准备语句涉及 PHP 和 DB 之间的大量来回。这被查询是一劳永逸地计划的这一事实所抵消。有时这是可取的(类似的查询被多次使用);有时不会(查询只使用一次)。

进一步需要注意的是,由于所涉及的变量,真实准备语句的查询计划可能是也可能不是最好的。假设 foo 上的 b 树索引(柱):

select bar from foo order by bar limit ?
如果变量很小,则需要索引扫描;如果

变量较大,则位图索引扫描(如果可用)是有意义的;如果变量很大,则需要 seq 扫描。在后两种情况下,计划人员还需要选择一种排序方法。但是,由于查询规划器的任务是提出一个计划,墨菲定律指出,它偶尔会为您的特定用例选择最糟糕的计划。接下来你知道,你最终会扫描整个表的排序以检索几行,或者按照栏上的索引检索整个表。

最后,顺便说一句

,如果您还不熟悉ORM,则可能需要查看ORM。

从技术上讲,这是可能的,正如您通过简单地尝试或只是阅读已经知道的那样:

查询 [...] 可以多次执行。

我认为准备构造函数中的所有语句是一个坏主意。我想如果您在没有任何上下文的构造函数中有一堆 SQL 语句,它将变得无法维护。此外,您可能会准备比实际需要的更多。

克服这个问题的一个想法是使用语句映射:

private $statments = array();
public function getStatement($sql)
{
    if (! isset($this->statements[$sql])) {
        $this->statements[$sql] = $this->pdo->prepare($sql);
    }
    return $this->statements[$sql];
}

这将只准备一次语句,并且您将 SQL 上下文放在正确的位置。

但我认为这是过早的优化,因为您的数据库的查询缓存很可能为您执行此操作。