虽然这个问题相当古老,但有些主题并未真正讨论过,应在此处概述,以供其他研究与 OP 相同的人使用。
总结如下:
- 是的,总是使用准备语句
- 是的,使用 PDO 而不是 mysqli 而不是 mysql。 如果你切换数据库系统,你需要做的就是更新查询而不是查询、函数调用和参数,因为它支持准备好的语句。
- 始终清理用户提供的数据,尽管使用带参数的预准备语句
- 查看 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() 中引发,这有助于开发以确保您的查询是正确的。
无论是否使用 prepare 对实际性能没有任何好处。
如果您使用 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% 准确,因此请在合并之前验证更改。它还将为其创建的全局变量增加大量开销。