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

时间:2014-11-20 01:03:57

标签: php mysql pdo

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

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

这有什么理由不起作用吗?我不明白为什么不这样做,但我以前从未见过它,这让我想知道出于某种原因这样做是不是很糟糕。


即。像这样的东西:

Class MyClass {

    private stmt1;

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

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

3 个答案:

答案 0 :(得分:8)

  

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

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

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

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

  1. 衡量当前表现。
  2. 关闭模拟准备。
  3. 衡量效果影响。
  4. 使用名为memoizationlazy loading的技术来缓存准备工作,但只在实际使用时准备查询。
  5. 再次衡量效果影响。
  6. 这可以让您看到您更改的每件作品的影响。我怀疑,如果你真的称呼这些查询数千次,那么这些变化中的部分或全部将对你有所帮助,但你必须在测量之前和之后进行测量才能知道。

答案 1 :(得分:2)

将语句存储为变量可以在纸上进行。但是要警惕性能。

特别是,真实准备(默认情况下为MySQL)或模拟准备(MySQL的默认设置,使用PDO::ATTR_EMULATE_PREPARES)之间存在差异。

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

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

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

另一个警告是,由于涉及的变量,真正准备好的陈述的查询计划可能是也可能不是最好的。假设foo(bar)上的b树索引:

select bar from foo order by bar limit ?

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

最后,如果您不熟悉它们,您可能想要查看ORM。

答案 2 :(得分:1)

从技术上讲,您可以通过简单地尝试或只是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'查询缓存很可能是为你做的。