提问者:小点点

为什么我不应该在PHP中使用mysql_*函数呢?


不应该使用mysql_*函数的技术原因是什么? (例如mysql_query()mysql_connect()mysql_real_escape_string())?

为什么我要使用其他东西,即使它们在我的网站上工作?

如果它们在我的站点上不起作用,为什么我会出现这样的错误

警告:mysql_connect():没有这样的文件或目录


共3个答案

匿名用户

MySQL扩展:

  • 未处于活动开发状态
  • 从PHP 5.5(2013年6月发布)开始,
  • 正式被弃用。
  • 在PHP 7.0(2015年12月发布)中,
  • 已被完全删除
    • 这意味着,截至2018年12月31日,它不存在于任何支持的PHP版本中。 如果您使用的是支持它的PHP版本,那么您使用的是一个没有解决安全问题的版本。
    • 非阻塞异步查询
    • 准备好的语句或参数化查询
    • 存储过程
    • 多语句
    • 事务
    • “new”密码身份验证方法(在MySQL 5.6中默认为打开;在5.7中需要)
    • MySQL 5.1或更高版本中的任何新功能

    由于它已被弃用,因此使用它可以减少代码的未来证明。

    缺少对准备好的语句的支持尤为重要,因为与使用单独的函数调用手动转义外部数据相比,它们提供了更清晰,更不易出错的转义和引用外部数据的方法。

    请参阅SQL扩展的比较。

匿名用户

PHP提供了三种不同的API来连接MySQL。 它们是mysql(从PHP 7开始删除),mysqlipdo扩展。

mysql_*函数曾经非常流行,但现在不再鼓励使用它们。 文档团队正在讨论数据库安全情况,教育用户远离常用的ext/mySQL扩展是其中的一部分(请检查php.internals:deprecating ext/mySQL)。

后来的PHP开发人员团队决定在用户通过mysql_connect()mysql_pconnect()ext/MySQL内置的隐式连接功能连接到MySQL时生成e_deprecated错误。

ext/mysql从PHP 5.5开始被正式弃用,从PHP 7开始被删除。

看到那个红盒子了吗?

当您进入任何mysql_*函数手册页时,您会看到一个红色框,说明不应该再使用它。

远离ext/MySQL不仅关系到安全性,而且关系到访问MySQL数据库的所有功能。

ext/MySQL是为MySQL3.23构建的,从那时起只增加了很少的内容,但基本上保持了与旧版本的兼容性,这使得代码更难维护。 ext/mysql不支持的缺失特性包括:(来自PHP手册)。

  • 存储过程(无法处理多个结果集)
  • 准备好的声明
  • 加密(SSL)
  • 压缩
  • 完整字符集支持

不使用mysql_*函数的原因:

  • 未处于活动开发中
  • 从PHP 7开始删除
  • 缺少OO接口
  • 不支持非阻塞异步查询
  • 不支持准备好的语句或参数化查询
  • 不支持存储过程
  • 不支持多语句
  • 不支持事务
  • 不支持MySQL 5.1中的所有功能

以上观点引自昆汀的回答

缺少对准备好的语句的支持尤为重要,因为它们提供了一种更清晰,更不容易出错的方法来转义和引用外部数据,而不是通过单独的函数调用手动转义它。

请参阅SQL扩展的比较。

取消弃用警告

将代码转换为mysqli/pdo时,可以通过在php.ini中设置error_reporting以排除e_deprecated错误来抑制e_deprecated:

error_reporting = E_ALL ^ E_DEPRECATED

请注意,这还将隐藏其他不推荐使用的警告,然而,这些警告可能是针对MySQL以外的东西。 (来自PHP手册)

文章PDO vs.Mysqli:您应该使用哪个? 德扬·马里亚诺维奇会帮你选择。

而且一个更好的方法是PDO,我现在正在编写一个简单的PDO教程。

a.“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(数据源名称)和可选的usernamepassword

这里我想大家除了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驱动程序中默认启用的prepare emulation,但是应该关闭prepare emulation才能安全地使用PDO

稍后我将解释为什么要关闭准备仿真。 要找到原因,请查看此帖。

只有在使用旧版本的MySQL时才可用,我不推荐使用旧版本的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));

或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中异常的更多信息,请参见异常。 它的行为非常类似或die(mysql_error());,当它没有被捕获时。 但与或die()不同的是,如果您选择这样做,可以捕获和处理PDOException

读得好:

  • 错误和错误处理<
  • 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.
}

另外,您可以通过或die()处理,或者我们可以说像mysql_*,但是它会非常不同。 您可以通过关闭display_errorsof并只读取错误日志来隐藏生产中的危险错误消息。

现在,在阅读了上述所有内容之后,您可能会想:当我只想开始学习简单的selectinsertupdatedelete语句时,到底是怎么回事? 别担心,我们开始了:

所以您在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将行作为关联数组返回,字段名作为键。 还有许多其他的获取模式,我将一一解释。

首先,我解释如何选择fetch模式:

 $stmt->fetch(PDO::FETCH_ASSOC)

在上面的内容中,我一直使用fetch()。 您还可以使用:

  • PDOStatement::FetchAll()-返回包含所有结果集行的数组
  • PDOStatement::FetchColumn()-从结果集的下一行返回单个列
  • PDOStatement::FetchObject()-获取下一行并将其作为对象返回。
  • PDOStatement::SetFetchMode()-设置此语句的默认提取模式

现在我进入获取模式:

  • pdo::fetch_assoc:返回按结果集中返回的列名索引的数组
  • pdo::fetch_both(默认值):返回一个由结果集中返回的列名和0索引列号编制索引的数组

还有更多的选择! 请参阅PDOStatement提取文档中的所有内容。

获取行计数:

您可以不使用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语句并返回受影响的行数。

插入和删除将在后面介绍。

上述方法仅在查询中不使用variable时才有用。 但是,当您需要在查询中使用变量时,千万不要像上面那样尝试,对于准备好的语句或参数化语句就是这样。

Q.什么是准备好的语句以及为什么需要它们?
a.准备好的语句是一种预编译的SQL语句,只需将数据发送到服务器就可以多次执行。

使用准备好的语句的典型工作流程如下(引自维基百科three 3point):

>

  • 准备:语句模板由应用程序创建并发送到数据库管理系统(DBMS)。 某些值未指定,称为参数,占位符或绑定变量(下面标记为):

    插入产品(名称,价格)值(?,?)

    DBMS对语句模板进行解析,编译和查询优化,并在不执行结果的情况下存储结果。

    您可以通过在SQL中包含占位符来使用准备好的语句。 基本上有三个没有占位符的(不要用变量its上面的一个尝试这样做),一个有未命名的占位符,一个有命名的占位符。

    Q.那么现在,什么是命名占位符以及我如何使用它们?
    A.命名占位符。 使用冒号前面的描述性名称,而不是问号。 我们不关心名称占位符中值的位置/顺序:

     $stmt->bindParam(':bla', $bla);
    

    bindparam(参数,变量,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朋友来说,另一个很好的特性是命名占位符能够将对象直接插入到数据库中,前提是属性与命名字段匹配。 例如:

    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);
    

    Q.那么现在,什么是未命名占位符,我如何使用它们?
    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()方法的数组中元素的正确顺序。

    >

  • 选择:

    $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);
    

    插入:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    

    删除:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
    

    更新:

    $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版本中! 继续使用这些函数可能会在(不那么远的)将来破坏您的代码。

    新的! -ext/mysql从PHP 5.5开始正式被弃用!

    相反,您应该学习准备好的语句

    mysql_*扩展不支持准备好的语句,这(除其他外)是针对SQL注入的非常有效的对策。 它修复了依赖MySQL的应用程序中的一个非常严重的漏洞,该漏洞允许攻击者访问您的脚本并对数据库执行任何可能的查询。

    有关更多信息,请参见如何在PHP中防止SQL注入?

    看到那个红盒子了吗?

    当您转到任何MySQL函数手册页时,您会看到一个红色框,说明它不应该再使用了。

    使用PDO或MySQLi

    还有更好的,更健壮的和构建良好的替代方案:PDO-PHP Database Object,它为数据库交互提供了完整的OOP方法;MySQLi,它是MySQL特有的改进。