mysql> create table users(
-> id int(2) primary key auto_increment,
-> userid tinytext,
-> pass tinytext);
Query OK, 0 rows affected (0.05 sec)
mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)
mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)
mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)
完成后,我们可以转到我们的PHP代码 .
让我们假设以下脚本是网站管理员的验证过程(简化但是如果您复制并使用它进行测试则有效):
<?php
if(!empty($_POST['user']))
{
$user=$_POST['user'];
}
else
{
$user='bob';
}
if(!empty($_POST['pass']))
{
$pass=$_POST['pass'];
}
else
{
$pass='bob';
}
$database='prep';
$link=mysql_connect('localhost', 'prepared', 'example');
mysql_select_db($database) or die( "Unable to select database");
$sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
//echo $sql."<br><br>";
$result=mysql_query($sql);
$isAdmin=false;
while ($row = mysql_fetch_assoc($result)) {
echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
$isAdmin=true;
// We have correctly matched the Username and Password
// Lets give this person full access
}
if($isAdmin)
{
echo "The check passed. We have a verified admin!<br>";
}
else
{
echo "You could not be verified. Please try again...<br>";
}
mysql_close($link);
?>
<form name="exploited" method='post'>
User: <input type='text' name='user'><br>
Pass: <input type='text' name='pass'><br>
<input type='submit'>
</form>
现在,您有更好的选择使用mysqli_或PDO . 我个人是PDO的忠实粉丝,所以我将在其余的答案中使用PDO . 有亲's and con',但我个人觉得亲's far outweigh the con' . 它可以在多个数据库引擎中移植 - 无论您使用的是MySQL还是Oracle,或者只是通过更改连接字符串,只需更改连接字符串,它具有我们想要使用的所有奇特功能,而且非常干净 . 我喜欢干净 .
现在,让我们再看看那段代码,这次使用PDO对象编写:
<?php
if(!empty($_POST['user']))
{
$user=$_POST['user'];
}
else
{
$user='bob';
}
if(!empty($_POST['pass']))
{
$pass=$_POST['pass'];
}
else
{
$pass='bob';
}
$isAdmin=false;
$database='prep';
$pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
$sql="select id, userid, pass from users where userid=:user and pass=:password";
$myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
{
while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
{
echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
$isAdmin=true;
// We have correctly matched the Username and Password
// Lets give this person full access
}
}
if($isAdmin)
{
echo "The check passed. We have a verified admin!<br>";
}
else
{
echo "You could not be verified. Please try again...<br>";
}
?>
<form name="exploited" method='post'>
User: <input type='text' name='user'><br>
Pass: <input type='text' name='pass'><br>
<input type='submit'>
</form>
主要区别在于没有更多 mysql_* 功能 . 它是's all done via a PDO object, secondly, it is using a prepared statement. Now, what'你要问的准备好的声明吗?这是一种在运行查询之前告诉数据库的方法,我们将要运行的查询是什么 . 在这种情况下,我们告诉数据库:"Hi, I am going to run a select statement wanting id, userid and pass from the table users where the userid is a variable and the pass is also a variable." .
include_once("pdo_mysql.php");
pdo_connect("localhost", "usrABC", "pw1234567");
pdo_select_db("test");
$result = pdo_query("SELECT title, html FROM pages");
while ($row = pdo_fetch_assoc($result)) {
print "$row[title] - $row[html]";
}
Etvoilà . 您的代码正在使用PDO . 现在是时候实际利用它了 .
绑定参数可以很容易使用
你只需要一个不那么笨拙的API .
pdo_query() 为绑定参数添加了非常方便的支持 . 转换旧代码很简单:
将变量移出SQL字符串 .
将它们作为逗号分隔的函数参数添加到 pdo_query() .
将问号 ? 放置为变量所在的占位符 .
摆脱之前包含的字符串值/变量的 ' 单引号 .
对于更长的代码,优势变得更加明显 .
通常,字符串变量不仅仅插入到SQL中,而是与之间的转义调用连接起来 .
pdo_query("SELECT id, links, html, title, user, date FROM articles
WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
pdo_real_escape_string($title) . "' AND user <> '" .
pdo_real_escape_string($root) . "' ORDER BY date")
应用 ? 占位符后,您无需为此烦恼:
pdo_query("SELECT id, links, html, title, user, date FROM articles
WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)
try {
//Connect as appropriate as above
$db->query('hi'); //Invalid query!
}
catch (PDOException $ex) {
echo "An Error occured!"; //User friendly message/message you want to show to user
some_logging_function($ex->getMessage());
}
function data_fun($db) {
$stmt = $db->query("SELECT * FROM table");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
//Then later
try {
data_fun($db);
}
catch(PDOException $ex) {
//Here you can handle error and show message/perform action you want.
}
此外,你可以通过 or die() 来处理,或者我们可以说像 mysql_* ,但它会变化多端 . 您可以通过转动 display_errors off 并只读取错误日志来隐藏 生产环境 中的危险错误消息 .
class person {
public $name;
public $add;
function __construct($a,$b) {
$this->name = $a;
$this->add = $b;
}
}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));
因此,如果您是普通的PHP用户并希望在使用本机预处理语句时节省大量的麻烦,那么再次使用PDO是唯一的选择 . 然而,PDO也不是银弹,并且有其艰辛 . 所以,我为PDO tag wiki中的所有常见陷阱和复杂案例编写了解决方案 .
然而,每个人都在谈论扩展总是错过关于Mysqli和PDO的 2 important facts :
准备好的声明 isn't a silver bullet . 有动态标识符不能使用预准备语句绑定 . 存在具有未知数量的参数的动态查询,这使得查询构建成为困难的任务 .
Neither mysqli_ nor PDO functions should have appeared in the application code.* 它们和应用程序代码之间应该有一个 abstraction layer ,它将完成内部绑定,循环,错误处理等所有脏工作,使应用程序代码干燥和清理 . 特别是对于像动态查询构建这样的复杂情况 .
所以,仅仅切换到PDO或mysqli是不够的 . 必须使用ORM,查询构建器或任何数据库抽象类,而不是在其代码中调用原始API函数 . 相反 - 如果你的应用程序代码和mysql API之间有一个抽象层 - it doesn't actually matter which engine is used. 你可以使用mysql ext直到它被弃用,然后轻松地将你的抽象类重写为另一个引擎, having all the application code intact.
14 回答
可以使用mysqli或PDO定义几乎所有
mysql_*
函数 . 只需将它们包含在旧的PHP应用程序之上,它就可以在PHP7上运行 . 我的解决方案here .mysql_* 函数已被弃用(截至 PHP 5.5 ),因为开发了更好的函数和代码结构 . 该功能被弃用的事实意味着在性能和安全性方面不再需要付出努力来改进它, which means it is less future proof .
如果您需要更多理由:
mysql_* 函数不支持预准备语句 .
mysql_* 函数不支持参数绑定 .
mysql_* 函数缺少面向对象编程的功能 .
列表继续......
原因很多,但也许最重要的原因是这些功能鼓励不安全的编程实践,因为它们不支持预处理语句 . 准备好的语句有助于防止SQL注入攻击 .
使用
mysql_*
函数时,必须记住通过mysql_real_escape_string()
运行用户提供的参数 . 如果您只是在一个地方忘记或者您碰巧只是逃避了部分输入,那么您的数据库可能会受到攻击 .在
PDO
或mysqli
中使用预准备语句会使这些编程错误更难以制作 .与
mysql_connect()
,mysql_query()
类似的函数是PHP的先前版本(PHP 4)函数,现在尚未使用 .在最新的PHP5中,它们被
mysqli_connect()
,mysqli_query()
取代 .这就是错误背后的原因 .
这个答案是为了说明绕过写得不好的PHP用户验证代码,如何(和使用什么)这些攻击工作以及如何用安全的预处理语句替换旧的MySQL函数是多么微不足道 - 基本上,为什么StackOverflow用户(可能有很多代表)正在咆哮新用户提出问题以改进他们的代码 .
首先,请随意创建这个测试mysql数据库(我已经打过我的准备):
完成后,我们可以转到我们的PHP代码 .
让我们假设以下脚本是网站管理员的验证过程(简化但是如果您复制并使用它进行测试则有效):
乍一看似乎足够合法 .
用户必须输入登录名和密码,对吧?
很棒,不要输入以下内容:
并提交 .
输出如下:
超!按预期工作,现在让我们尝试实际的用户名和密码:
惊人!全面的Hi-fives,代码正确验证了管理员 . 这是完美的!
嗯,不是真的 . 让我们说用户是一个聪明的小人物 . 让我们说这个人就是我 .
输入以下内容:
输出是:
恭喜,您刚刚允许我输入您的超级保护管理员部分,我输入了错误的用户名和虚假密码 . 说真的,如果你不相信我,用我提供的代码创建数据库,并运行这个PHP代码 - 一眼看上去似乎确实很好地验证了用户名和密码 .
所以,作为回答,那就是为什么你会被骂 .
所以,让我们来看看出了什么问题,以及为什么我刚刚进入你的超级管理员蝙蝠洞 . 我猜了一下,并假设你没有小心你的输入,只是直接将它们传递给数据库 . 我以一种改变您实际运行的查询的方式构造输入 . 那么,它应该是什么样的,最终是什么呢?
这是查询,但是当我们用我们使用的实际输入替换变量时,我们得到以下结果:
看看我如何构建我的“密码”,以便它首先关闭密码周围的单引号,然后引入一个全新的比较?然后为了安全起见,我添加了另一个“字符串”,以便单引号在我们原来的代码中按预期关闭 .
但是,这不是关于人们现在大吼大叫,这是关于向您展示如何使您的代码更安全 .
好的,那么出了什么问题,我们该如何解决呢?
这是一种典型的SQL注入攻击 . 最简单的事情之一 . 在攻击向量的范围内,这是一个蹒跚学步的攻击坦克 - 并获胜 .
那么,我们如何保护您的神圣管理部分并使其变得美观和安全?要做的第一件事就是停止使用那些非常旧的和已弃用的
mysql_*
函数 . 我知道,你按照你在网上找到的教程进行了工作,但它已经过时了,而且在几分钟的时间里,我刚刚打破过它而没有那么多汗流浃背 .现在,您有更好的选择使用mysqli_或PDO . 我个人是PDO的忠实粉丝,所以我将在其余的答案中使用PDO . 有亲's and con',但我个人觉得亲's far outweigh the con' . 它可以在多个数据库引擎中移植 - 无论您使用的是MySQL还是Oracle,或者只是通过更改连接字符串,只需更改连接字符串,它具有我们想要使用的所有奇特功能,而且非常干净 . 我喜欢干净 .
现在,让我们再看看那段代码,这次使用PDO对象编写:
主要区别在于没有更多
mysql_*
功能 . 它是's all done via a PDO object, secondly, it is using a prepared statement. Now, what'你要问的准备好的声明吗?这是一种在运行查询之前告诉数据库的方法,我们将要运行的查询是什么 . 在这种情况下,我们告诉数据库:"Hi, I am going to run a select statement wanting id, userid and pass from the table users where the userid is a variable and the pass is also a variable." .然后,在execute语句中,我们向数据库传递一个包含它现在所需的所有变量的数组 .
结果太棒了 . 让我们再次尝试那些用户名和密码组合:
用户未经过验证 . 真棒 .
怎么样:
哦,我有点兴奋,它有效:检查通过了 . 我们有经过验证的管理员!
现在,让我们尝试一下聪明的小伙子会输入的数据,试图通过我们的小验证系统:
这一次,我们得到以下内容:
这就是为什么你在发布问题时被大吼大叫的原因 - 这是因为人们可以看到你的代码可以绕过而不用尝试 . 请使用此问题和答案来改进您的代码,使其更安全并使用当前的功能 .
最后,这并不是说这是完美的代码 . 你还可以做很多事情来改进它,例如使用散列密码,确保当你在数据库中存储感知信息时,你不会以纯文本形式存储它,有多级验证 - 但实际上,如果你只需要改变你的旧注入代码就可以了,在编写优秀代码的过程中你会很好 - 而且你已经掌握了这一点而且还在阅读的事实让我有一种希望,你不仅会实现这种类型在编写您的网站和应用程序时的代码,但您可能会出去研究我刚才提到的其他事情 - 以及更多 . 编写尽可能最好的代码,而不是几乎不起作用的最基本代码 .
我觉得上面的答案真的很冗长,总结一下:
资料来源:MySQLi overview
正如上面的答案所解释的,mysql的替代品是mysqli和PDO(PHP数据对象) .
API支持服务器端准备语句:由MYSQLi和PDO支持
API支持客户端准备语句:仅受PDO支持
API支持存储过程:MySQLi和PDO
API支持多语句和所有MySQL 4.1功能 - 由MySQLi支持,主要由PDO支持
MySQLi和PDO都是在PHP 5.0中引入的,而MySQL则是在PHP 3.0之前引入的 . 值得注意的是,MySQL已包含在PHP5.x中,但在以后的版本中已弃用 .
MySQL扩展:
未进行积极开发
自PHP 5 . 5(2013年6月发布)起 officially deprecated .
自PHP 7.0起已于 removed entirely (2015年12月发布)
这意味着从31 Dec 2018开始,它将不存在于任何受支持的PHP版本中 . 目前,它只获得安全更新 .
缺少OO接口
不支持:
非阻塞,异步查询
Prepared statements or parameterized queries
存储过程
多个声明
交易
"new"密码验证方法(MySQL 5.6中默认启用; 5.7中要求)
MySQL 5.1中的所有功能
由于它已被弃用,因此使用它会使您的代码不再适用于未来 .
缺乏对预准备语句的支持尤其重要,因为它们提供了一种更清晰,更不易出错的转义和引用外部数据的方法,而不是通过单独的函数调用手动转义它 .
见the comparison of SQL extensions .
MySQL扩展是三者中最老的,是开发人员用来与MySQL通信的原始方式 . 由于PHP和MySQL的新版本中的改进,此扩展现在正在deprecated支持其他two alternatives .
MySQLi是用于处理MySQL数据库的'improved'扩展 . 它利用了较新版本的MySQL服务器中提供的功能,向开发人员公开了面向功能的界面和面向对象的界面,并且做了一些其他很好的事情 .
PDO提供了一个API,可以整合之前传播的大部分功能主要数据库访问扩展,即MySQL,PostgreSQL,SQLite,MSSQL等 . 该接口公开高级对象,程序员使用数据库连接,查询和结果集,低级驱动程序与数据库执行通信和资源处理服务器 . 许多讨论和工作正在进入PDO,它被认为是在现代专业代码中使用数据库的适当方法 .
首先,让我们从我们给大家的标准评论开始:
让我们逐句逐句解释:
这意味着PHP社区正在逐渐放弃对这些非常旧的功能的支持 . 它们很可能不存在于PHP的未来(最新)版本中!继续使用这些函数可能会破坏(不是那么)远期的代码 .
NEW! - ext/mysql is now officially deprecated as of PHP 5.5!
更新!在PHP 7中删除了ext / mysql .
mysql_*
扩展名不支持 prepared statements ,这是(除其他事项外)对 SQL Injection 的一个非常有效的对策 . 它修复了MySQL依赖应用程序中的一个非常严重的漏洞,它允许攻击者访问您的脚本并在您的数据库上执行 any possible query .有关更多信息,请参阅 How can I prevent SQL injection in PHP?
当您转到任何
mysql
功能手册页时,您会看到一个红色框,说明它不应再使用了 .有更好,更强大和精心构建的替代方案 PDO - PHP Database Object ,它提供了完整的OOP方法来进行数据库交互,而 MySQLi 则是MySQL特定的改进 .
易于使用
已经提到了分析和综合原因 . 对于新手来说,停止使用过时的mysql_函数是一个更重要的动机 .
Contemporary database APIs are just easier to use.
它主要是可以简化代码的绑定参数 . 而excellent tutorials (as seen above)过渡到PDO并不过分艰巨 .
一次重写更大的代码库需要时间 . Raison d'être为这个中间选择:
等效的pdo_ *函数代替mysql_ *
使用<pdo_mysql.php>,您可以轻松地从旧的mysql_函数切换 . 它添加了
pdo_
函数包装器来替换它们的mysql_
对应物 .只需
include_once(
"pdo_mysql.php");
在每个必须与数据库交互的调用脚本中 .在任何地方删除mysql_函数前缀并将其替换为 pdo_ .
mysql_
connect()
成为 pdo_connect()
mysql_
query()
成为 pdo_query()
mysql_
num_rows()
成为 pdo_num_rows()
mysql_
insert_id()
成为 pdo_insert_id()
mysql_
fetch_array()
成为 pdo_fetch_array()
mysql_
fetch_assoc()
成为 pdo_fetch_assoc()
mysql_
real_escape_string()
成为 pdo_real_escape_string()
等等......
您的代码将起作用,但大多数看起来仍然相同:
Etvoilà .
您的代码正在使用PDO .
现在是时候实际利用它了 .
绑定参数可以很容易使用
你只需要一个不那么笨拙的API .
pdo_query()
为绑定参数添加了非常方便的支持 . 转换旧代码很简单:将变量移出SQL字符串 .
将它们作为逗号分隔的函数参数添加到
pdo_query()
.将问号
?
放置为变量所在的占位符 .摆脱之前包含的字符串值/变量的
'
单引号 .对于更长的代码,优势变得更加明显 .
通常,字符串变量不仅仅插入到SQL中,而是与之间的转义调用连接起来 .
应用
?
占位符后,您无需为此烦恼:请记住,pdo_ *仍然允许或者 .
只是不要转义变量并将其绑定在同一个查询中 .
占位符功能由其后面的真实PDO提供 .
因此也允许
:named
占位符列表 .更重要的是,您可以在任何查询后安全地传递$ _REQUEST []变量 . 当提交
<form>
字段与数据库结构完全匹配时,它甚至更短:这么简单 . 但是,让我们回到一些更多的重写建议和技术原因,为什么你可能想要摆脱mysql_和逃避 .
修复或删除任何oldschool sanitize()函数
将所有mysql_调用转换为带有绑定参数的
pdo_query
后,删除所有多余的pdo_real_escape_string
调用 .特别是你应该修复任何
sanitize
或clean
或filterThis
或clean_data
函数,这些函数是由日期教程以一种形式或另一种形式公布的:这里最明显的错误是缺乏文档 . 更重要的是,过滤顺序完全错误 .
正确的顺序本来应该是:弃用
stripslashes
作为最里面的调用,然后是trim
,之后是strip_tags
,htmlentities
用于输出上下文,并且最后_escape_string
作为其应用程序应该直接在SQL间距之前 .但是第一步只是 get rid of the _real_escape_string 来电 .
如果您的数据库和应用程序流期望HTML上下文安全的字符串,您可能必须暂时保留
sanitize()
函数的其余部分 . 添加一条评论,它仅适用于今后的HTML转义 .字符串/值处理委托给PDO及其参数化语句 .
如果在您的清理功能中有任何
stripslashes()
的提及,则可能表示更高级别的疏忽 .这通常用于从已弃用的magic_quotes撤消损坏(双重转义) . 然而,这是best fixed centrally,而不是逐字符串 .
使用userland reversal方法之一 . 然后删除
sanitize
函数中的stripslashes()
.准备好的陈述有何不同
当您将字符串变量加密到SQL查询中时,它不会让您更加复杂 . MySQL再次分离代码和数据也是一项无关紧要的工作 .
SQL注入只是在数据流入代码上下文时 . 数据库服务器以后不能发现PHP最初在查询子句之间粘合变量的位置 .
使用绑定参数,可以在PHP代码中分隔SQL代码和SQL上下文值 . 但它不会在幕后再次混乱(除了PDO :: EMULATE_PREPARES) . 您的数据库接收未变量的SQL命令和1:1变量值 .
虽然这个答案强调你应该关心删除mysql_的可读性优势 . 由于这种可见和技术数据/代码分离,偶尔也会有性能优势(重复INSERT只有不同的值) .
请注意,参数绑定仍然不是针对所有SQL注入的神奇的一站式解决方案 . 它处理数据/值的最常见用途 . 但是不能将列名称/表标识符列入白名单,有助于动态子句构造,或者只是简单的数组值列表 .
混合PDO使用
这些
pdo_*
包装函数构成了一个编码友好的止差API . (如果它不是特殊功能签名转换的话,那几乎就是MYSQLI
) . 它们在大多数时候也暴露了真正的PDO .重写不必停止使用新的pdo_函数名称 . 您可以逐个将每个pdo_query()转换为普通的$ pdo-> prepare() - > execute()调用 .
不过,最好再次开始简化 . 例如,获取的常见结果:
可以只用foreach迭代替换:
或者更好的是直接和完整的数组检索:
在大多数情况下,您将获得比PDO或mysql_通常在查询失败后提供的更有用的警告 .
其他选项
所以这有希望可视化一些实际的原因和一个值得去除mysql_的途径 .
只是切换到pdo并没有完全削减它 .
pdo_query()
也只是它的前端 .除非你还引入参数绑定或者可以利用更好的API中的其他内容,否则它是一个毫无意义的开关 . 我希望它的描述足够简单,不会进一步阻碍新人的沮丧 . (教育通常比禁止更好 . )
虽然它有资格获得最简单的可能工作类别,但它却是众多的替代品 . 只需谷歌PHP database abstraction并浏览一下 . 对于这样的任务,总会有很多优秀的库 .
如果您想进一步简化数据库交互,像Paris/Idiorm这样的映射器值得一试 . 就像没有人再使用JavaScript中的平淡DOM一样,你现在不必保持原始的数据库接口 .
mysql_
函数:已过期 - 它们不再维护
不允许您轻松移动到另一个数据库后端
因此,
不支持预备语句
鼓励程序员使用连接来构建查询,从而导致SQL注入漏洞
PHP提供了三种不同的API来连接MySQL . 这些是mysql(从PHP 7中删除),mysqli和PDO扩展名 .
mysql_*
函数曾经很受欢迎,但它们的使用并非如此鼓励了 . 文档小组正在讨论数据库安全情况,并教育用户远离常用的ext / mysql扩展是其中的一部分(检查php.internals: deprecating ext/mysql) .当用户连接到MySQL时,后来的PHP开发团队已经决定生成E_DEPRECATED错误,无论是通过
mysql_connect()
,mysql_pconnect()
还是内置于ext/mysql
的隐式连接功能 .ext/mysql 是officially deprecated as of PHP 5.5并且一直是removed as of PHP 7 .
See the Red Box?
当你进入任何
mysql_*
功能手册页时,你会看到一个红色框,说明它不应再被使用了 .为什么
远离
ext/mysql
不仅涉及安全性,还涉及访问MySQL数据库的所有功能 .ext/mysql
是为 MySQL 3.23 构建的,从那时起只添加了很少的内容,同时主要保持与旧版本的兼容性,这使得代码更难维护 . 缺少ext/mysql
不支持的功能包括:(from PHP manual) .Stored procedures(无法处理多个结果集)
Prepared statements
加密(SSL)
压缩
完整的Charset支持
Reason to not use mysql_ function* :
未进行积极开发
从PHP 7开始删除
缺少OO接口
不支持非阻塞的异步查询
不支持预准备语句或parameterized queries
不支持存储过程
不支持多个语句
不支持transactions
不支持MySQL 5.1中的所有功能
Above point quoted from Quentin's answer
缺乏对预准备语句的支持特别重要,因为它们提供了一种更清晰,更不容易出错的方法来转义和引用外部数据,而不是通过单独的函数调用来手动转义它 .
见comparison of SQL extensions .
Suppressing deprecation warnings
当代码转换为
MySQLi
/PDO
时,可以通过在 php.ini 中设置error_reporting
来排除E_DEPRECATED
错误以排除E_DEPRECATED:
请注意,这也会隐藏 other deprecation warnings ,但是,这可能是MySQL以外的东西 . (from PHP manual)
Dejan Marjanovic的文章PDO vs. MySQLi: Which Should You Use?将帮助您选择 .
更好的方法是
PDO
,我现在正在编写一个简单的PDO
教程 .一个简单而简短的PDO教程
问:我脑海中的第一个问题是:什么是“PDO”?
答:“ PDO – PHP Data Objects - 是一个数据库访问层,提供访问多个数据库的统一方法 . ”
连接MySQL
使用
mysql_*
函数或我们可以用旧方式说(在PHP 5.5及以上版本中弃用)使用
PDO
:您需要做的就是创建一个新的PDO
对象 . 构造函数接受用于指定数据库源的参数PDO
的构造函数主要采用四个参数,即DSN
(数据源名称)和可选的username
,password
.在这里,我认为你熟悉除了
DSN
之外的所有人;这是PDO
中的新内容 .DSN
基本上是一串选项,告诉PDO
使用哪个驱动程序,以及连接详细信息 . 如需进一步参考,请查看PDO MySQL DSN .Note: 你也可以使用
charset=UTF-8
,但有时它会导致错误,所以最好使用utf8
.如果有任何连接错误,它将抛出一个
PDOException
对象,可以捕获该对象以进一步处理Exception
.Good read :Connections and Connection management ¶
您还可以将多个驱动程序选项作为数组传递给第四个参数 . 我建议传递将
PDO
置于异常模式的参数 . 因为某些PDO
驱动程序不支持本机预处理语句,所以PDO
执行准备的模拟 . 它还允许您手动启用此仿真 . 要使用本机服务器端预处理语句,应明确设置它false
.另一种方法是关闭默认情况下在
MySQL
驱动程序中启用的准备仿真,但应该关闭准备仿真以安全地使用PDO
.我稍后会解释为什么应该关闭准备仿真 . 要查找原因,请查看this post .
它仅在您使用我不推荐的旧版本
MySQL
时才可用 .以下是如何执行此操作的示例:
Can we set attributes after PDO construction?
Yes ,我们还可以在使用
setAttribute
方法构建PDO后设置一些属性:错误处理
在
PDO
中,错误处理比mysql_*
容易得多 .使用
mysql_*
时的常见做法是:OR die()
不是处理错误的好方法,因为我们无法处理die
中的事情 . 它会突然结束脚本,然后将错误回显到您通常不希望向最终用户显示的屏幕,并让血腥的黑客发现您的架构 . 或者,mysql_*
函数的返回值通常可以与mysql_error()一起使用来处理错误 .PDO
提供了更好的解决方案:例外 . 我们用_74330做的任何事情都应该包含在try
-catch
块中 . 我们可以通过设置错误模式属性将PDO
强制转换为三种错误模式之一 . 下面是三种错误处理模式 .PDO::ERRMODE_SILENT
. 它只是设置错误代码并且与mysql_*
几乎相同,您必须检查每个结果,然后查看$db->errorInfo();
以获取错误详细信息 .PDO::ERRMODE_WARNING
举起E_WARNING
. (运行时警告(非致命错误) . 脚本的执行不会停止 . )PDO::ERRMODE_EXCEPTION
:抛出异常 . 它表示PDO引发的错误 . 你不应该从自己的代码中抛出PDOException
. 有关PHP中的异常的更多信息,请参阅异常 . 当它没有被捕获时,它的行为非常像or die(mysql_error());
. 但与or die()
不同的是,如果您选择这样做,PDOException
可以被优雅地捕获和处理 .Good read :
Errors and error handling ¶
The PDOException class ¶
Exceptions ¶
喜欢:
你可以把它包装在
try
-catch
中,如下所示:您现在不必处理
try
-catch
. 你可以随时 grab 它,但我强烈建议你使用try
-catch
. 另外,在调用PDO
函数的函数外部捕获它可能更有意义:此外,你可以通过
or die()
来处理,或者我们可以说像mysql_*
,但它会变化多端 . 您可以通过转动display_errors off
并只读取错误日志来隐藏 生产环境 中的危险错误消息 .现在,在阅读了上述所有内容后,您可能正在思考:当我只想开始倾向于简单的
SELECT
,INSERT
,UPDATE
或DELETE
语句时,到底是什么?别担心,我们走了:选择数据
所以你在
mysql_*
做的是:现在
PDO
,您可以这样做:要么
Note :如果使用如下方法(
query()
),则此方法返回PDOStatement
对象 . 因此,如果您想获取结果,请像上面一样使用它 .在PDO数据中,它是通过
->fetch()
获得的,这是一种语句句柄方法 . 在调用fetch之前,最好的方法是告诉PDO你想要获取数据的方式 . 在下面的部分我将解释这一点 .获取模式
注意在上面的
fetch()
和fetchAll()
代码中使用PDO::FETCH_ASSOC
. 这告诉PDO
将行作为关联数组返回,并将字段名称作为键 . 还有许多其他的获取模式,我将逐一解释 .首先,我解释如何选择获取模式:
在上面,我一直在使用
fetch()
. 您还可以使用:PDOStatement::fetchAll() - 返回包含所有结果集行的数组
PDOStatement::fetchColumn() - 从结果集的下一行返回单个列
PDOStatement::fetchObject() - 获取下一行并将其作为对象返回 .
PDOStatement::setFetchMode() - 设置此语句的默认提取模式
现在我来获取模式:
PDO::FETCH_ASSOC
:返回由结果集中返回的列名索引的数组PDO::FETCH_BOTH
(默认值):返回由结果集中返回的列名和0索引列号索引的数组还有更多的选择!在PDOStatement Fetch documentation.中了解所有这些内容 .
Getting the row count :
而不是使用
mysql_num_rows
来获取返回的行数,您可以获得PDOStatement
并执行rowCount()
,如:Getting the Last Inserted ID
插入和更新或删除语句
我们在
mysql_*
函数中所做的是:在pdo中,同样的事情可以通过以下方式完成:
在上面的查询中PDO::exec执行一条SQL语句并返回受影响的行数 .
稍后将介绍插入和删除 .
只有在查询中不使用变量时,上述方法才有用 . 但是当你需要在查询中使用变量时,不要尝试上面那样的prepared statement or parameterized statement .
准备好的陈述
Q. 什么是准备好的声明,为什么需要它们?
A. 预准备语句是预编译的SQL语句,可以通过仅将数据发送到服务器来执行多次 .
使用预准备语句的典型工作流程如下(quoted from Wikipedia three 3 point):
?
):INSERT INTO PRODUCT (name, price) VALUES (?, ?)
DBMS在语句模板上解析,编译和执行查询优化,并存储结果而不执行它 .
Execute :稍后,应用程序提供(或绑定)参数的值,DBMS执行该语句(可能返回结果) . 应用程序可以使用不同的值多次执行语句 . 在此示例中,它可能为第一个参数提供'Bread',为第二个参数提供
1.00
.您可以通过在SQL中包含占位符来使用预准备语句 . 基本上有三个没有占位符(不要尝试使用上面的变量),一个带有未命名的占位符,另一个带有命名的占位符 .
Q. 所以现在,有什么名字占位符以及如何使用它们?
A. 命名占位符 . 使用以冒号开头的描述性名称,而不是问号 . 我们不关心名称持有人的 Value 位置/顺序:
bindParam(parameter,variable,data_type,length,driver_options)
您也可以使用执行数组进行绑定:
OOP
朋友的另一个不错的功能是命名占位符能够将对象直接插入到数据库中,假设属性与命名字段匹配 . 例如:Q. 那么现在,什么是未命名的占位符以及如何使用它们?
A. 让我们举个例子:
和
在上面,你可以看到那些
?
而不是像名字持有者那样的名字 . 现在在第一个示例中,我们将变量分配给各个占位符($stmt->bindValue(1, $name, PDO::PARAM_STR);
) . 然后,我们为这些占位符分配值并执行该语句 . 在第二个示例中,第一个数组元素转到第一个?
,第二个转到第二个?
.NOTE :在 unnamed placeholders 中,我们必须注意我们传递给
PDOStatement::execute()
方法的数组中元素的正确顺序 .SELECT,INSERT,UPDATE,DELETE准备好的查询
注意:
但是
PDO
和/或MySQLi
并不完全安全 . 检查ircmaxell的答案Are PDO prepared statements sufficient to prevent SQL injection? . 另外,我引用他的回答中的一部分:说到技术原因,只有少数,非常具体,很少使用 . 很可能你永远不会在生活中使用它们 .
也许我太无知了,但我从未有机会使用它们之类的东西
非阻塞,异步查询
存储过程返回多个结果集
加密(SSL)
压缩
如果你需要它们 - 这些毫无疑问是技术上的原因,从mysql扩展转向更时尚和现代化的东西 .
Nevertheless, there are also some non-technical issues, which can make your experience a bit harder
在现代PHP版本中进一步使用这些函数将引发弃用级别的通知 . 他们只是可以关闭 .
在遥远的未来,它们可能会从默认的PHP构建中删除 . 也没什么大不了的,因为mydsql ext将被转移到PECL中,每个主机都很乐意用它来编译PHP,因为他们不想失去那些网站工作了几十年的客户 .
来自Stackoverflow社区的强大阻力 . Еverytime你提到这些诚实的功能,你被告知他们是严格的禁忌 .
是普通的PHP用户,很可能你使用这些函数的想法容易出错并且错误 . 正因为所有这些教程和手册教你错误的方法 . 不是功能本身 - 我必须强调它 - 但它们的使用方式 .
后一个问题是一个问题 .
但是,在我看来,提议的解决方案也不是更好 .
在我看来,所有这些PHP用户都将学习如何正确处理SQL查询 . 很可能他们只是将mysql_ *机械地改为mysqli_ *, leaving the approach the same . 特别是因为mysqli使准备好的语句使用令人难以置信的痛苦和麻烦 .
更不用说SQL注入的原生语句 aren't enough to protect ,mysqli和PDO都没有提供解决方案 .
所以,与其打击这种诚实的延伸,我宁愿采取错误的做法,并以正确的方式教育人们 .
Also, there are some false or non-significant reasons, like
不支持存储过程(我们多年来一直使用
mysql_query("CALL my_proc");
)不支持交易(与上述相同)
不支持多个语句(谁需要它们?)
没有正在积极开发(那么什么?它以任何实际的方式对你有影响吗?)
缺少OO界面(创建一个是几个小时的事情)
不支持预准备语句或参数化查询
最后一个是有趣的一点 . 虽然mysql ext不支持本机预处理语句,但它们不是安全性所必需的 . 我们可以使用手动处理的占位符轻松伪造准备好的语句(就像PDO一样):
瞧,一切都是参数化和安全的 .
但好吧,如果您不喜欢手册中的红色框,会出现选择问题:mysqli或PDO?
嗯,答案如下:
如果您了解使用数据库抽象层并寻找创建API的必要性, mysqli 是一个非常好的选择,因为它确实支持许多特定于mysql的功能 .
如果像绝大多数PHP人一样,你正在应用程序代码中使用原始API调用(这实际上是错误的做法) - PDO is the only choice ,因为这个扩展假装不只是API而是半DAL,仍然不完整但是提供了许多重要的功能,其中两个使得PDO与mysqli有着明显的区别:
与mysqli不同,PDO可以按值绑定占位符,这使得动态构建的查询可行而没有几个相当混乱的屏幕码 .
与mysqli不同,PDO总是可以在一个简单的常用数组中返回查询结果,而mysqli只能在mysqlnd安装上执行 .
因此,如果您是普通的PHP用户并希望在使用本机预处理语句时节省大量的麻烦,那么再次使用PDO是唯一的选择 .
然而,PDO也不是银弹,并且有其艰辛 .
所以,我为PDO tag wiki中的所有常见陷阱和复杂案例编写了解决方案 .
然而,每个人都在谈论扩展总是错过关于Mysqli和PDO的 2 important facts :
准备好的声明 isn't a silver bullet . 有动态标识符不能使用预准备语句绑定 . 存在具有未知数量的参数的动态查询,这使得查询构建成为困难的任务 .
Neither mysqli_ nor PDO functions should have appeared in the application code.*
它们和应用程序代码之间应该有一个 abstraction layer ,它将完成内部绑定,循环,错误处理等所有脏工作,使应用程序代码干燥和清理 . 特别是对于像动态查询构建这样的复杂情况 .
所以,仅仅切换到PDO或mysqli是不够的 . 必须使用ORM,查询构建器或任何数据库抽象类,而不是在其代码中调用原始API函数 .
相反 - 如果你的应用程序代码和mysql API之间有一个抽象层 - it doesn't actually matter which engine is used. 你可以使用mysql ext直到它被弃用,然后轻松地将你的抽象类重写为另一个引擎, having all the application code intact.
以下是基于我的safemysql class的一些示例,以显示这样的抽象类应该如何:
将这一行与amount of code you will need with PDO进行比较 .
然后与crazy amount of code进行比较,您需要使用原始的Mysqli预处理语句 . 请注意,错误处理,分析,查询日志已经内置并运行 .
将它与通常的PDO插入进行比较,当每个字段名称重复六到十次时 - 在所有这些众多的命名占位符,绑定和查询定义中 .
另一个例子:
你很难找到PDO处理这种实际案例的例子 .
而且它太过于罗嗦而且很可能不安全 .
所以,再一次 - 不仅仅是原始驱动程序应该是你的关注而是抽象类,不仅对初学者手册中的愚蠢例子有用,而且对解决任何现实生活中的问题都很有用 .
因为(除其他原因外)确保输入数据被消毒要困难得多 . 如果您使用参数化查询,就像使用PDO或mysqli一样,您可以完全避免风险 .
例如,有人可以使用
"enhzflep); drop table users"
作为用户名 . 旧函数将允许每个查询执行多个语句,因此类似讨厌的bugger可以删除整个表 .如果要使用mysqli的PDO,则用户名将最终为
"enhzflep); drop table users"
.见bobby-tables.com .