为什么不应该使用mysql_*
函数的技术原因是什么? (例如mysql_query()
, mysql_connect()
或mysql_real_escape_string()
)?
即使他们在我的网站上工作,为什么还要使用别的东西?
如果他们不在我的网站上工作,为什么我会收到错误
警告:mysql_connect():没有这样的文件或目录
MySQL 扩展:
由于它已被弃用,因此使用它会使您的代码不再适用于未来。
缺乏对预准备语句的支持尤其重要,因为它们提供了一种更清晰,更不易出错的转义和引用外部数据的方法,而不是通过单独的函数调用手动转义它。
请参阅SQL 扩展的比较 。
PHP 提供了三种不同的 API 来连接 MySQL。这些是mysql
(从 PHP 7 中删除), mysqli
和PDO
扩展。
mysql_*
函数曾经很受欢迎,但不再鼓励它们的使用。文档团队正在讨论数据库安全情况,并教育用户远离常用的 ext / mysql 扩展是其中的一部分(检查php.internals:弃用 ext / mysql )。
当用户连接到 MySQL 时,后来的 PHP 开发团队已经决定生成E_DEPRECATED
错误,无论是通过mysql_connect()
, mysql_pconnect()
还是内置到ext/mysql
的隐式连接功能。
从 PHP 5.5 开始 , ext/mysql
已被正式弃用 ,自 PHP 7起已被删除 。
看红盒子?
当你进入任何mysql_*
函数手册页时,你会看到一个红色框,说明它不应再被使用了。
远离ext/mysql
不仅涉及安全性,还涉及访问 MySQL 数据库的所有功能。
ext/mysql
是为MySQL 3.23构建的,从那时起只添加了很少的内容,同时大部分都保持与旧版本的兼容性,这使代码更难维护。缺少ext/mysql
不支持的功能包括:( 来自 PHP 手册 )。
不使用mysql_*
函数的原因 :
缺乏对预准备语句的支持特别重要,因为它们提供了一种更清晰,更不容易出错的方法来转义和引用外部数据,而不是通过单独的函数调用来手动转义它。
抑制弃用警告
当代码被转换为MySQLi
/ PDO
, E_DEPRECATED
错误能够通过设定被抑制error_reporting
在php.ini排除E_DEPRECATED:
error_reporting = E_ALL ^ E_DEPRECATED
请注意,这也会隐藏其他弃用警告 ,但这些警告可能适用于 MySQL 以外的其他事项。 ( 来自 PHP 手册 )
文章PDO vs. MySQLi:你应该使用哪个?由Dejan Marjanovic帮助您选择。
更好的方法是PDO
,我现在正在编写一个简单的PDO
教程。
答 :“ PDO - PHP 数据对象 - 是一个数据库访问层,提供访问多个数据库的统一方法。”
使用mysql_*
函数或者我们可以用旧的方式说(在 PHP 5.5 及以上版本中弃用)
$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);
使用PDO
:您需要做的就是创建一个新的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
。
如果有任何连接错误,它将抛出一个PDOException
对象,可以捕获该对象以进一步处理Exception
。
好读 : 连接和连接管理 ¶
您还可以将多个驱动程序选项作为数组传递给第四个参数。我建议传递将PDO
置于异常模式的参数。由于某些PDO
驱动程序不支持本机预处理语句,因此PDO
会执行准备模拟。它还允许您手动启用此仿真。要使用本机服务器端预处理语句,应明确将其设置为false
。
另一种方法是关闭默认情况下在MySQL
驱动程序中启用的准备仿真,但应该关闭准备仿真以安全地使用PDO
。
我稍后会解释为什么应该关闭准备仿真。要查找原因,请查看此帖子 。
只有在使用我不推荐的旧版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 构建后设置属性吗?
是的 ,我们还可以使用setAttribute
方法在 PDO 构造之后设置一些属性:
$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);
PDO
错误处理比mysql_*
容易得多。
使用mysql_*
时的常见做法是:
//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));
OR die()
不是处理错误的好方法,因为我们无法处理die
的事情。它会突然结束脚本,然后将错误回显到您通常不希望向最终用户显示的屏幕,并让血腥的黑客发现您的架构。或者, mysql_*
函数的返回值通常可以与mysql_error()一起使用来处理错误。
PDO
提供了更好的解决方案:例外。我们用PDO
做的任何事都应该包含在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
。 好读 :
喜欢:
$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
- catch
。你可以在适当的时候捕捉它,但我强烈建议你使用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_*
,但它会真的变化。您可以通过关闭display_errors off
并只读取错误日志来隐藏生产中的危险错误消息。
现在,在阅读了上述所有内容后,您可能会想:当我只想开始使用简单的SELECT
, INSERT
, UPDATE
或DELETE
语句时,到底是什么?别担心,我们走了:
所以你在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 Data 中,它是通过->fetch()
,这是一个语句句柄的方法。在调用 fetch 之前,最好的方法是告诉 PDO 你想要获取数据的方式。在下面的部分我将解释这一点。
注意在上面的fetch()
和fetchAll()
代码中使用PDO::FETCH_ASSOC
。这告诉PDO
将行作为关联数组返回,并将字段名称作为键。还有许多其他的获取模式,我将逐一解释。
首先,我解释如何选择获取模式:
$stmt->fetch(PDO::FETCH_ASSOC)
在上面,我一直在使用fetch()
。您还可以使用:
PDOStatement::fetchAll()
- 返回包含所有结果集行的数组PDOStatement::fetchColumn()
- 从结果集的下一行返回单个列PDOStatement::fetchObject()
- 获取下一行并将其作为对象返回。 PDOStatement::setFetchMode()
- 设置此语句的默认提取模式现在我来获取模式:
PDO::FETCH_ASSOC
:返回由结果集中返回的列名索引的数组PDO::FETCH_BOTH
(默认值):返回由结果集中返回的列名和 0 索引列号索引的数组还有更多的选择!在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 语句并返回受影响的行数。
稍后将介绍插入和删除。
只有在查询中不使用变量时,上述方法才有用。但是当你需要在查询中使用变量时,不要尝试像上面那样用于预处理语句或参数化语句 。
问:什么是准备好的声明,为什么需要它们?
A.预准备语句是预编译的 SQL 语句,可以通过仅将数据发送到服务器来多次执行。
使用预准备语句的典型工作流程如下( 引自维基百科三点 3 ):
准备 :语句模板由应用程序创建并发送到数据库管理系统(DBMS)。某些值未指定,称为参数,占位符或绑定变量(标记为?
下方):
INSERT INTO PRODUCT (name, price) VALUES (?, ?)
DBMS 在语句模板上解析,编译和执行查询优化,并存储结果而不执行它。
1.00
。 您可以通过在 SQL 中包含占位符来使用预准备语句。基本上有三个没有占位符(不要尝试使用上面的变量),一个带有未命名的占位符,另一个带有命名的占位符。
问:那么现在,什么是命名占位符以及如何使用它们?
A.命名占位符。使用以冒号开头的描述性名称,而不是问号。我们不关心名称持有人的价值位置 / 顺序:
$stmt->bindParam(':bla', $bla);
bindParam(parameter,variable,data_type,length,driver_options)
您也可以使用执行数组进行绑定:
<?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
朋友的另一个不错的功能是命名占位符能够将对象直接插入到数据库中,假设属性与命名字段匹配。例如:
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'));
在上面,你可以看到那些?
而不是像名字持有人那样的名字。现在在第一个例子中,我们将变量分配给各个占位符( $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 - 本文将帮助您确定哪些。如果您选择 PDO, 这是一个很好的教程 。
让我们逐句逐句解释:
它们不再维护,并且已被正式弃用
这意味着 PHP 社区正在逐渐放弃对这些非常旧的功能的支持。它们很可能不存在于 PHP 的未来(最新)版本中!继续使用这些函数可能会破坏(不是那么)远期的代码。
新! - 从 PHP 5.5 开始, ext / mysql 现已正式弃用!
相反,你应该学习准备好的陈述
mysql_*
扩展不支持预处理语句 ,这是(除其他外)一个非常有效的SQL 注入对策。它修复了 MySQL 依赖应用程序中的一个非常严重的漏洞,它允许攻击者访问您的脚本并对您的数据库执行任何可能的查询 。
有关更多信息,请参阅如何在 PHP 中阻止 SQL 注入?
看红盒子?
当你进入任何mysql
函数手册页时,你会看到一个红色框,解释它不应再被使用了。
使用 PDO 或 MySQLi
有更好,更强大和精心构建的替代品, PDO - PHP 数据库对象 ,提供完整的 OOP 方法进行数据库交互,以及MySQLi ,这是 MySQL 特定的改进。