协慌网

登录 贡献 社区

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

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

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

如果他们不在我的网站上工作,为什么我会收到错误

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

答案

MySQL 扩展:

  • 不是在积极发展
  • 自 PHP 5.5(2013 年 6 月发布)起正式弃用
  • 完全删除 PHP 7.0(2015 年 12 月发布)
    • 这意味着自2018 年 12 月 31 日起 ,它将不存在于任何受支持的 PHP 版本中。目前,它只获得安全更新。
  • 缺少 OO 界面
  • 不支持:
    • 非阻塞,异步查询
    • 准备语句或参数化查询
    • 存储过程
    • 多个陈述
    • 交易
    • “新” 密码验证方法(MySQL 5.6 中默认启用; 5.7 中要求)
    • MySQL 5.1 中的所有功能

由于它已被弃用,因此使用它会使您的代码不再适用于未来。

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

请参阅SQL 扩展的比较

PHP 提供了三种不同的 API 来连接 MySQL。这些是mysql (从 PHP 7 中删除), mysqliPDO扩展。

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_*函数的原因

  • 没有积极的发展
  • 从 PHP 7 开始删除
  • 缺少 OO 界面
  • 不支持非阻塞的异步查询
  • 不支持预准备语句或参数化查询
  • 不支持存储过程
  • 不支持多个语句
  • 不支持交易
  • 不支持 MySQL 5.1 中的所有功能

以上点引用了昆汀的回答

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

请参阅SQL 扩展比较


抑制弃用警告

当代码被转换为MySQLi / PDOE_DEPRECATED错误能够通过设定被抑制error_reportingphp.ini排除E_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

请注意,这也会隐藏其他弃用警告 ,但这些警告可能适用于 MySQL 以外的其他事项。 ( 来自 PHP 手册

文章PDO vs. MySQLi:你应该使用哪个?Dejan Marjanovic帮助您选择。

更好的方法是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 :您需要做的就是创建一个新的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驱动程序中启用的准备仿真,但应该关闭准备仿真以安全地使用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并只读取错误日志来隐藏生产中的危险错误消息。

现在,在阅读了上述所有内容后,您可能会想:当我只想开始使用简单的SELECTINSERTUPDATEDELETE语句时,到底是什么?别担心,我们走了:


选择数据

PDO选择图像

所以你在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() 。您还可以使用:

现在我来获取模式:

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

插入和更新或删除语句

插入和更新PDO图像

我们在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 ):

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

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. DBMS 在语句模板上解析,编译和执行查询优化,并存储结果而不执行它。

  3. 执行 :稍后,应用程序提供(或绑定)参数的值,DBMS 执行该语句(可能返回结果)。应用程序可以使用不同的值多次执行语句。在此示例中,它可能为第一个参数提供 “Bread”,为第二个参数提供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()方法的数组中元素的正确顺序。


SELECTINSERTUPDATEDELETE准备好的查询

  1. 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);
  2. INSERT

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

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
  4. 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_*函数 。它们不再维护, 并且已被正式弃用 。看到红色的盒子 ?转而学习预备语句 ,并使用PDOMySQLi - 本文将帮助您确定哪些。如果您选择 PDO, 这是一个很好的教程

让我们逐句逐句解释:

  • 它们不再维护,并且已被正式弃用

    这意味着 PHP 社区正在逐渐放弃对这些非常旧的功能的支持。它们很可能不存在于 PHP 的未来(最新)版本中!继续使用这些函数可能会破坏(不是那么)远期的代码。

    新! - 从 PHP 5.5 开始, ext / mysql 现已正式弃用!

    较新的! 在 PHP 7 中删除了 ext / mysql。

  • 相反,你应该学习准备好的陈述

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

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

  • 看红盒子?

    当你进入任何mysql函数手册页时,你会看到一个红色框,解释它不应再被使用了。

  • 使用 PDO 或 MySQLi

    有更好,更强大和精心构建的替代品, PDO - PHP 数据库对象 ,提供完整的 OOP 方法进行数据库交互,以及MySQLi ,这是 MySQL 特定的改进。