为什么我不应该在PHP中使用mysql_ *函数?
为什么不应该使用mysql_*
函数的技术原因是什么? (例如mysql_query()
, mysql_connect()
或mysql_real_escape_string()
)?
为什么我应该使用别的东西,即使他们在我的网站上工作?
如果他们没有在我的网站上工作,为什么我会得到如Warning:mysql_connect()这样的错误:没有这样的文件或目录 ?
MySQL扩展:
- 没有积极的发展
- 自PHP 5.5起(2013年6月发布) 正式弃用 。
- 已从 PHP 7.0(2015年12月发布) 完全删除
- 这意味着截至2018年12月31日,它不会存在于任何受支持的PHP版本中。 目前它只获得安全更新。
- 缺less一个面向对象的接口
- 不支持:
- 非阻塞asynchronous查询
- 编写语句或参数化查询
- 存储过程
- 多个陈述
- 交易
- “新的”密码authentication方法(在MySQL 5.6中默认开启;在5.7中需要)
- MySQL 5.1中的所有function
由于它被弃用,使用它会使你的代码在未来的certificate。
对准备好的语句缺乏支持是非常重要的,因为它们提供了一个更清晰,更不容易出错的转义和引用外部数据的方法,而不是通过单独的函数调用手动转义它。
查看SQL扩展的比较 。
PHP提供了三种不同的API来连接到MySQL。 这些是mysql
(从PHP 7中删除), mysqli
和PDO
扩展。
mysql_*
函数以前非常stream行,但是不再鼓励使用它们。 文档团队正在讨论数据库安全情况,并教育用户摆脱常用的ext / mysql扩展是这个的一部分(检查php.internals:使用ext / mysql )。
后来的PHP开发人员团队已经决定在用户连接到MySQL时生成E_DEPRECATED
错误,无论是通过mysql_connect()
, mysql_pconnect()
还是内置在ext/mysql
的隐式连接function。
从PHP 5.5开始 , ext/mysql
被正式弃用 ,从PHP 7开始已经被删除了 。
看到红盒?
当你去任何mysql_*
函数手册页,你会看到一个红色的盒子,解释它不应该被使用了。
为什么
离开ext/mysql
不仅关系到安全性,还关系到访问MySQL数据库的所有function。
ext/mysql
是为MySQL 3.23构build的,从那以后只增加了很less的内容,而且大部分时间保持与这个旧版本的兼容性,这使得代码难以维护。 缺lessext/mysql
不支持的function包括:( 来自PHP手册 )。
- 存储过程 (不能处理多个结果集)
- 准备好的陈述
- encryption(SSL)
- 压缩
- 完整的字符集支持
不使用mysql_*
函数的原因 :
- 没有积极的发展
- 从PHP 7中删除
- 缺less一个面向对象的接口
- 不支持非阻塞的asynchronous查询
- 不支持预准备语句或参数化查询
- 不支持存储过程
- 不支持多个语句
- 不支持交易
- 不支持MySQL 5.1中的所有function
昆汀的回答引用了上面这一点
对准备好的语句缺乏支持是非常重要的,因为它们提供了一个更清晰,更不容易出错的转义和引用外部数据的方法,而不是通过单独的函数调用手动转义它。
查看SQL扩展的比较 。
抑制弃用警告
在将代码转换为MySQLi
/ PDO
,可以通过在php.ini中设置error_reporting
来排除E_DEPRECATED:
error_reporting = E_ALL ^ E_DEPRECATED
请注意,这也会隐藏其他的弃用警告 ,但是,这可能是MySQL以外的其他情况。 ( 来自PHP手册 )
文章PDO与MySQLi:你应该使用哪一个? 由德扬Marjanovic将帮助您select。
而更好的方法是PDO
,现在我正在编写一个简单的PDO
教程。
一个简单而简短的PDO教程
问:我脑海中的第一个问题是:什么是“PDO”?
答 :“ PDO – PHP数据对象 – 是一个数据库访问层,提供了访问多个数据库的统一方法。
连接到MySQL
使用mysql_*
函数,或者我们可以用旧的方式(在PHP 5.5及以上版本中不推荐使用)
$link = mysql_connect('localhost', 'user', 'pass'); mysql_select_db('testdb', $link); mysql_set_charset('UTF-8', $link);
使用PDO
:您只需要创build一个新的PDO
对象。 构造函数接受用于指定数据库源的参数PDO
的构造函数大多需要四个参数,它们是DSN
(数据源名称)和可选的username
和password
。
在这里,我认为你除了DSN
之外都是熟悉的; 这在PDO
是新的。 DSN
基本上是一串选项,告诉PDO
使用哪个驱动程序,以及连接细节。 有关进一步参考,请查看PDO MySQL DSN 。
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');
注意:你也可以使用charset=UTF-8
,但有时会导致错误,所以最好使用utf8
。
如果有任何连接错误,它将抛出一个可以caching的PDOException
对象来进一步处理Exception
。
良好的阅读 : 连接和连接pipe理¶
您也可以将多个驱动程序选项作为数组传递给第四个参数。 我build议通过放入PDO
的参数进入exception模式。 由于某些PDO
驱动程序不支持本机预处理语句,因此PDO
会执行准备工作。 它也可以让你手动启用这个模拟。 要使用本机服务器端准备好的语句,您应该明确地将其设置为false
。
另一种是closures在MySQL
驱动程序中默认启用的准备模拟,但是准备模拟应该closures以便安全地使用PDO
。
稍后我会解释为什么准备模拟应该被closures。 要find理由,请检查这个职位 。
只有在使用我不推荐的MySQL
旧版本时才可用。
下面是你如何做到这一点的例子:
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 'username', 'password', array(PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
我们可以在PDO构build后设置属性吗?
是的 ,我们也可以使用setAttribute
方法在PDO构build之后设置一些属性:
$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 'username', 'password'); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
error handling
PDO
error handling比mysql_*
容易得多。
使用mysql_*
时的一个常见做法是:
//Connected to MySQL $result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));
OR die()
不是处理错误的好方法,因为我们无法处理这个事情。 它会突然结束脚本,然后将错误回显到通常不希望显示给最终用户的屏幕上,让血腥的黑客发现您的架构。 或者, mysql_*
函数的返回值通常可以和mysql_error()一起使用来处理错误。
PDO
提供了一个更好的解决scheme:例外。 我们用PDO
做的任何事情都应该包含在try
– catch
块中。 通过设置错误模式属性,我们可以强制PDO
进入三种错误模式之一。 下面是三种error handling模式。
-
PDO::ERRMODE_SILENT
。 它只是设置错误代码,并且与mysql_*
几乎相同,您必须检查每个结果,然后查看$db->errorInfo();
获取错误的详细信息。 -
PDO::ERRMODE_WARNING
提高E_WARNING
。 (运行时警告(非致命错误),脚本的执行不会停止。) -
PDO::ERRMODE_EXCEPTION
:抛出exception。 它代表了PDO提出的一个错误。 你不应该从你自己的代码中抛出一个PDOException
。 有关PHP中的exception的更多信息,请参阅exception 。 它非常像or die(mysql_error());
,当它没有被抓到。 但是不像or die()
,如果你select的话,PDOException
可以被正确地捕获和处理。
好读 :
- 错误和error handling¶
- PDOException类¶
- 例外¶
喜欢:
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); $stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING ); $stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
你可以用try
– catch
来包装,如下所示:
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()); }
你现在不必处理try
。 你可以在任何适当的时候捕捉它,但我强烈build议你使用try
– catch
。 在调用PDO
的函数外部捕获它也许更有意义:
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_*
,但它会变得非常多。 您可以通过closuresdisplay_errors off
并只读取错误日志来隐藏生产中的危险错误消息。
现在,在阅读了上述所有内容之后,您可能会想:当我只想简单地使用SELECT
, INSERT
, UPDATE
或DELETE
语句时,究竟是什么感觉? 别担心,我们走吧:
select数据
那么你在做什么mysql_*
是:
<?php $result = mysql_query('SELECT * from table') or die(mysql_error()); $num_rows = mysql_num_rows($result); while($row = mysql_fetch_assoc($result)) { echo $row['field1']; }
现在在PDO
,你可以这样做:
<?php $stmt = $db->query('SELECT * FROM table'); while($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo $row['field1']; }
要么
<?php $stmt = $db->query('SELECT * FROM table'); $results = $stmt->fetchAll(PDO::FETCH_ASSOC); //Use $results
注意 :如果使用下面的方法( query()
),则此方法返回一个PDOStatement
对象。 所以如果你想获取结果,像上面一样使用它。
<?php foreach($db->query('SELECT * FROM table') as $row) { echo $row['field1']; }
在PDO数据中,它是通过->fetch()
,你的语句句柄的方法获得的。 在调用fetch之前,最好的方法是告诉PDO你想如何获取数据。 在下面的部分我正在解释这一点。
获取模式
请注意,在上面的fetch()
和fetchAll()
代码中使用了PDO::FETCH_ASSOC
。 这告诉PDO
将行作为关联数组以字段名作为关键字返回。 还有很多其他的获取模式,我将逐一解释。
首先,我解释如何select获取模式:
$stmt->fetch(PDO::FETCH_ASSOC)
在上面,我一直在使用fetch()
。 你也可以使用:
-
PDOStatement::fetchAll()
– 返回一个包含所有结果集行的数组 -
PDOStatement::fetchColumn()
– 从结果集的下一行返回一列 -
PDOStatement::fetchObject()
– 获取下一行并将其作为对象返回。 -
PDOStatement::setFetchMode()
– 为此语句设置默认的获取模式
现在我来取模式:
-
PDO::FETCH_ASSOC
:返回结果集中返回的按列名索引的数组 -
PDO::FETCH_BOTH
(默认值):返回一个由列名和0索引列号索引的数组,返回结果集
还有更多的select! 在PDOStatement
Fetch文档中阅读所有内容。 。
获得行数 :
而不是使用mysql_num_rows
来获取返回的行数,你可以得到一个PDOStatement
和做rowCount()
,如:
<?php $stmt = $db->query('SELECT * FROM table'); $row_count = $stmt->rowCount(); echo $row_count.' rows selected';
获取最后插入的ID
<?php $result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')"); $insertId = $db->lastInsertId();
插入和更新或删除语句
我们在mysql_*
函数中做的是:
<?php $results = mysql_query("UPDATE table SET field='value'") or die(mysql_error()); echo mysql_affected_rows($result);
而在pdo中,同样的事情可以通过:
<?php $affected_rows = $db->exec("UPDATE table SET field='value'"); echo $affected_rows;
在上面的查询中, PDO::exec
执行一条SQL语句并返回受影响的行数。
插入和删除将在稍后介绍。
上述方法仅在查询中不使用variables时才有用。 但是当你需要在一个查询中使用一个variables的时候,千万不要像上面那样尝试,并且准备好的语句或者参数化语句就是这样的。
准备好的陈述
问:什么是准备好的陈述,为什么我需要他们?
答:准备好的语句是预编译的SQL语句,可以通过仅将数据发送到服务器多次执行。
使用准备语句的典型工作stream程如下( 引自维基百科三点三 ):
-
准备 :语句模板由应用程序创build并发送到数据库pipe理系统(DBMS)。 某些值是未指定的,称为参数,占位符或绑定variables(标记如下):
INSERT INTO PRODUCT (name, price) VALUES (?, ?)
-
DBMSparsing,编译并在语句模板上执行查询优化,并存储结果而不执行。
- 执行 :稍后,应用程序提供(或绑定)参数值,DBMS执行该语句(可能返回结果)。 应用程序可以根据需要以不同的值执行多次该语句。 在这个例子中,它可能为第一个参数提供“Bread”,为第二个参数提供
1.00
。
您可以通过在SQL中包含占位符来使用准备好的语句。 基本上有三个没有占位符的地方(不要试图用上面那个variables来做这个),一个是没有名字的占位符,另一个是带有指定的占位符。
问:那么现在,什么是指定的占位符,我如何使用它们?
A.命名的占位符。 使用以冒号开头的描述性名称,而不是问号。 我们不关心名人所在地的价值的位置/顺序:
$stmt->bindParam(':bla', $bla);
bindParam(parameter,variable,data_type,length,driver_options)
你也可以使用execute数组来绑定:
<?php $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name"); $stmt->execute(array(':name' => $name, ':id' => $id)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
OOP
好友的另一个不错的function是,假定属性与命名字段匹配,命名的占位符就可以直接将对象插入到数据库中。 例如:
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);
问:那么现在,什么是无名的占位符,我如何使用它们?
A.举个例子吧:
<?php $stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)"); $stmt->bindValue(1, $name, PDO::PARAM_STR); $stmt->bindValue(2, $add, PDO::PARAM_STR); $stmt->execute();
和
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)"); $stmt->execute(array('john', '29 bla district'));
在上面,你可以看到那些?
而不是像名称持有者那样的名字。 现在在第一个例子中,我们将variables分配给各种占位符( $stmt->bindValue(1, $name, PDO::PARAM_STR);
)。 然后,我们为这些占位符赋值并执行语句。 在第二个例子中,第一个数组元素转到第一个?
第二个到第二个?
。
注意 :在未命名的占位符中,我们必须注意传递给PDOStatement::execute()
方法的数组中元素的正确顺序。
SELECT
, INSERT
, UPDATE
, DELETE
准备的查询
-
SELECT
:$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name"); $stmt->execute(array(':name' => $name, ':id' => $id)); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
-
INSERT
:$stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)"); $stmt->execute(array(':field1' => $field1, ':field2' => $field2)); $affected_rows = $stmt->rowCount();
-
DELETE
:$stmt = $db->prepare("DELETE FROM table WHERE id=:id"); $stmt->bindValue(':id', $id, PDO::PARAM_STR); $stmt->execute(); $affected_rows = $stmt->rowCount();
-
UPDATE
:$stmt = $db->prepare("UPDATE table SET name=? WHERE id=?"); $stmt->execute(array($name, $id)); $affected_rows = $stmt->rowCount();
注意:
但是, PDO
和/或MySQLi
并不完全安全。 检查答案PDO准备的语句是否足以防止SQL注入? 由ircmaxell 。 另外,我从他的回答中引用了一些部分:
$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 /*"));
首先,让我们从我们给每个人的标准评论开始:
请不要在新代码中使用
mysql_*
函数 。 他们不再维护,并正式弃用 。 看到红色的盒子 ? 了解准备好的语句 ,并使用PDO或MySQLi – 本文将帮助您决定哪些。 如果你selectPDO, 这里是一个很好的教程 。
我们一个接一个地去看看,然后解释一下:
-
他们不再被维护,并被正式弃用
这意味着PHP社区正在逐渐放弃对这些非常古老的function的支持。 它们很可能不存在于未来(最近)的PHP版本中! 继续使用这些函数可能会在不远的将来破坏你的代码。
新! – 从PHP 5.5起, ext / mysql现在已经正式被弃用了!
较新的! ext / mysql 已经在PHP 7中被删除了
-
相反,你应该学习准备好的陈述 –
mysql_*
扩展不支持mysql_*
准备语句 ,这是(除其他之外)针对SQL注入的一个非常有效的对策。 它修复了MySQL依赖应用程序中的一个非常严重的漏洞,允许攻击者访问您的脚本,并对您的数据库执行任何可能的查询 。有关更多信息,请参阅如何防止PHP中的SQL注入?
-
看到红盒?
当你进入任何
mysql
函数手册页,你会看到一个红色的框,解释它不应该被使用了。 -
使用PDO或MySQLi
有更好,更强大和更完善的替代scheme, PDO – PHP数据库对象 ,它提供了一个完整的面向数据库交互的OOP方法, MySQLi ,这是一个MySQL的具体改进。
使用方便
分析和综合的原因已经提到。 对于新手来说,有一个更重要的动机来停止使用date的mysql_函数。
当代数据库API只是更容易使用。
它主要是可以简化代码的绑定参数 。 而且,通过优秀的教程(如上所示) ,向PDO的过渡不会过于繁琐。
一次重写更大的代码库需要时间。 对于这个中间select来说,Raison d'être:
等价的pdo_ *函数代替mysql_ *
使用< pdo_mysql.php >,您可以用最less的努力从旧的mysql_函数切换。 它增加了pdo_
函数包装器,它们replace了mysql_
对应的函数。
-
简单的
include_once(
"pdo_mysql.php"
);
在每个必须与数据库交互的调用脚本中。 -
除去
函数前缀,并将其replace为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()
- 等等…
-
-
你的代码将工作相似,仍然大致看起来是一样的:
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()
为绑定参数增加了非常容易的支持。 转换旧的代码很简单:
将您的variables移出SQLstring。
- 将它们作为逗号分隔的函数参数添加到
pdo_query()
。 - 放置问号
?
作为variables之前的占位符。 - 摆脱之前包含string值/variables
'
单引号。
对于更长的代码来说,优势变得更加明显。
通常,stringvariables不是插入到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)
请记住,pdo_ *仍然允许或 。
只是不要转义一个variables, 并将其绑定在同一个查询中。
- 占位符function由背后的真实PDO提供。
- 因此也允许
:named
稍后:named
占位符列表。
更重要的是,您可以在任何查询后安全地传递$ _REQUEST []variables。 当提交的<form>
字段完全匹配数据库结构时,它甚至更短:
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);
非常简单。 但让我们回到一些重写build议和技术原因,为什么你可能想摆脱和转义。 mysql_
修复或删除任何oldschool sanitize()
函数
一旦将所有的调用转换为带有参数的mysql_
pdo_query
,删除所有冗余的pdo_real_escape_string
调用。
特别是你应该修复任何sanitize
或clean
或filterThis
或clean_data
function,如date的教程在一种forms或另一种forms:
function sanitize($str) { return trim(strip_tags(htmlentities(pdo_real_escape_string($str)))); }
这里最明显的缺陷是缺less文档。 更重要的是,过滤的顺序完全是错误的顺序。
-
正确的顺序应该是:不
stripslashes
使用stripslashes
作为最内层的调用,然后trim
strip_tags
,输出上下文的htmlentities
,最后只有_escape_string
作为其应用程序直接在SQL间隔之前。 -
但作为第一步只是摆脱
_real_escape_string
调用。 -
如果您的数据库和应用程序stream期望HTML上下文安全的string,那么您现在可能必须保留
sanitize()
函数的其余部分。 添加一个评论,它只适用于今后的HTML转义。 -
string/值处理委托给PDO及其参数化语句。
-
如果您的清理function中提到了
stripslashes()
,则可能表示有更高层次的疏漏。-
这通常是撤销已废弃的
magic_quotes
造成的损坏(双重转义)。 然而,哪一个最好是固定的 ,而不是string。 -
使用其中一种用户态逆转方法。 然后删除
sanitize
函数中的stripslashes()
。
有关magic_quotes的历史笔记。 该function是正确的弃用。 但是,它经常被错误地描述为失败的安全function。 但是magic_quotes与网球作为营养来源失败的安全function一样失败。 那根本不是他们的目的。
在PHP2 / FI的原始实现引入了明确的“ 引号将被自动转义,更容易将表单数据直接传递给msql查询 ”。 值得注意的是,与mSQL一起使用是安全的,因为只支持ASCII。
然后PHP3 / Zend为MySQL重新引入了magic_quotes,并将其误logging。 但最初这只是一个方便的function ,不是为了安全。 -
如何准备报表不同
当您将stringvariables置乱到SQL查询中时,它不会变得更加错综复杂。 这也是MySQL为了再次分离代码和数据而付出的额外努力。
SQL注入只是当数据stream入代码上下文时。 一个数据库服务器不能在PHP最初粘贴查询子句之间的variables的地方。
使用绑定的参数,可以在PHP代码中分离SQL代码和SQL上下文值。 但是它不会在幕后重新洗牌(除了PDO :: EMULATE_PREPARES)。 您的数据库接收不变的SQL命令和1:1variables值。
虽然这个答案强调你应该关心的可读性好处。 由于这种可见的和技术性的数据/代码分离,偶尔也有一个性能优势(反复插入只有不同的值)。 mysql_
请注意参数绑定仍然不是针对所有 SQL注入的魔术一站式解决scheme。 它处理数据/值最常用的用法。 但不能列名列表/表标识符,帮助dynamic子句构造,或只是普通的数组值列表。
混合使用PDO
这些pdo_*
包装函数使编码友好的停止API。 (这几乎是MYSQLI
如果不是因为特殊的函数签名转换而可能发生的)。 他们也暴露了真正的PDO在大多数时间。
重写不必停止使用新的pdo_函数名称。 您可以逐个将每个pdo_query()转换为普通的$ pdo-> prepare() – > execute()调用。
然而,最好再从简化开始。 例如,获取常见的结果:
$result = pdo_query("SELECT * FROM tbl"); while ($row = pdo_fetch_assoc($result)) {
可以用一个foreach迭代代替:
foreach ($result as $row) {
或者更好的是直接和完整的数组检索:
$result->fetchAll();
在大多数情况下,您会得到比PDO或mysql_通常在失败查询后提供的更有用的警告。
其他选项
所以这希望可以看到一些实际的原因和一个值得删除途径。 mysql_
只是切换到Pdo并不能完全切断它。 pdo_query()
也只是一个前端。
除非你还引入了参数绑定,或者可以利用更好的API中的其他东西,否则这是一个毫无意义的开关。 我希望它的描述足够简单,不要让新人感到灰心。 (教育通常比禁止更好。)
虽然它符合最简单的可能工作类别,但它仍然是非常实验性的代码。 我只是在周末写的。 然而,有太多的select。 只需谷歌PHP数据库抽象和浏览一点。 对于这样的任务总是会有很多优秀的库。
如果你想进一步简化你的数据库交互,像Paris / Idiorm这样的映射器是值得一试的。 就像没有人在JavaScript中使用平淡的DOM一样,你现在不需要照顾一个原始的数据库接口。
The mysql_
functions are:
- out of date – they're not maintained any more
- don't allow you to move easily to another database backend
- don't support prepared statements, hence
- encourage programmers to use concatenation to build queries, leading to SQL injection vulnerabilities
Speaking of technical reasons, there are only few, extremely specific and rarely used. Most likely you will never ever use them in your life.
May be I am too ignorant, but I never had an opportunity to use them things like
- non-blocking, asynchronous queries
- stored procedures returning multiple resultsets
- Encryption (SSL)
- 压缩
If you need them – these are no doubt technical reasons to move away from mysql extension toward something more stylish and modern-looking.
Nevertheless, there are also some non-technical issues, which can make your experience a bit harder
- further use of these functions with modern PHP versions will raise deprecated-level notices. They simply can be turned off.
- in a distant future they can be possibly removed from the default PHP build. Not a big deal too, as mydsql ext will be moved into PECL and every hoster will be happy to complie PHP with it, as they don't want to lose clients whose sites were working for decades.
- strong resistance from Stackoverflow community. Еvery time you mention these honest functions, you being told that they are under strict taboo.
- being an average php user, most likely your idea of using these functions is error-prone and wrong. Just because of all these numerous tutorials and manuals which teach you wrong way. Not the functions themselves – I have to emphasize it – but the way they are used.
This latter issue is a problem.
But, to my opinion, proposed solution is no better either.
It seems to me too idealistic a dream that all those PHP users will learn how to handle SQL queries properly at once. Most likely they would just change mysql_* to mysqli_* mechanically, leaving the approach the same . Especially because mysqli makes prepared statements usage incredible painful and troublesome.
Not to mention that native prepared statements aren't enough to protect from SQL injections, and neither mysqli nor PDO offers a solution.
So, instead of fighting this honest extension, I'd prefer to fight wrong practices and educate people in the right ways.
Also, there are some false or non-significant reasons, like
- Doesn't support Stored Procedures (we were using
mysql_query("CALL my_proc");
for ages) - Doesn't support Transactions (same as above)
- Doesn't support Multiple Statements (who need them?)
- Not under active development (so what? does it affect you in any practical way?)
- Lacks an OO interface (to create one is a matter of several hours)
- Doesn't support Prepared Statements or Parametrized Queries
A latter one is an interesting point. Although mysql ext do not support native prepared statements, they aren't required for the safety. We can easily fake prepared statements using manually handled placeholders (just like PDO does):
function paraQuery() { $args = func_get_args(); $query = array_shift($args); $query = str_replace("%s","'%s'",$query); foreach ($args as $key => $val) { $args[$key] = mysql_real_escape_string($val); } $query = vsprintf($query, $args); $result = mysql_query($query); if (!$result) { throw new Exception(mysql_error()." [$query]"); } return $result; } $query = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d"; $result = paraQuery($query, $a, "%$b%", $limit);
voila , everything is parameterized and safe.
But okay, if you don't like the red box in the manual, a problem of choice arises: mysqli or PDO?
Well, the answer would be as follows:
- If you understand the necessity of using a database abstraction layer , and looking for an API to create one, mysqli is a very good choice, as it indeed supports many mysql-specific features.
-
If, like wast majority of PHP folks, you are using raw API calls right in the application code (which is essentially wrong practice) – PDO is the only choice , as this extension pretends to be not just API but rather a semi-DAL, still incomplete but offers many important features, with two of them makes PDO critically distinguished from mysqli:
- unlike mysqli, PDO can bind placeholders by value , which makes dynamically built queries feasible without several screens of quite messy code.
- unlike mysqli, PDO can always return query result in a simple usual array, while mysqli can do it only on mysqlnd installations.
So, if you are an average PHP user and want to save yourself a ton of headaches when using native prepared statements, PDO – again – is the only choice.
However, PDO is not a silver bullet too, and has it's hardships.
So, I wrote solutions for all the common pitfalls and complex cases in the PDO tag wiki
Nevertheless, everyone talking of extensions always missing the 2 important facts about Mysqli and PDO:
-
Prepared statement isn't a silver bullet . There are dynamical identifiers which cannot be bound using prepared statements. There are dynamical queries with unknown number of parameters which makes query building a difficult task.
-
Neither mysqli_* nor PDO functions should be appeared in the application code.
There ought to be an abstraction layer between them and application code, which will do all the dirty job of binding, looping, error handling, etc. inside, making application code DRY and clean. Especially for the complex cases like dynamical query building.
So, just switching to PDO or mysqli is not enough. One have to use an ORM, or a query builder, or whatever database abstraction class instead of calling raw API functions in their code.
And contrary – if you have an abstraction layer between your application code and mysql API – it doesn't actually matter which engine is used. You can use mysql ext until it goes deprecated and then easily rewrite your abstraction class to another engine, having all the application code intact.
Here are some examples based on my safemysql class to show how such an abstraction class ought to be:
$city_ids = array(1,2,3); $cities = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);
Compare this one single line with amount of code you will need with PDO .
Then compare with crazy amount of code you will need with raw Mysqli prepared statements. Note that error handling, profiling, query logging already built in and running.
$insert = array('name' => 'John', 'surname' => "O'Hara"); $db->query("INSERT INTO users SET ?u", $insert);
Compare it with usual PDO inserts, when every single field name being repeated six to ten times – in all these numerous named placeholders, bindings and query definitions.
另一个例子:
$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);
You can hardly find an example for PDO to handle such practical case.
And it will be too wordy and most likely unsafe.
So, once more – it is not just raw driver should be your concern but abstraction class, useful not only for silly examples from beginner's manual but to solve whatever real life problems.
There are many reasons, but perhaps the most important one is that those functions encourage insecure programming practices because they do not support prepared statements. Prepared statements help prevent SQL injection attacks.
When using mysql_*
functions, you have to remember to run user-supplied parameters through mysql_real_escape_string()
. If you forget in just one place or if you happen to escape only part of the input, your database may be subject to attack.
Using prepared statements in PDO
or mysqli
will make it so that these sorts of programming errors are more difficult to make.
Because (amongst other reasons) it's much harder to ensure the input data is sanitized. If you use parametrized queries, as one does with PDO or mysqli you can entirely avoid the risk.
As an example, some-one could use "enhzflep); drop table users" as a user name. The old functions will allow executing of multiple statements per query, so something like that nasty bugger can delete a whole table.
If one were to use PDO of mysqli, the user-name would end-up being "enhzflep); drop table users"
See bobby-tables.com .
This answer is written to show just how trivial it is to bypass poorly written PHP user-validation code, how (and using what) these attacks work and how to replace the old MySQL functions with a secure prepared statement – and basically, why StackOverflow users (probably with a lot of rep) are barking at new users asking questions to improve their code.
First off, please feel free to create this test mysql database (I have called mine prep):
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)
With that done, we can move to our PHP code.
Lets assume the following script is the verification process for an admin on a website (simplified but working if you copy and use it for testing):
<?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>
Seems legit enough at first glance.
The user has to enter a login and password, right?
Brilliant, not enter in the following:
user: bob pass: somePass
and submit it.
输出如下:
You could not be verified. Please try again...
超! Working as expected, now lets try the actual username and password:
user: Fluffeh pass: mypass
惊人! Hi-fives all round, the code correctly verified an admin. It's perfect!
那么,不是真的。 Lets say the user is a clever little person. Lets say the person is me.
Enter in the following:
user: bob pass: n' or 1=1 or 'm=m
输出是:
The check passed. We have a verified admin!
Congrats, you just allowed me to enter your super-protected admins only section with me entering a false username and a false password. Seriously, if you don't believe me, create the database with the code I provided, and run this PHP code – which at glance REALLY does seem to verify the username and password rather nicely.
So, in answer, THAT IS WHY YOU ARE BEING YELLED AT.
So, lets have a look at what went wrong, and why I just got into your super-admin-only-bat-cave. I took a guess and assumed that you weren't being careful with your inputs and simply passed them to the database directly. I constructed the input in a way tht would CHANGE the query that you were actually running. So, what was it supposed to be, and what did it end up being?
select id, userid, pass from users where userid='$user' and pass='$pass'
That's the query, but when we replace the variables with the actual inputs that we used, we get the following:
select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'
See how I constructed my "password" so that it would first close the single quote around the password, then introduce a completely new comparison? Then just for safety, I added another "string" so that the single quote would get closed as expected in the code we originally had.
However, this isn't about folks yelling at you now, this is about showing you how to make your code more secure.
Okay, so what went wrong, and how can we fix it?
This is a classic SQL injection attack. One of the simplest for that matter. On the scale of attack vectors, this is a toddler attacking a tank – and winning.
So, how do we protect your sacred admin section and make it nice and secure? The first thing to do will be to stop using those really old and deprecated mysql_*
functions. I know, you followed a tutorial you found online and it works, but it's old, it's outdated and in the space of a few minutes, I have just broken past it without so much as breaking a sweat.
Now, you have the better options of using mysqli_ or PDO . I am personally a big fan of PDO, so I will be using PDO in the rest of this answer. There are pro's and con's, but personally I find that the pro's far outweigh the con's. It's portable across multiple database engines – whether you are using MySQL or Oracle or just about bloody anything – just by changing the connection string, it has all the fancy features we want to use and it is nice and clean. I like clean.
Now, lets have a look at that code again, this time written using a PDO object:
<?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>
The major differences are that there are no more mysql_*
functions. It's all done via a PDO object, secondly, it is using a prepared statement. Now, what's a prepred statement you ask? It's a way to tell the database ahead of running a query, what the query is that we are going to run. In this case, we tell the database: "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.".
Then, in the execute statement, we pass the database an array with all the variables that it now expects.
The results are fantastic. Lets try those username and password combinations from before again:
user: bob pass: somePass
User wasn't verified. 真棒。
怎么样:
user: Fluffeh pass: mypass
Oh, I just got a little excited, it worked: The check passed. We have a verified admin!
Now, lets try the data that a clever chap would enter to try to get past our little verification system:
user: bob pass: n' or 1=1 or 'm=m
This time, we get the following:
You could not be verified. Please try again...
This is why you are being yelled at when posting questions – it's because people can see that your code can be bypassed wihout even trying. Please, do use this question and answer to improve your code, to make it more secure and to use functions that are current.
Lastly, this isn't to say that this is PERFECT code. There are many more things that you could do to improve it, use hashed passwords for example, ensure that when you store sensetive information in the database, you don't store it in plain text, have multiple levels of verification – but really, if you just change your old injection prone code to this, you will be WELL along the way to writing good code – and the fact that you have gotten this far and are still reading gives me a sense of hope that you will not only implement this type of code when writing your websites and applications, but that you might go out and research those other things I just mentioned – and more. Write the best code you can, not the most basic code that barely functions.
The MySQL extension is the oldest of the three and was the original way that developers used to communicate with MySQL. This extension is now being deprecated in favor of the other two alternatives because of improvements made in newer releases of both PHP and MySQL.
-
MySQLi is the 'improved' extension for working with MySQL databases. It takes advantage of features that are available in newer versions of the MySQL server, exposes both a function-oriented and an object-oriented interface to the developer and a does few other nifty things.
-
PDO offers an API that consolidates most of the functionality that was previously spread across the major database access extensions, ie MySQL, PostgreSQL, SQLite, MSSQL, etc. The interface exposes high-level objects for the programmer to work with database connections, queries and result sets, and low-level drivers perform communication and resource handling with the database server. A lot of discussion and work is going into PDO and it's considered the appropriate method of working with databases in modern, professional code.
I find the above answers really lengthy, so to summarize:
The mysqli extension has a number of benefits, the key enhancements over the mysql extension being:
- Object-oriented interface
- Support for Prepared Statements
- Support for Multiple Statements
- Support for Transactions
- Enhanced debugging capabilities
- Embedded server support
Source: MySQLi overview
As explained in the above answeres, the alternatives to mysql are mysqli and PDO (PHP Data Objects).
- API supports server-side Prepared Statements : Supported by MYSQLi and PDO
- API supports client-side Prepared Statements : Supported only by PDO
- API supports Stored Procedures : Both MySQLi and PDO
- API supports Multiple Statements and all MySQL 4.1+ functionality – Supported by MySQLi and mostly also by PDO
Both MySQLi and PDO where introduced in PHP 5.0, whereas MySQL was introduced prior to PHP3.0. A point to note is that MySQL is included in PHP5.x though deprecated in later versions.
It's possible to define almost all mysql_* functions using mysqli or pdo. Just include them on top of your old PHP application, and it will works on PHP7. My solution here .
<?php define('MYSQL_LINK', 'dbl'); $GLOBALS[MYSQL_LINK] = null; function mysql_link($link=null) { return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link; } function mysql_connect($host, $user, $pass) { $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass); return $GLOBALS[MYSQL_LINK]; } function mysql_pconnect($host, $user, $pass) { return mysql_connect($host, $user, $pass); } function mysql_select_db($db, $link=null) { $link = mysql_link($link); return mysqli_select_db($link, $db); } function mysql_close($link=null) { $link = mysql_link($link); return mysqli_close($link); } function mysql_error($link=null) { $link = mysql_link($link); return mysqli_error($link); } function mysql_errno($link=null) { $link = mysql_link($link); return mysqli_errno($link); } function mysql_ping($link=null) { $link = mysql_link($link); return mysqli_ping($link); } function mysql_stat($link=null) { $link = mysql_link($link); return mysqli_stat($link); } function mysql_affected_rows($link=null) { $link = mysql_link($link); return mysqli_affected_rows($link); } function mysql_client_encoding($link=null) { $link = mysql_link($link); return mysqli_character_set_name($link); } function mysql_thread_id($link=null) { $link = mysql_link($link); return mysqli_thread_id($link); } function mysql_escape_string($string) { return mysql_real_escape_string($string); } function mysql_real_escape_string($string, $link=null) { $link = mysql_link($link); return mysqli_real_escape_string($link, $string); } function mysql_query($sql, $link=null) { $link = mysql_link($link); return mysqli_query($link, $sql); } function mysql_unbuffered_query($sql, $link=null) { $link = mysql_link($link); return mysqli_query($link, $sql, MYSQLI_USE_RESULT); } function mysql_set_charset($charset, $link=null){ $link = mysql_link($link); return mysqli_set_charset($link, $charset); } function mysql_get_host_info($link=null) { $link = mysql_link($link); return mysqli_get_host_info($link); } function mysql_get_proto_info($link=null) { $link = mysql_link($link); return mysqli_get_proto_info($link); } function mysql_get_server_info($link=null) { $link = mysql_link($link); return mysqli_get_server_info($link); } function mysql_info($link=null) { $link = mysql_link($link); return mysqli_info($link); } function mysql_get_client_info() { $link = mysql_link(); return mysqli_get_client_info($link); } function mysql_create_db($db, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); return mysqli_query($link, "CREATE DATABASE `$db`"); } function mysql_drop_db($db, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); return mysqli_query($link, "DROP DATABASE `$db`"); } function mysql_list_dbs($link=null) { $link = mysql_link($link); return mysqli_query($link, "SHOW DATABASES"); } function mysql_list_fields($db, $table, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); $table = str_replace('`', '', mysqli_real_escape_string($link, $table)); return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`"); } function mysql_list_tables($db, $link=null) { $link = mysql_link($link); $db = str_replace('`', '', mysqli_real_escape_string($link, $db)); return mysqli_query($link, "SHOW TABLES FROM `$db`"); } function mysql_db_query($db, $sql, $link=null) { $link = mysql_link($link); mysqli_select_db($link, $db); return mysqli_query($link, $sql); } function mysql_fetch_row($qlink) { return mysqli_fetch_row($qlink); } function mysql_fetch_assoc($qlink) { return mysqli_fetch_assoc($qlink); } function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) { return mysqli_fetch_array($qlink, $result); } function mysql_fetch_lengths($qlink) { return mysqli_fetch_lengths($qlink); } function mysql_insert_id($qlink) { return mysqli_insert_id($qlink); } function mysql_num_rows($qlink) { return mysqli_num_rows($qlink); } function mysql_num_fields($qlink) { return mysqli_num_fields($qlink); } function mysql_data_seek($qlink, $row) { return mysqli_data_seek($qlink, $row); } function mysql_field_seek($qlink, $offset) { return mysqli_field_seek($qlink, $offset); } function mysql_fetch_object($qlink, $class="stdClass", array $params=null) { return ($params === null) ? mysqli_fetch_object($qlink, $class) : mysqli_fetch_object($qlink, $class, $params); } function mysql_db_name($qlink, $row, $field='Database') { mysqli_data_seek($qlink, $row); $db = mysqli_fetch_assoc($qlink); return $db[$field]; } function mysql_fetch_field($qlink, $offset=null) { if ($offset !== null) mysqli_field_seek($qlink, $offset); return mysqli_fetch_field($qlink); } function mysql_result($qlink, $offset, $field=0) { if ($offset !== null) mysqli_field_seek($qlink, $offset); $row = mysqli_fetch_array($qlink); return (!is_array($row) || !isset($row[$field])) ? false : $row[$field]; } function mysql_field_len($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset); return is_object($field) ? $field->length : false; } function mysql_field_name($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset); if (!is_object($field)) return false; return empty($field->orgname) ? $field->name : $field->orgname; } function mysql_field_table($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset); if (!is_object($field)) return false; return empty($field->orgtable) ? $field->table : $field->orgtable; } function mysql_field_type($qlink, $offset) { $field = mysqli_fetch_field_direct($qlink, $offset); return is_object($field) ? $field->type : false; } function mysql_free_result($qlink) { try { mysqli_free_result($qlink); } catch (Exception $e) { return false; } return true; }
mysql_* functions were depreciated (as of php 5.5 ) given the fact that better functions and code structures were developed. The fact that the function was depreciated means that no more effort will be placed into improving it in terms of performance and security, which means it is less future proof .
If you need more reasons:
- mysql_* functions do not support prepared statements.
- mysql_* functions do not support the binding of parameters.
- mysql_* functions lack functionality for Object Oriented Programming.
- the list goes on …