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

2022-08-30 05:44:25

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

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

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

警告:mysql_connect():没有此类文件或目录


答案 1

MySQL扩展:

  • 未处于积极开发状态
  • 自 PHP 5.5(2013 年 6 月发布)起正式弃用
  • 自 PHP 7.0 起已完全删除(2015 年 12 月发布)
    • 这意味着截至 2018 年 12 月 31 日,它不存在于任何受支持的 PHP 版本中。如果您使用的是支持它的PHP版本,那么您使用的版本不会修复安全问题。
  • 缺少 OO 接口
  • 不支持:
    • 非阻塞、异步查询
    • 预准备语句或参数化查询
    • 存储过程
    • 多个语句
    • 交易
    • “新”密码身份验证方法(在MySQL 5.6中默认启用;在5.7中是必需的)
    • MySQL 5.1 或更高版本中的任何新功能

由于它已被弃用,因此使用它会使代码不那么具有未来性。

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

请参阅 SQL 扩展的比较


答案 2

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

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

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

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

看到红盒子了吗?

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

为什么


离开不仅与安全性有关,还与访问MySQL数据库的所有功能有关。ext/mysql

ext/mysql是为MySQL 3.23构建的,从那时起只有很少的添加,同时大部分都与此旧版本保持兼容性,这使得代码更难维护。不支持的缺失功能包括:(来自PHP手册)。ext/mysql

不使用mysql_*功能的原因

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

以上引用自昆汀的回答

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

请参阅 SQL 扩展的比较


禁止显示弃用警告

当代码转换为 / 时,可以通过在 php 中设置来抑制错误.ini以排除MySQLiPDOE_DEPRECATEDerror_reportingE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

请注意,这也将隐藏其他弃用警告,但是,这些警告可能适用于MySQL以外的其他内容。(摘自 PHP 手册)

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

更好的方法是,我现在正在写一个简单的教程。PDOPDO


简单而简短的PDO教程


Q.我脑海中的第一个问题是:什么是“PDO”?

答:“PDO – PHP 数据对象 – 是一个数据库访问层,提供访问多个数据库的统一方法。

alt text


连接到 MySQL

使用函数,或者我们可以用旧的方式说出来(在PHP 5.5及更高版本中已弃用)mysql_*

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

使用 :您需要做的就是创建一个新对象。构造函数接受用于指定数据库源构造函数的参数,主要采用四个参数,分别是(数据源名称)和可选的,。PDOPDOPDODSNusernamepassword

在这里,我认为您熟悉所有内容,除了;这是 .A 基本上是一串选项,用于指示要使用的驱动程序和连接详细信息。有关进一步的参考,请查看PDO MySQL DSNDSNPDODSNPDO

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

注意:您也可以使用 ,但有时会导致错误,因此最好使用 .charset=UTF-8utf8

如果有任何连接错误,它将抛出一个可以被捕获以进一步处理的对象。PDOExceptionException

好读连接和连接管理 ¶

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

另一种方法是关闭默认情况下在驱动程序中启用的准备模拟,但应关闭准备模拟以安全使用。MySQLPDO

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

仅当您使用我不推荐的旧版本时,它才可用。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);

错误处理


中的错误处理比 容易得多。PDOmysql_*

使用时的常见做法是:mysql_*

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die()不是处理错误的好方法,因为我们无法处理中的事情。它只会突然结束脚本,然后将错误回显到您通常不想向最终用户显示的屏幕,并让血腥的黑客发现您的架构。或者,函数的返回值通常可以与mysql_error()结合使用来处理错误。diemysql_*

PDO提供了一个更好的解决方案:例外。我们所做的任何事情都应该包装在一个 - 块中。我们可以通过设置错误模式属性来强制进入三种错误模式之一。下面有三种错误处理模式。PDOtrycatchPDO

  • PDO::ERRMODE_SILENT.它只是设置错误代码,其行为与您必须检查每个结果然后查看以获取错误详细信息的行为几乎相同。mysql_*$db->errorInfo();
  • PDO::ERRMODE_WARNING举。(运行时警告(非致命错误)。脚本的执行不会停止。E_WARNING
  • PDO::ERRMODE_EXCEPTION:引发异常。它表示 PDO 引发的错误。您不应该从自己的代码中抛出一个。有关 PHP 中的异常的更多信息,请参阅例外。它的行为非常像 ,当它没有被捕获时。但与 不同的是,如果你选择这样做,可以优雅地捕获和处理。PDOExceptionor 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 );

你可以把它包装在-中,如下所示:trycatch

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

你不必处理 - 现在。您可以随时抓住它,但我强烈建议您使用-。此外,在调用这些东西的函数外部捕获它可能更有意义:trycatchtrycatchPDO

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 select image

所以你正在做的是: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 之前,最好的方法是告诉 PDO 您希望如何获取数据。在下面的部分中,我将对此进行解释。->fetch()

抓取模式

请注意上面的 和 代码中的用法。这说明如何将行作为关联数组返回,并将字段名称作为键。还有许多其他的获取模式,我将逐一解释。PDO::FETCH_ASSOCfetch()fetchAll()PDO

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

 $stmt->fetch(PDO::FETCH_ASSOC)

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

现在我来到获取模式:

  • PDO::FETCH_ASSOC:返回按结果集中返回的列名编制索引的数组
  • PDO::FETCH_BOTH(默认值):返回按结果集中返回的列名和 0 索引列号编制索引的数组

还有更多选择!在 PDOStatement Fetch 文档中阅读有关它们的所有信息。

获取行计数

您可以获取 a 和 do,而不是使用 来获取返回的行数,例如:mysql_num_rowsPDOStatementrowCount()

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

插入和更新或删除语句

Insert and update PDO image

我们在功能中所做的是: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);

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

<?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、 、 、 准备的查询INSERTUPDATEDELETE

  1. 选择

    $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. 插入

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

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

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();
    

注意:

但是和/或不完全安全。检查答案 PDO 预准备语句是否足以防止 SQL 注入?通过 ircmaxell。另外,我引用了他答案中的一部分:PDOMySQLi

$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 /*"));

推荐