【问题标题】:Why shouldn't I use mysql_* functions in PHP?为什么我不应该在 PHP 中使用 mysql_* 函数?
【发布时间】:2022-05-16 05:09:44
【问题描述】:

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

为什么我应该使用其他东西,即使它们在我的网站上有效?

如果他们在我的网站上不起作用,为什么我会收到类似这样的错误

警告:mysql_connect():没有那个文件或目录

【问题讨论】:

  • 错误类似于:致命错误:未捕获错误:调用未定义函数 mysql_connect() ...
  • 仅弃用就足以避免使用它们

标签: php mysql


【解决方案1】:

MySQL扩展:

  • 未在积极开发中
  • 官方deprecated从 PHP 5.5(2013 年 6 月发布)开始。
  • 已经removed完全从 PHP 7.0 开始(2015 年 12 月发布)
    • 这意味着截至31 Dec 2018,它不存在于任何受支持的 PHP 版本中。如果您使用的是支持它的 PHP 版本,则您使用的是未修复安全问题的版本。
  • 缺少 OO 接口
  • 不支持:
    • 非阻塞、异步查询
    • Prepared statements 或参数化查询
    • 存储过程
    • 多个语句
    • 交易
    • “新”密码验证方法(MySQL 5.6 默认启用;5.7 要求)
    • MySQL 5.1 或更高版本中的任何新功能

由于它已被弃用,使用它会使您的代码不那么将来。

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

参见the comparison of SQL extensions

【讨论】:

  • 仅弃用就足以避免使用它们。他们哪天不在,靠着他们也不会幸福。其余的只是使用旧扩展阻止人们学习的事情列表。
  • 弃用并不是每个人都认为的灵丹妙药。 PHP 本身不会有一天出现,但我们依赖于我们今天可以使用的工具。当我们必须更换工具时,我们会的。
  • @LightnessRacesinOrbit — 弃用不是灵丹妙药,它是一面旗帜,上面写着“我们认识到这很糟糕,所以我们不会支持它太久”。虽然拥有更好的未来代码验证是摆脱弃用功能的一个很好的理由,但它并不是唯一的(甚至不是主要的)。改变工具是因为有更好的工具,而不是因为你被迫这样做。 (在你被迫之前改变工具意味着你不是在学习新工具只是因为你的代码已经停止工作并且昨天需要修复......这是学习新工具的最糟糕时间)。
  • 准备好的陈述对我来说很重要。许多 PHP 早期被认为是一种被诅咒的安全语言的早期声誉源于早期组合魔法变量和 SQL 通过插值结合起来产生一些非常愚蠢的代码。准备好的语句对防止这种情况有很长的路要走。永远不要插入 SQL。只是……不要这样做。
  • Doesn't support: Non-blocking, asynchronous queries - 这也是不使用 PDO 的原因,它不支持异步查询(与 mysqli 不同)
【解决方案2】:

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

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

而后来的 PHP 开发团队已经决定在用户​​连接 MySQL 时产生E_DEPRECATED错误,无论是通过mysql_connect()mysql_pconnect()还是ext/mysql内置的隐式连接功能。

ext/mysqlofficially deprecated as of PHP 5.5,一直是removed as of PHP 7

看到红框了吗?

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

为什么


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

ext/mysql 是为MySQL 3.23从那以后只增加了很少的内容,同时主要保持与这个旧版本的兼容性,这使得代码更难维护。 ext/mysql 不支持的缺失功能包括:(from PHP manual).

不使用mysql_*函数的原因:

  • 未在积极开发中
  • 从 PHP 7 开始删除
  • 缺少 OO 接口
  • 不支持非阻塞、异步查询
  • 不支持准备好的语句或parameterized queries
  • 不支持存储过程
  • 不支持多语句
  • 不支持transactions
  • 不支持 MySQL 5.1 中的所有功能

Above point quoted from Quentin's answer

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

请参阅comparison of SQL extensions


抑制弃用警告

当代码被转换为MySQLi/PDO时,E_DEPRECATED错误可以通过设置error_reporting来抑制文件排除E_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

请注意,这也会隐藏其他弃用警告,但是,它可能用于 MySQL 以外的东西。 (from PHP manual)

文章PDO vs. MySQLi: Which Should You Use?by 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

好读: Connections and Connection management ¶

您还可以将几个驱动程序选项作为数组传递给第四个参数。我建议传递将 PDO 置于异常模式的参数。因为某些PDO 驱动程序不支持本地准备语句,所以PDO 执行准备模拟。它还允许您手动启用此仿真。要使用本机服务器端准备好的语句,您应该显式设置它false

另一种是关闭默认在MySQL驱动程序中启用的prepare emulation,但应该关闭prepare emulation才能安全使用PDO

我稍后会解释为什么要关闭准备仿真。要查找原因,请查看this post

它仅在您使用我不推荐的旧版本 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构建后可以设置属性吗?

是的,我们也可以在 PDO 构造之后使用 setAttribute 方法设置一些属性:

$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语句?别担心,我们开始:


选择数据

所以你在 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 数据中,它是通过 -&gt;fetch() 获取的,这是您的语句句柄的一种方法。在调用 fetch 之前,最好的方法是告诉 PDO 您希望如何获取数据。在下面的部分中,我将对此进行解释。

获取模式

请注意在上面的fetch()fetchAll() 代码中使用PDO::FETCH_ASSOC。这告诉PDO 将行作为关联数组返回,字段名称作为键。还有很多其他的fetch方式,我会一一解释。

首先,我解释一下如何选择抓取模式:

 $stmt->fetch(PDO::FETCH_ASSOC)

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

现在我来获取模式:

  • PDO::FETCH_ASSOC:返回一个由结果集中返回的列名索引的数组
  • PDO::FETCH_BOTH(默认):返回一个由结果集中返回的列名和 0 索引列号索引的数组

还有更多的选择!在PDOStatement Fetch documentation. 中阅读所有内容。

获取行数:

您可以获取 PDOStatement 并执行 rowCount(),而不是使用 mysql_num_rows 来获取返回的行数,例如:

<?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 语句并返回受影响的行数。

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

上述方法仅在您不在查询中使用变量时才有用。但是当你需要在查询中使用变量时,永远不要像上面那样尝试,prepared statement or parameterized statement 就是这样。


准备好的陈述

问。什么是准备好的陈述,为什么我需要它们?
一种。准备好的语句是预编译的 SQL 语句,可以通过仅将数据发送到服务器来执行多次。

使用准备好的语句的典型工作流程如下(quoted from Wikipedia three 3 point):

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

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

  2. DBMS对语句模板进行解析、编译、查询优化,并存储结果,但不执行。

  3. 执行:稍后,应用程序提供(或绑定)参数值,DBMS 执行语句(可能返回结果)。应用程序可以使用不同的值多次执行该语句。在此示例中,它可能为第一个参数提供“面包”,为第二个参数提供1.00

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

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

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

    问。那么现在,什么是未命名占位符,我该如何使用它们?
    一种。让我们举个例子:

    <?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-&gt;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并不完全安全。检查答案Are PDO prepared statements sufficient to prevent SQL injection?通过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 /*"));
      

【讨论】:

    【解决方案3】:

    首先,让我们从给大家的标准评论说起:

    Please, don't use mysql_* functions in new code。他们不再维护and are officially deprecated。看到red box了吗?改为了解prepared statements,并使用PDOMySQLi - this article将帮助您决定使用哪个。如果你选择 PDO,here is a good tutorial

    让我们逐句过一遍,并解释一下:

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

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

      新的! - ext/mysql 现在是officially deprecated as of PHP 5.5!

      更新!分机/mysqlhas been removed in PHP 7.

    • 相反,您应该了解准备好的陈述

      mysql_*分机不支持准备好的陈述,这是(除其他事项外)一个非常有效的反制措施SQL注入.它修复了依赖 MySQL 的应用程序中的一个非常严重的漏洞,该漏洞允许攻击者访问您的脚本并执行任何可能的查询在你的数据库上。

      有关详细信息,请参阅How can I prevent SQL injection in PHP?

    • 看到红框了吗?

      当您转到任何mysql 功能手册页时,您会看到一个红色框,说明不应再使用它。

    • 使用 PDO 或 MySQLi

      有更好、更健壮和精心打造的替代品,PDO - PHP Database Object,它提供了一个完整的 OOP 数据库交互方法,以及MySQLi,这是 MySQL 的特定改进。

    【讨论】:

    • @Mario——PHP 开发人员确实有一个流程,他们刚刚投票赞成从 5.5 开始正式弃用 ext/mysql。这不再是一个假设性的问题。
    • 使用经过验证的技术(例如 PDO 或 MySQLi)添加几行额外代码,仍然可以提供 PHP 一贯提供的易用性。为了开发人员,我希望他/她知道在任何教程中看到这些可怕的 mysql_* 函数实际上会减损课程,并且应该告诉 OP 这种代码是 10 年前的 - 并且应该质疑本教程的相关性,太!
    • 答案应该提到什么:准备好的语句取消了对IN (...) construct的任何有意义的使用。
    • 本站点其他地方引用的另一条评论是不要简单地将所有 mysql_ 语句转换为 mysqli_。两者之间存在差异。
    • @Madara's Ghost 我想知道他们为什么不用现代的、更安全的代码重写 mysql_*
    【解决方案4】:

    便于使用

    上面已经提到了分析和综合的原因。对于新手来说,停止使用过时的 mysql_ 函数有更重要的动机。

    当代数据库 API 只是更轻松使用。

    这主要是绑定参数这可以简化代码。使用 excellent tutorials (as seen above) 过渡到 PDO 并不过分艰巨。

    然而,一次重写更大的代码库需要时间。这种中间替代方案存在的理由:

    等效的 pdo_* 函数代替mysql_*

    使用<pdo_mysql.php>,您可以从旧的 mysql_ 函数切换最小的努力.它添加了 pdo_ 函数包装器来替换它们的 mysql_ 对应物。

    1. 在每个必须与数据库交互的调用脚本中只需include_once("pdo_mysql.php");

    2. 去除那个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()
      • 等等...

    3. 您的代码将以相似的方式工作,并且大部分看起来仍然相同:

      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]";
      }
      

      Et voilà。
      你的代码是使用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)
      

      请记住 pdo_* 仍然允许两者任一.
      只是不要转义变量将其绑定在同一个查询中。

      • 占位符功能由其背后的真实 PDO 提供。
      • 因此以后也允许:named占位符列表。

      更重要的是,您可以在任何查询后面安全地传递 $_REQUEST[] 变量。当提交 &lt;form&gt; 字段完全匹配数据库结构时,它甚至更短:

      pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);
      

      如此简单。但是让我们回到一些更多的重写建议和技术原因上,为什么你可能想要摆脱mysql_和逃跑。

      修复或删除任何旧学校 sanitize() 功能

      一旦你全部转换mysql_使用绑定参数调用pdo_query,删除所有多余的pdo_real_escape_string调用。

      特别是,您应该修复任何 sanitizecleanfilterThisclean_data 过时教程以一种或另一种形式宣传的功能:

      function sanitize($str) {
         return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
      }
      

      这里最明显的错误是缺乏文档。更重要的是,过滤顺序完全错误。

      • 正确的顺序应该是:stripslashes 作为最内层的调用,然后是 trim,然后是 strip_tagshtmlentities 作为输出上下文,最后才是 _escape_string,因为它的应用程序应该直接在 SQL interparsing 之前。

      • 但作为第一步摆脱_real_escape_string称呼。

      • 如果您的数据库和应用程序流需要 HTML 上下文安全字符串,您可能必须暂时保留 sanitize() 函数的其余部分。添加一条注释,说明此后它仅适用于 HTML 转义。

      • 字符串/值处理委托给 PDO 及其参数化语句。

      • 如果在您的清理功能中提到stripslashes(),则可能表示更高级别的监督。

        关于 magic_quotes 的历史记录。该功能已被正确弃用。它经常被错误地描述为失败安全但是功能。但是 magic_quotes 是一个失败的安全功能,就像网球作为营养来源一样失败。那根本不是他们的目的。

        PHP2/FI 中的原始实现仅通过 "引号将自动转义,从而更容易将表单数据直接传递给 sql 查询"。值得注意的是,与mSQL一起使用是意外安全的,因为它仅支持 ASCII。
        然后 PHP3/Zend 为 MySQL 重新引入了 magic_quotes 并错误地记录了它。但最初它只是一个convenience feature,不是为了安全。

      准备好的陈述有何不同

      当您将字符串变量置入 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() 调用。

      然而,最好从简化开始。例如常见的结果获取:

      $result = pdo_query("SELECT * FROM tbl");
      while ($row = pdo_fetch_assoc($result)) {
      

      可以只替换为 foreach 迭代:

      foreach ($result as $row) {
      

      或者更好的是直接和完整的数组检索:

      $result->fetchAll();
      

      在大多数情况下,您会得到比 PDO 或 mysql_ 通常在查询失败后提供的警告更有用的警告。

      其他选项

      所以这希望可视化一些实际的放弃的理由和有价值的途径mysql_.

      只是切换到 并不能完全解决问题。 pdo_query() 也只是它的前端。

      除非您还引入参数绑定或可以利用更好的 API 中的其他东西,否则这是一个毫无意义的切换。我希望它描述得足够简单,不会进一步打击新人。 (教育通常比禁止更有效。)

      虽然它符合最简单的可能工作类别,但它仍然是非常实验性的代码。我只是在周末写的。然而,有太多的选择。只需在谷歌上搜索 PHP database abstraction 并稍微浏览一下。对于此类任务,过去和将来都会有很多优秀的库。

      如果您想进一步简化数据库交互,Paris/Idiorm 之类的映射器值得一试。就像没有人再在 JavaScript 中使用平淡无奇的 DOM 一样,如今您不必照看原始数据库接口。

    【讨论】:

    • 小心 pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST); 函数 - 即:pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' =&gt; 'lawl', 'password' =&gt; '123', 'is_admin' =&gt; 'true');
    • @Tom 当然,虽然维护得不多(0.9.2 是最后一个),但您可以创建一个fossil account,添加到 wiki 或提交一个bug report(无需注册 IIRC)。
    • pdo_real_escape_string() <- 这甚至是一个真正的功能,我找不到它的任何文档吗?请为此发布来源。
    【解决方案5】:

    mysql_ 函数:

    1. 已经过时了——它们不再被维护
    2. 不允许您轻松移动到另一个数据库后端
    3. 不支持准备好的语句,因此
    4. 鼓励程序员使用连接构建查询,导致SQL注入漏洞

    【讨论】:

    • #2 同样适用于mysqli_
    • 公平地说,考虑到 SQL 方言的变化,即使是 PDO 也不会给你 #2 任何程度的确定性。为此,您需要一个合适的 ORM 包装器。
    • mysql_* 函数是用于较新 PHP 版本的 mysqlnd 函数的外壳。所以即使不再维护旧的客户端库,mysqlnd 也会维护 :)
    • 问题是由于过时的 php 版本,没有多少虚拟主机提供商可以支持这种面向对象的设计风格
    • @RajuGujarati 所以找一个可以的网络主机。如果您的网络托管服务商没有这样做,那么他们的服务器很容易受到攻击。
    【解决方案6】:

    说起技术的原因,只有少数,非常具体且很少使用。您很可能永远不会在生活中使用它们。
    也许我太无知了,但我从来没有机会使用它们之类的东西

    • 非阻塞、异步查询
    • 返回多个结果集的存储过程
    • 加密(SSL)
    • 压缩

    如果您需要它们 - 这些无疑是从 mysql 扩展转向更时尚和现代外观的技术原因。

    尽管如此,也有一些非技术问题,这可能会让您的体验更加困难

    • 在现代 PHP 版本中进一步使用这些函数将引发弃用级别的通知。它们可以简单地关闭。
    • 在遥远的将来,它们可能会从默认的 PHP 构建中删除。这也没什么大不了的,因为 mydsql ext 将被移入 PECL,每个托管商都会很乐意用它来编译 PHP,因为他们不想失去网站已经运行了几十年的客户。
    • 来自 Stackoverflow 社区的强烈抵制。每当你提到这些诚实的功能时,你都会被告知它们是严格禁忌的。
    • 作为普通的 PHP 用户,您使用这些函数的想法很可能是错误的。只是因为所有这些无数的教程和手册教你错误的方法。不是功能本身——我必须强调——而是它们的使用方式。

    后一个问题是一个问题。
    但是,在我看来,提议的解决方案也好不到哪里去。
    在我看来过于理想化梦想所有 PHP 用户都能立即学会如何正确处理 SQL 查询。他们很可能只是机械地将 mysql_* 更改为 mysqli_*,保持方法不变.特别是因为 mysqli 使准备好的语句的使用非常痛苦和麻烦。
    更不用说那个本国的准备好的陈述不足以保护来自 SQL 注入,mysqli 和 PDO 都没有提供解决方案。

    因此,与其与这种诚实的扩展作斗争,我更愿意与错误的做法作斗争并以正确的方式教育人们。

    此外,还有一些错误或不重要的原因,例如

    • 不支持存储过程(我们使用 mysql_query("CALL my_proc"); 很长时间了)
    • 不支持交易(同上)
    • 不支持多语句(谁需要它们?)
    • 未在积极开发中(那又怎样?它会影响以任何实际的方式?)
    • 缺少 OO 界面(创建一个需要几个小时)
    • 不支持准备好的语句或参数化查询

    最后一个是一个有趣的点。虽然mysql ext不支持本国的准备好的陈述,它们不是安全所必需的。我们可以使用手动处理的占位符轻松伪造准备好的语句(就像 PDO 一样):

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

    ,一切都是参数化和安全的。

    但是好吧,如果你不喜欢手册中的红框,那么就会出现一个选择问题:mysqli还是PDO?

    那么,答案如下:

    • 如果您了解使用数据库抽象层并寻找一个 API 来创建一个,mysqli是一个很好的选择,因为它确实支持许多 mysql 特定的功能。
    • 如果像绝大多数 PHP 用户一样,您在应用程序代码中直接使用原始 API 调用(这本质上是错误的做法)-PDO是唯一的选择,因为这个扩展假装不仅仅是 API,而是一个半 DAL,仍然不完整,但提供了许多重要的特性,其中两个使 PDO 与 mysqli 截然不同:

      • 不像mysqli,PDO可以绑定占位符按价值,这使得动态构建的查询变得可行,而无需几屏非常混乱的代码。
      • 与 mysqli 不同,PDO 总是可以在一个简单的常用数组中返回查询结果,而 mysqli 只能在 mysqlnd 安装上执行此操作。

    因此,如果您是普通的 PHP 用户,并且希望在使用本机准备好的语句时为自己省去很多麻烦,那么 PDO - 再次 - 是唯一的选择。
    然而,PDO 也不是灵丹妙药,也有它的困难。
    所以,我在 PDO tag wiki 中为所有常见的陷阱和复杂的案例编写了解决方案。

    然而,每个谈论扩展的人总是忽略了2 重要事实关于 Mysqli 和 PDO:

    1. 准备好的声明不是灵丹妙药.有些动态标识符无法使用准备好的语句进行绑定。存在参数数量未知的动态查询,这使得查询构建成为一项艰巨的任务。

    2. mysqli_* 和 PDO 函数都不应该出现在应用程序代码中。
      应该有一个抽象层在它们和应用程序代码之间,这将在内部完成绑定、循环、错误处理等所有肮脏的工作,使应用程序代码干干净净。特别是对于动态查询构建等复杂情况。

      所以,仅仅切换到 PDO 或 mysqli 是不够的。必须使用 ORM、查询构建器或任何数据库抽象类,而不是在代码中调用原始 API 函数。
      相反 - 如果您的应用程序代码和 mysql API 之间有一个抽象层 -使用哪个引擎实际上并不重要。您可以使用 mysql ext 直到它被弃用,然后轻松地将您的抽象类重写为另一个引擎,使所有应用程序代码完好无损。

      下面是一些基于我的safemysql class 的示例,以展示这样一个抽象类应该是怎样的:

      $city_ids = array(1,2,3);
      $cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);
      

      将这一行与 amount of code you will need with PDO 进行比较。
      然后与 crazy amount of code 进行比较,您将需要使用原始 Mysqli 准备好的语句。 请注意,错误处理、分析、查询日志记录已经内置并正在运行。

      $insert = array('name' => 'John', 'surname' => "O'Hara");
      $db->query("INSERT INTO users SET ?u", $insert);
      

      将它与通常的 PDO 插入进行比较,当每个字段名称重复六到十次时 - 在所有这些众多的命名占位符、绑定和查询定义中。

      另一个例子:

      $data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);
      

      你很难找到 PDO 的例子来处理这种实际情况。
      而且它太冗长而且很可能不安全。

      所以,再一次——你应该关心的不仅仅是原始驱动程序,还有抽象类,它不仅对初学者手册中的愚蠢示例有用,而且对解决任何现实生活中的问题都很有用。

    【讨论】:

    • mysql_* 使得漏洞很容易被发现。由于 PHP 被很多新手用户使用,mysql_* 在实践中是非常有害的,即使理论上它可以顺利使用。
    • everything is parameterized and safe - 它可能被参数化,但你的函数没有使用真实的准备好的陈述。
    • Not under active development 怎么只针对那个虚构的“0.01%”?如果你用这个静止的函数构建一些东西,在一年内更新你的 mysql 版本并以一个无法工作的系统结束,我敢肯定会有很多人突然进入那个“0.01%”。我会说 deprecatednot under active development 密切相关。你可以说它“没有 [值得] 的理由”,但事实是,当在选项之间进行选择时,no active development 几乎和 deprecated 一样糟糕?
    • @MadaraUchiha:你能解释一下漏洞是如何很容易出现的吗?特别是在那些相同的漏洞不影响 PDO 或 MySQLi 的情况下......因为我不知道你所说的一个。
    • @ShaquinTrifonoff:当然,它不使用准备好的语句。但是 neither does PDO,大多数人推荐它而不是 MySQLi。所以我不确定这是否会产生重大影响。上面的代码(多一点解析)是 PDO 在默认情况下准备语句时所做的...
    【解决方案7】:

    原因有很多,但也许最重要的一个是这些函数鼓励不安全的编程实践,因为它们不支持准备好的语句。准备好的语句有助于防止 SQL 注入攻击。

    使用mysql_* 函数时,您必须记住通过mysql_real_escape_string() 运行用户提供的参数。如果您只忘记了一个地方,或者如果您碰巧只转义了部分输入,您的数据库可能会受到攻击。

    PDOmysqli 中使用准备好的语句将使此类编程错误更难发生。

    【讨论】:

    • 不幸的是,MySQLi_* 对传递可变数量参数的支持不佳(例如,当你想传递一个值列表以在 IN 子句中进行检查时)鼓励不使用参数,鼓励使用完全相同的串联查询让 MySQL_* 调用容易受到攻击。
    • 但是,再一次强调,不安全不是 mysql_* 函数的固有问题,而是使用不当的问题。
    • @Agamemnus 问题是 mysql_* 很容易实现“不正确的用法”,尤其是对于没有经验的程序员。实现准备好的语句的库使得这种类型的错误更难发生。
    【解决方案8】:

    因为(除其他原因外)确保输入数据被清理要困难得多。如果您使用参数化查询,就像使用 PDO 或 mysqli 一样,您可以完全避免风险。

    例如,有人可以使用 "enhzflep); drop table users" 作为用户名。旧函数将允许每个查询执行多个语句,所以像那个讨厌的错误可以删除整个表。

    如果要使用 mysqli 的 PDO,用户名最终会变成"enhzflep); drop table users"

    bobby-tables.com

    【讨论】:

    • The old functions will allow executing of multiple statements per query - 不,他们不会。这种注入在 ext/mysql 中是不可能的——这种注入在 PHP 和 MySQL 中唯一可行的方法是使用 MySQLi 和 mysqli_multi_query() 函数。 ext/mysql 和未转义字符串可能实现的注入类似于 ' OR '1' = '1,用于从数据库中提取不可访问的数据。在某些情况下,可以注入子查询,但是仍然不可能以这种方式修改数据库。
    【解决方案9】:

    写这个答案是为了说明绕过写得不好的 PHP 用户验证代码是多么微不足道,这些攻击如何(以及使用什么)工作以及如何用安全的准备好的语句替换旧的 MySQL 函数 - 基本上,为什么 StackOverflow 用户(可能有很多代表)正在向新用户咆哮,提出问题以改进他们的代码。

    首先,请随意创建这个测试 mysql 数据库(我已经调用了我的 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)
    

    完成后,我们可以转到我们的 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>
    

    乍一看似乎足够合法。

    用户必须输入登录名和密码,对吗?

    太棒了,现在输入以下内容:

    user: bob
    pass: somePass
    

    并提交。

    输出如下:

    You could not be verified. Please try again...
    

    极好的!按预期工作,现在让我们尝试实际的用户名和密码:

    user: Fluffeh
    pass: mypass
    

    惊人的!大家击掌,代码正确地验证了管理员。这是完美的!

    好吧,不是真的。可以说用户是一个聪明的小人物。可以说这个人是我。

    输入以下内容:

    user: bob
    pass: n' or 1=1 or 'm=m
    

    输出是:

    The check passed. We have a verified admin!
    

    恭喜,你刚刚允许我进入你的超级保护管理员专用部分,我输入了一个虚假的用户名和一个虚假的密码。说真的,如果您不相信我,请使用我提供的代码创建数据库,然后运行此 PHP 代码 - 乍一看,它确实可以很好地验证用户名和密码。

    所以,作为回答,这就是你被大吼大叫的原因。

    那么,让我们看看出了什么问题,以及为什么我进入了你的超级管理员专用蝙蝠洞。我猜测并假设您对输入不小心,只是将它们直接传递给数据库。我以一种会更改您实际运行的查询的方式构建输入。那么,它应该是什么,最终又是什么?

    select id, userid, pass from users where userid='$user' and pass='$pass'
    

    那是查询,但是当我们用我们使用的实际输入替换变量时,我们得到以下内容:

    select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'
    

    看看我是如何构造我的“密码”的,以便它首先关闭密码周围的单引号,然后引入一个全新的比较?然后为了安全起见,我添加了另一个“字符串”,这样单引号就会像我们最初的代码中预期的那样被关闭。

    然而,这不是人们现在对你大喊大叫,而是向你展示如何让你的代码更安全。

    好的,那么出了什么问题,我们该如何解决呢?

    这是典型的 SQL 注入攻击。就此而言,这是最简单的方法之一。在攻击向量的规模上,这是一个蹒跚学步的孩子攻击坦克 - 并获胜。

    那么,我们如何保护您神圣的管理部分并使其美观安全?要做的第一件事就是停止使用那些非常陈旧且已弃用的mysql_* 功能。我知道,您遵循了在网上找到的教程并且它有效,但是它很旧,已经过时并且在几分钟内,我刚刚突破了它而没有流汗。

    现在,您可以更好地选择使用mysqli_PDO。我个人是 PDO 的忠实粉丝,所以我将在本回答的其余部分中使用 PDO。有利有弊,但我个人认为利远大于弊。它可以跨多个数据库引擎移植 - 无论您使用的是 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_* 函数。这一切都是通过 PDO 对象完成的,其次,它使用的是准备好的语句。现在,你问准备好的陈述是什么?这是一种在运行查询之前告诉数据库我们要运行的查询是什么的方法。在这种情况下,我们告诉数据库:“嗨,我要运行一个 select 语句,需要 id、userid 和来自表 users 的 pass,其中 userid 是一个变量,pass 也是一个变量。”。

    然后,在执行语句中,我们向数据库传递一个数组,其中包含它现在需要的所有变量。

    结果太棒了。让我们再次尝试之前的用户名和密码组合:

    user: bob
    pass: somePass
    

    用户未验证。惊人的。

    怎么样:

    user: Fluffeh
    pass: mypass
    

    哦,我只是有点兴奋,它起作用了:检查通过了。我们有一个经过验证的管理员!

    现在,让我们尝试一个聪明的家伙输入的数据,以试图通过我们的小验证系统:

    user: bob
    pass: n' or 1=1 or 'm=m
    

    这一次,我们得到以下信息:

    You could not be verified. Please try again...
    

    这就是为什么您在发布问题时被大吼大叫的原因 - 这是因为人们可以看到您的代码甚至可以在不尝试的情况下被绕过。请务必使用这个问题和答案来改进您的代码,使其更安全并使用最新的功能。

    最后,这并不是说这是完美的代码。您可以做更多的事情来改进它,例如使用散列密码,确保当您在数据库中存储敏感信息时,您不会以纯文本形式存储它,有多个级别的验证 - 但实际上,如果你只需将旧的易注入代码更改为这个,你就会在编写好的代码的过程中走得很好——事实上你已经走到这一步并且还在阅读,这让我有一种希望,你不仅会实现这种类型编写您的网站和应用程序时的代码,但您可能会出去研究我刚才提到的其他事情 - 以及更多。尽可能编写最好的代码,而不是几乎不能运行的最基本的代码。

    【讨论】:

    • 谢谢您的回答!有我的+1!值得注意的是 mysql_* 本身并不是不安全的,但它确实通过糟糕的教程和缺乏适当的语句准备 API 来促进不安全的代码。
    【解决方案10】:

    MySQL 扩展是三者中最古老的,是开发人员用来与 MySQL 通信的原始方式。这个扩展现在是 deprecated 以支持另一个 two alternatives 因为在较新版本的 PHP 和 MySQL 中进行了改进。

    • MySQLi 是用于 MySQL 数据库的“改进”扩展。它利用了较新版本的 MySQL 服务器中可用的功能,向开发人员公开了面向功能和面向对象的接口,并做了一些其他漂亮的事情。

    • PDO 提供了一个 API,该 API 整合了以前分散在主要数据库访问扩展(即 MySQL、PostgreSQL、SQLite、MSSQL 等)中的大部分功能。该接口为程序员公开高级对象以处理数据库连接,查询和结果集,低级驱动程序执行与数据库服务器的通信和资源处理。许多讨论和工作正在进入 PDO,它被认为是在现代、专业代码中使用数据库的合适方法。

    【讨论】:

      【解决方案11】:

      我发现上面的答案真的很长,所以总结一下:

      mysqli 扩展有许多 好处,在关键增强 mysql 扩展是:

      • 面向对象的接口
      • 支持准备好的语句
      • 支持多语句
      • 交易支持
      • 增强的调试功能
      • 嵌入式服务器支持

      来源:MySQLi overview


      如以上答案所述,mysql 的替代方案是 mysqli 和 PDO(PHP 数据对象)。

      • API支持服务器端Prepared Statements:MYSQLi和PDO支持
      • API 支持客户端 Prepared Statements:仅 PDO 支持
      • API 支持存储过程:MySQLi 和 PDO
      • API 支持多语句和所有 MySQL 4.1+ 功能 - 受 MySQLi 支持,大部分也受 PDO 支持

      MySQLi 和 PDO 都是在 PHP 5.0 中引入的,而 MySQL 是在 PHP 3.0 之前引入的。需要注意的一点是,MySQL 包含在 PHP5.x 中,但在以后的版本中已弃用。

      【讨论】:

      • 你的回答太长了,而真正的总结是“mysql ext is no more”。就这样
      • @YourCommonSense 我的回答是为什么 mysqli 取代了 mysql。关键不是说 Mysqli 今天存在所以使用它.. 每个人都知道!
      • 好吧,除了没有人问为什么mysqli取代了mysql之外,它也没有回答这个问题。它确实回答了引入 mysqli 的原因。但这并不能解释为什么不允许 mysql 和 mysqli 并行存在
      • @YourCommonSense 另外,OP 的问题是“即使它们在我的网站上工作,我为什么还要使用其他东西?”这就是我指出更改和改进的原因。您可能会查看所有其他答案,它们很长,所以我想我应该总结一下。
      【解决方案12】:

      可以使用 mysqli 或 PDO 定义几乎所有 mysql_* 函数。只需将它们包含在您的旧 PHP 应用程序之上,它就可以在 PHP7 上运行。我的解决方案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;
      }
      

      【讨论】:

      • 不要显示您的解决方案的链接,请在此处添加它们作为答案。
      【解决方案13】:

      不要使用 mysql 因为不推荐使用 Mysqli 代替。

      弃用的意思:

      这意味着不要使用某些特定的功能/方法/软件功能/特定的软件实践它只是意味着它不应该被使用,因为在应该使用的软件中有(或将会有)更好的选择。

      使用已弃用的函数时可能会出现几个常见问题:

      1.功能完全停止工作:应用程序或脚本可能依赖于不再受支持的功能,因此使用它们的改进版本或替代版本。

      2.关于弃用的警告信息显示:这些消息通常不会干扰网站功能。但是,在某些情况下,它们可能会破坏服务器发送标头的过程。

      例如:这可能会导致登录问题(cookies/会话设置不正确)或转发问题(301/302/303 重定向)。

      请记住:

      - 弃用的软件仍然是软件的一部分。

      - 弃用的代码只是代码的一个状态(标签)。

      MYSQL 与 MYSQLI 的主要区别数据库*

      • 旧数据库驱动程序
      • MySQL只能在过程中使用
      • 没有针对 SQL 注入攻击的保护
      • 已在 PHP 5.5.0 中弃用并在 PHP 7 中删除

      mysqli

      • 新数据库驱动
      • 目前正在使用中
      • 准备好的语句防止攻击

      【讨论】:

        【解决方案14】:

        如果您确定不想升级 php 版本,则无需更新,但同时您也不会获得安全更新,这将使您的网站更容易受到黑客攻击,这是主要原因。

        【讨论】:

        • 已经有一个获得 2k+ 投票的已接受答案,请提供更多有关您的答案如何解决问题的详细信息。您可以提供一些链接并描述它如何帮助未来的读者。
        猜你喜欢
        • 2013-05-27
        • 2011-06-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-16
        • 2014-12-05
        • 2011-09-17
        相关资源
        最近更新 更多