【问题标题】:Can prepared statement handles be stored in member variables?准备好的语句句柄可以存储在成员变量中吗?
【发布时间】:2014-11-20 01:03:56
【问题描述】:

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

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

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


I.E.像这样:

Class MyClass {

    private stmt1;

    function __construct($dbh) {
        $this->stmt1 = $dbh->prepare('SELECT foo FROM bar WHERE foobar = :foobar');
    }

    private function doFoo() {
        $this->stmt1->execute(...)
        ...
    }
}

【问题讨论】:

  • $stmt1 = $dbh->prepare() 不应该是$this->stmt1 = $dbh->prepare() 吗?可能是准备好的语句不可用的原因。
  • @tradyblix 谢谢,错字!
  • 啊,我以为就是这样!你有没有尝试添加一些try-catch 看看它说了什么?这个想法应该可行。只要处理程序存在并且函数能够访问保存处理程序的成员变量。
  • @tradyblix 我刚刚发布了该代码作为示例。我不是在寻求帮助以使其正常工作,而是想弄清楚我的建议是否出于某种原因是一种不好的做法(我以前从未见过这样做过)。
  • 当然您可以将stmt 存储在一个类中,坏事可能是您将它们初始化在__construct 中,并且在实例生命周期内您可能不需要stmt。所以对 stmts 使用延迟加载。

标签: php mysql pdo


【解决方案1】:

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

每当我看到赏金问题时,我都会问自己:“他们甚至解决了正确的问题吗?”在这个对象的生命周期内用不同的参数执行相同的查询数千次真的是最好的方法吗?

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

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

  1. 衡量当前性能。
  2. 关闭模拟准备。
  3. 衡量性能影响。
  4. 使用称为memoizationlazy loading 的技术来缓存准备,但仅在实际使用时才准备查询。
  5. 再次衡量性能影响。

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

【讨论】:

  • 查询几乎都是INSERTs,但由于脚本的性质,我认为我不能使用批量插入。衡量性能并比较不同的方法是个好主意,谢谢。
【解决方案2】:

将语句存储为变量在纸上有效。但请注意性能。

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

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

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

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

另一个需要注意的是,由于涉及的变量,真正的预准备语句的查询计划可能是最好的,也可能不是。假设 foo (bar) 上有一个 b-tree 索引:

select bar from foo order by bar limit ?

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

最后,顺便说一句,如果您还不熟悉 ORM,您可能想研究一下 ORM。

【讨论】:

    【解决方案3】:

    从技术上讲这是可能的,正如您已经知道的那样,您只需尝试或只是 reading

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

    我认为在构造函数中准备所有语句是个坏主意。我想如果你在没有任何上下文的构造函数中有一堆 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 上下文位于正确的位置。

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

    【讨论】:

    • 这很有趣。这种技术被广泛使用吗?我尝试搜索“pdo statement map”(和类似术语),但找不到任何东西。
    • 我不知道它的使用分布。但是从this question 的答案中可以看出,人们确实使用它。就我个人而言,只要我没有发现创建新的准备好的语句存在问题,我就会将其称为过早的优化。
    • @Nate:不,它没有被广泛使用。人们使用 ORM。
    • @Nate:这种技术称为memoization,是的,它是一种广泛使用的方法,用于及时缓存昂贵的操作。
    • 我在这里没有看到记忆。我看到lazy loading
    猜你喜欢
    • 2014-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多