虽然这个问题相当古老,但有些主题并没有真正讨论,应该在这里概述,以供其他研究与 OP 相同的人使用。
总结以下所有内容:
- 是的,总是使用准备语句
- 是的,通过 mysqli 在 mysql 上使用 PDO。这样,如果您切换数据库系统,您只需更新查询而不是查询、函数调用和参数,因为它支持准备好的语句。
- 尽管使用带参数的准备好的语句,但始终清理用户提供的数据
- 研究 DBAL(数据库抽象层)以轻松处理所有这些因素并操纵查询以满足您的需求。
有一个主题 PDO::ATTR_EMULATE_PREPARES 将提高在 MySQL >= 5.1.21 中调用缓存查询的性能,当仿真关闭时,默认情况下是启用的。这意味着 PHP 将在执行之前模拟准备将其发送到实际数据库。模拟和非模拟之间的时间通常可以忽略不计,除非使用外部数据库(不是本地主机),例如在云上,它可能具有异常高的 ping 率。
缓存也取决于 my.cnf 中的 MySQL 设置,但 MySQL 优化超出了本文的范围。
<?php
$pdo = new \PDO($connection_string);
$pdo->setAttribute( \PDO::ATTR_EMULATE_PREPARES, false );
?>
所以请记住这一点,因为 mysqli_ 不提供用于客户端仿真的 API,并且总是使用 MySQL 来准备语句。
http://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php
尽管具有相似的功能,但还是存在差异,您可能需要一个 API 提供而另一个 API 不提供的功能。请参阅 PHP 关于选择一种 API 的参考:http ://www.php.net/manual/en/mysqlinfo.api.choosing.php
因此,这几乎与您在应用程序范围内定义语句的要求一致,因为可缓存查询将被缓存在 MySQL 服务器上,并且不需要在应用程序范围内进行准备。另一个好处是您的查询中的异常将在prepare() 而不是execute() 中抛出,这有助于开发以确保您的查询是正确的。
不管是否使用准备都没有现实世界的性能优势。
如果您使用 InnoDB for MySQL,准备好的语句的另一个好处是使用事务。您可以启动一个事务、插入一条记录、获取最后一个插入 id、更新另一个表、从另一个表中删除,如果在此过程中出现任何故障,您可以 rollBack() 到事务发生之前。否则,如果您选择提交更改。例如处理一个新订单并将用户的最后一个订单列设置为新订单 ID,并删除一个挂单,但提供的付款类型不符合从 order_flags 表下订单的条件,因此您可以 rollBack()并向用户显示友好的错误消息。
至于安全性,我很困惑没有人提到这一点。将任何用户提供的数据发送到包括 PHP 和 MySQL 在内的任何系统时,对其进行清理和标准化。是的,准备好的语句在转义数据时确实提供了一些安全性,但它不是 100% 的防弹。
因此,始终使用准备好的语句比没有真正的性能损失要好得多,并且缓存有一些好处,但您仍然应该清理用户提供的数据。第一步是将变量类型转换为您正在使用的所需数据类型。使用对象将进一步简化这一点,因为您在数据类型的单个模型中工作,而不是每次使用相同数据时都必须记住它。
要添加到上述内容,您应该查看使用 PDO 的数据库抽象层。例如 Doctrine DBAL:http ://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html
使用 DBAL+PDO 的额外好处是
- 您可以标准化并缩短您必须完成的工作量。
- 帮助清理用户提供的数据
- 轻松操作复杂的查询
- 使用嵌套事务
- 在数据库之间轻松切换
- 您的代码在其他项目中变得更加可移植和可用
例如,我扩展了 PDO 并覆盖了 query()、fetchAll() 和 fetch() 方法,以便它们始终使用准备好的语句,这样我就可以在 fetch() 或 fetchAll() 中编写 SQL 语句,而不必编写一切都出来了。例如:
<?php
$pdo = new PDOEnhanced( $connection );
$pdo->fetchAll( "SELECT * FROM foo WHERE bar = 'hi'", PDO::FETCH_OBJ );
//would automatically provide
$stmt = $pdo->prepare( "SELECT * FROM foo WHERE bar=?" );
$stmt->execute( array( 'hi' ) );
$resultSet = $stmt->fetchAll( PDO::FETCH_OBJ )
?>
至于建议 mysql_* 风格的人,用 mysqli_* API 替换要容易得多。事实并非如此。大部分 mysql_* 函数被遗漏或与 mysqli_* 有参数更改参见:http ://php.net/manual/en/mysqli.summary.php
但是,您可以获得 Oracle 发布的转换器以简化流程:https ://wikis.oracle.com/display/mysql/Converting+to+MySQLi
请记住,它是一个文件源文本解析器,并非 100% 准确,因此请在合并之前验证更改。它还将为其创建的全局变量增加大量开销。