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


2501

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

即使我的网站上可以使用,我为什么还要使用其他内容?

如果它们在我的网站上不起作用,为什么会出现类似

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


类似于以下错误:致命错误:未捕获错误:调用未定义函数mysql_connect()...
Bimal Poudel

21
弃用一个足以避免它们的理由
Sasa1234

Answers:


2088

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扩展的比较


286
仅弃用是足以避免它们的理由。他们有一天不会在那里,如果你依靠他们,你将不会幸福。其余只是使用旧扩展使人们无法学习的事情列表。
Tim Post

111
弃用并不是每个人都认为是的魔咒。PHP本身将不会有一天出现,但是我们依赖于今天可用的工具。当我们不得不更换工具时,我们会的。
Lightness Races in Orbit

133
@LightnessRacesinOrbit —弃用不是灵丹妙药,它是一个标语,上面写着“我们意识到这很糟糕,因此我们将不再为它提供更多支持”。尽管拥有更好的未来代码证明是摆脱不推荐使用的功能的充分理由,但它并不是唯一的功能(甚至是主要功能)。更改工具是因为有更好的工具,而不是因为您被迫这么做。(在被迫更改工具之前,这意味着您不是仅因为代码已停止工作并且需要昨天修复而正在学习新工具,这是学习新工具的最糟糕时间)。
昆汀

18
关于性能不足,我还没有提到缺少准备好的语句的一件事。每次发表声明时,东西已经编译它,这样MySQL守护程序可以把它理解。使用此API,如果您在一个循环中发出200,000个相同的查询,则MySQL必须对该查询进行200,000倍的编译才能理解它。使用准备好的语句,它将被编译一次,然后将值参数化到已编译的SQL中。
Goldentoa11

20
@symcbean,它肯定不会支持预处理语句。实际上,这是不推荐使用的主要原因。没有(易于使用)准备好的语句,mysql扩展通常会成为SQL注入攻击的受害者。
rustyx 2014年

1287

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

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

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

ext/mysql从PHP 5.5开始正式弃用,从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与库MySQLi:哪你应该使用?德扬诺维奇将帮助您选择。

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


一个简单而简短的PDO教程


问:我想到的第一个问题是:什么是PDO?

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

替代文字


连接到MySQL

使用mysql_*function或我们可以用旧方式说(在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。A 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执行prepare的仿真。它还允许您手动启用此仿真。要使用本机服务器端准备好的语句,应显式设置它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从自己的代码中抛出A。有关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并仅阅读错误日志来隐藏生产中的危险错误消息。

现在,在阅读了上面所有的事情之后,你可能在想:到底是什么,当我刚要开始扶着简单SELECTINSERTUPDATE,或DELETE语句?不用担心,我们开始:


选择数据

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

提取模式

使用注意事项PDO::FETCH_ASSOCfetch()fetchAll()上面的代码。这告诉PDO将行作为关联数组返回,以字段名称作为键。我也将一一解释其他许多提取模式。

首先,我说明如何选择提取模式:

 $stmt->fetch(PDO::FETCH_ASSOC)

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

现在,我进入获取模式:

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

还有更多选择!在PDOStatementFetch文档中阅读所有相关内容。

获取行数

mysql_num_rows您可以使用a PDOStatement和do 来代替返回的行数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语句。

使用准备好的语句的典型工作流程如下(引自Wikipedia的三分之三):

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

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

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

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

您可以通过在SQL中包含占位符来使用准备好的语句。基本上有三种不带占位符的变量(不要在变量上面使用占位符尝试此操作),一种不带占位符的占位符,而另一种具有命名位点。

问:现在,什么叫占位符,我该如何使用它们?
A.命名的占位符。请在描述性名称前加上冒号,而不要使用问号。我们不在乎名称占位符中的位置/值的顺序:

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

bindParam(parameter,variable,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);

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

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

15
上面的好读应该恰当地提及:预处理语句剥夺了对的任何有意义的使用IN (...) construct
Eugen Rieck 2013年

24
问题是“为什么我不应该在PHP中使用mysql_ *函数”。这个答案虽然令人印象深刻并且提供了有用的信息,但是却超出了范围,就像@trejder所说的那样-每10个人中就有8个人会因为缺少4个小时的时间而错过这些信息它。将其分解成更多有价值的东西,并用作对几个更精确问题的答案。
Alex McMillan

我总是喜欢mysqli和PDO。但是对于模具处理,我尝试了异常替代方法, function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();它可以引发异常。
kuldeep.kamboj

您列出Doesn't support non-blocking, asynchronous queries不使用mysql_的原因-您也应该列出不使用PDO的原因,因为PDO也不支持。(但MySQLi支持它)
hanshenrik

我有使用此数据库的数据库,是否可以使用Charset utf8mb4_unicode_ci?
瑞安·斯通

301

首先,让我们从为大家提供的标准注释开始:

请不要mysql_*在新代码中使用函数。它们不再维护,已正式弃用。看到红色框了吗?改为了解准备好的语句,并使用 PDO库MySQLi - 本文将帮助你决定哪些。如果您选择PDO,这是一个很好的教程

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

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

    这意味着PHP社区正在逐渐放弃对这些非常老的功能的支持。它们可能在将来的PHP版本中不存在!继续使用这些功能可能会在不远的将来破坏您的代码。

    新!- 自PHP 5.5起, ext / mysql现在正式被弃用!

    较新!ext / mysql 已在PHP 7中删除

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

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

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

  • 看到红框了吗?

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

  • 使用PDO或MySQLi

    有更好,更健壮和完善的替代方案:PDO-PHP数据库对象(提供了完整的OOP方法来进行数据库交互)和MySQLi(这是MySQL的特定改进)。


6
还有一件事:我认为该函数在PHP中仍然仅存在一个原因-与旧的,过时但仍在运行的CMS,电子商务,公告板系统等兼容。最后,它将被删除,并且您必须重写您的应用...
Kamil 2012年

4
@Kamil:是的,但这并不是您不应该使用它的真正原因。不使用它的原因是因为它很古老,不安全等:)
Madara的《鬼魂》 2012年

4
@Mario-PHP开发人员确实有一个流程,他们刚刚投票赞成从5.5版开始正式弃用ext / mysql。这不再是一个假设的问题。
SDC 2012年

2
使用经过验证的技术(例如PDO或MySQLi)添加一些额外的行仍然可以使PHP始终提供易用性。我希望为开发人员着想,他/她知道在任何教程中看到这些令人敬畏的mysql_ *函数实际上会损害本课,并且应该告诉OP此类代码在10年前还不错,并且应该质疑教程的相关性!
FredTheWebGuy 2012

1
答案应该恰当地提及:预处理语句剥夺了对的任何有意义的使用IN (...) construct
Eugen Rieck 2013年

217

使用方便

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

当代数据库API 更加易于使用。

主要是可以简化代码的绑定参数。而且,通过出色的教程(如上所示),向PDO的过渡并不困难。

但是,立即重写较大的代码库需要花费时间。Raison d'être这个中间替代方案:

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

等等。
您的代码正在使用 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 []变量。当提交的<form>字段与数据库结构完全匹配时,它会更短:

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_tags,则应htmlentities仅将_escape_string其作为应用程序应直接在SQL插入之前进行。

  • 但是,第一步就是摆脱_real_escape_string通话。

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

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

  • 如果stripslashes()您的清理功能中提到任何内容,则可能表明存在更高级别的监督。

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

    PHP2 / FI中的原始实现仅通过“ 引号将被自动转义,从而使将表单数据直接传递到msql查询变得更加容易 ”而明确引入了它。值得注意的是,与mSQL一起使用是偶然安全的,因为它仅支持ASCII。
    然后PHP3 / Zend为MySQL重新引入了magic_quotes并错误地记录了它。但是最初它只是一种便利功能,并不旨在保证安全性。

准备好的语句有何不同

当您将字符串变量加扰到SQL查询中时,它不仅会使您更复杂。MySQL再次分离代码和数据也是多余的工作。

SQL注入只是什么时候 数据渗入代码上下文时发生。数据库服务器以后无法发现PHP最初将变量粘贴在查询子句之间的位置。

使用绑定的参数,可以在PHP代码中分隔SQL代码和SQL上下文值。但是它不会在后台再次被洗掉(PDO :: EMULATE_PREPARES除外)。您的数据库接收不变的SQL命令和1:1可变值。

尽管此答案强调您应该关注删除的可读性优点 mysql_。由于这种可见的和技术上的数据/代码分离,有时还具有性能优势(重复的INSERT具有不同的值)。

请注意,参数绑定仍然不是一个神奇的一站式解决方案 所有 SQL注入。它处理数据/值的最常见用法。但是不能将列名/表标识符列入白名单,不能帮助动态子句构造,或仅将简单数组值列表列入白名单。

混合PDO使用

这些pdo_*包装器函数构成了易于编码的Stop-gap 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数据库抽象和浏览一点。一直存在并且将有许多出色的库来执行此类任务。

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


8
请谨慎使用该pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);功能-即:pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');
rickyduck 2014年

@Tom当然,尽管它维护得很少(最后一个是0.9.2),但是您可以创建一个化石帐户,添加到Wiki或提交错误报告(无需注册IIRC)。
马里奥

pdo_real_escape_string() <-这甚至是一个真正的功能,我找不到它的任何文档吗?请为此发布信息。
Ryan Stone

144

mysql_功能:

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

18
#2同样适用于mysqli_
eggyal

16
公平地说,考虑到SQL方言的变化,即使PDO也没有给您第二确定性。为此,您需要一个合适的ORM包装器。
SDC 2012年

mysql_*函数是mysqlnd函数的外壳,用于更新的PHP版本。因此,即使不再维护旧的客户端库,也将维护mysqlnd :)
13年

问题是由于过时的php版本,没有太多的网络托管提供商可以支持这种面向对象的设计风格
Raju yourPepe 2013年

@RajuGujarati,所以找到可以的网络主机。如果您的Web主机没有,则很容易遭受服务器攻击。
Alnitak

106

说到技术原因,只有少数几个非常具体且很少使用。您很可能永远都不会在生活中使用它们。
也许我太无知了,但是我从来没有机会使用它们,例如

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

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

但是,还有一些非技术性的问题,可能会使您的体验更困难

  • 在现代PHP版本中进一步使用这些功能将引起不推荐使用的通知。只需将其关闭即可。
  • 在不久的将来,可以将它们从默认的PHP版本中删除。没什么大不了的,因为mydsql ext将被转移到PECL中,并且每个托管者都乐于用它编译PHP,因为他们不想失去那些工作了数十年的客户端。
  • 来自Stackoverflow社区的强烈抵抗。每次您提到这些诚实的功能时,都会被告知这些功能是严格的禁忌。
  • 作为一名普通的PHP用户,您使用这些功能的想法很容易出错和出错。正是由于所有这些无数的教程和手册都教给您错误的方法。不是函数本身-我必须强调它-而是函数的使用方式。

后一个问题是一个问题。
但是,我认为,提议的解决方案也不是更好。
在我看来,所有那些PHP用户都将学习如何立即正确处理SQL查询实在是太理想化了。最有可能的是,他们只是将mysql_ *机械地更改为mysqli_ *,而使方法相同。尤其是因为mysqli使准备好的语句用法难以置信的痛苦和麻烦。
更不用说本地预备语句不足以防止 SQL注入,而且mysqli和PDO都不提供解决方案。

因此,我宁愿与错误的做法作斗争并以正确的方式教育人们,而不是与诚实的扩展作斗争。

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

  • 不支持存储过程(我们使用mysql_query("CALL my_proc");了很长时间)
  • 不支持交易(与上述相同)
  • 不支持多条语句(谁需要它们?)
  • 不在积极的开发下(那又是什么呢?它会以任何实际的方式影响吗?)
  • 缺少OO接口(创建一个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标签Wiki中针对所有常见陷阱和复杂案例编写了解决方案

但是,每个谈论扩展的人都始终缺少关于Mysqli和PDO 的两个重要事实

  1. 准备好的声明不是万灵丹。有些动态标识符无法使用准备好的语句进行绑定。有些动态查询带有未知数量的参数,这使查询构建变得困难。

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

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

以下是一些基于我的safemysql类的示例,以说明这种抽象类应如何:

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

将这一行与PDO所需的代码量进行比较。
然后将原始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处理这种实际情况的示例。
这太罗word了,很可能不安全。

因此,再一次-您不仅应该关注原始驱动程序,还应该关注抽象类,它不仅对初学者手册中的愚蠢示例有用,而且可以解决实际问题。


20
mysql_*使得漏洞很容易得到。由于PHP被许多新手使用,mysql_*因此实际上在实践上是有害的,即使在理论上可以毫不费力地使用它。
Madara的《 Ghost》,

4
everything is parameterized and safe-可以将其参数化,但是您的函数未使用实际的预准备语句。
uınbɐɥs

6
怎么Not under active development只弥补那些“ 0.01%”呢?如果您使用此静态功能构建某些东西,并在一年内更新mysql版本并使用无法正常工作的系统结束,我肯定会突然有很多人使用“ 0.01%”的代码。我会这样说,deprecated并且not under active development关系密切。您可以说没有“ [值得]理由”,但事实是,当在选项之间进行选择时,no active development几乎和deprecated我说的一样糟糕?
Nanne

1
@MadaraUchiha:您能解释一下漏洞是如何轻易产生的吗?尤其是在那些相同的漏洞不会影响PDO或MySQLi的情况下...因为我不知道您所说的哪个。
ircmaxell,2013年

4
@ShaquinTrifonoff:当然,它不使用准备好的语句。但是PDO没有,大多数人都推荐使用MySQLi。因此,我不确定这是否会产生重大影响。上面的代码(还有一些解析)是默认情况下您准备语句时PDO所做的工作
ircmaxell 2013年

97

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

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

PDO或中使用准备好的语句mysqli会使语句更容易出错。


3
不幸的是,MySQLi_ *对传递可变数量的参数(例如,当您想传递一个值列表以在IN子句中进行检查时)的支持不佳,导致不使用参数,从而鼓励使用与使MySQL_ *调用易受攻击。
Kickstart 2013年

5
但是,再次,不安全性不是mysql_ *函数的固有问题,而是不正确使用的问题。
Agamemnus

2
@Agamemnus问题是mysql_ *使得实现“不正确的用法”变得容易,尤其是对于没有经验的程序员而言。实现预备语句的库会使这种类型的错误更加困难。
Trott 2014年

75

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

例如,有人可以用作用"enhzflep); drop table users"户名。旧的函数将允许每个查询执行多个语句,因此,诸如此类的臭虫可以删除整个表。

如果要使用mysqli的PDO,则用户名最终将为"enhzflep); drop table users"

看到 bobby-tables.com


10
The old functions will allow executing of multiple statements per query-不,他们不会。ext / mysql不能进行这种注入-PHP和MySQL可以进行这种注入的唯一方法是使用MySQLi和mysqli_multi_query()函数时。ext / mysql和未转义的字符串可能进行的种类注入是诸如' OR '1' = '1从数据库中提取原本不希望访问的数据之类的事情。在某些情况下,可以注入子查询,但是仍然无法以这种方式修改数据库。
DaveRandom

64

编写此答案的目的是显示绕过编写欠佳的PHP用户验证代码有多么微不足道,如何(以及使用何种方式)进行这些攻击以及如何用安全的预备语句替换旧的MySQL函数-基本上,就是为什么StackOverflow用户(可能有很多销售代表)骚扰新用户,以提问题以改善其代码。

首先,请随时创建此测试mysql数据库(我已将其称为我的准备):

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对象完成的,其次,它使用的是准备好的语句。现在,您要问什么预先准备好的陈述?这是一种在运行查询之前告诉数据库我们将要运行的查询的方式。在这种情况下,我们告诉数据库:“嗨,我要运行一个选择ID,用户ID并从表用户传递的选择语句,其中用户ID是变量,而传递也是变量。”

然后,在execute语句中,我们向数据库传递一个包含其现在期望的所有变量的数组。

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

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...

这就是为什么在发布问题时大喊大叫的原因-因为人们可以看到即使尝试也可以绕过您的代码。请使用此问题和答案来改进您的代码,使其更安全并使用最新功能。

最后,这并不是说这是完美的代码。您还可以做很多事情来改进它,例如使用哈希密码,确保在将有意义的信息存储在数据库中时,不要以纯文本形式存储它,而是要进行多级验证-但是实际上,如果您只要将旧的易于注入的代码更改为此,就可以很好地编写良好的代码-而且您已经走了很长一段距离并且仍在阅读中,这一事实让我感到,希望您不仅会实现这种类型编写网站和应用程序时的代码,但您可能会出去研究我刚才提到的其他内容-等等。编写可能的最佳代码,而不是几乎无法发挥作用的最基本的代码。


2
谢谢您的回答!有我的+1!值得注意的是,它mysql_*本身并不是不安全的,但是它确实通过糟糕的教程和缺乏适当的语句prepare API来促进不安全的代码。
马达拉的幽灵

2
未加密的密码,恐怖!= oP否则为+1,以进行详细说明。

33

MySQL扩展是这三个中最老的扩展,是开发人员用来与MySQL通信的原始方式。由于PHP和MySQL的新版本中进行了改进,因此不建议使用此扩展,而推荐使用其他两个 替代方法

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

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


21

我发现以上答案确实很冗长,因此总结一下:

mysqli扩展具有许多优点,相对于mysql扩展,主要的增强之处在于:

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

资料来源:MySQLi概述


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

  • API支持服务器端预处理语句:由MYSQLi和PDO支持
  • API支持客户端的预处理语句:仅受PDO支持
  • API支持存储过程:MySQLi和PDO
  • API支持多条语句和所有MySQL 4.1+功能-受MySQLi和PDO的大部分支持

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


2
您的答案太冗长,而真正的摘要是“ mysql ext is no more”。仅此
您的常识

1
@YourCommonSense我的答案是为什么mysqli代替mysql。关键不是要说Mysqli现在存在,所以请使用它。每个人都知道!
阿妮·梅农

1
好吧,除了没有人问为什么mysqli代替mysql之外,它也没有回答这个问题。它确实回答了为什么引入mysqli的问题。但这并不能解释为什么不允许mysql和mysqli并行运行
您的常识

@YourCommonSense另外,OP的问题是“即使我的网站上可以工作,为什么还要使用其他东西?” 这就是我指出更改和改进的原因。您可能会看到所有其他答案都很长,因此我认为应该对其进行总结。
阿妮·梅农

6

可以mysql_*使用mysqli或PDO 定义几乎所有函数。只需将它们包括在旧的PHP应用程序之上,它将在PHP7上运行。我的解决方案在这里

<?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;
}

而不是显示您的解决方案的链接,请在此处添加它们作为答案。
amarnath

1

这是因为与此类似的功能mysql_connect()mysql_query()类型是前一版本PHP即(PHP 4)的功能和现在不在使用中。

在最新的PHP5中mysqli_connect(),它们被替换为mysqli_query()

这就是错误背后的原因。


2
PHP 5至今尚未发布最新版本超过2年。
马达拉的幽灵

1

MySQL在PHP 5.5.0中已弃用,在PHP 7.0.0中已删除。对于大型而古老的应用程序,很难搜索和替换每个功能。

我们可以通过为下面每个正在运行的代码创建一个包装函数来使用MySQL函数。点击这里


-9

鉴于已开发出更好的功能和代码结构,因此不建议使用mysql_ *函数(从PHP 5.5开始)。该功能已被弃用这一事实意味着,就性能和安全性而言,将不再付出更多的精力来改进它,这意味着它的未来证明越来越少

如果您需要更多原因:

  • mysql_ *函数不支持预备语句。
  • mysql_ *函数不支持参数绑定。
  • mysql_ *函数缺少面向对象编程的功能。
  • 清单继续...

18
这个答案已经过时了。此外,它不会对已经存在的答案添加任何有用的信息。
常识
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.