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

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

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

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

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


答案

MySQL扩展:

  • 没有积极发展
  • 正式弃用的PHP 5.5(发布2013年6月)。
  • 已经删除全部为PHP 7.0(发布2015年12月)
  • 缺少OO界面
  • 不支持:
    • 非阻塞异步查询
    • 编写语句或参数化查询
    • 存储过程
    • 多重陈述
    • 交易
    • “新”密码认证方法(在MySQL 5.6中默认开启;在5.7中需要)
    • MySQL 5.1中的所有功能

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

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

查看SQL扩展的比较


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

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

而后来的PHP开发团队已产生决定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/mysqlinclude 🙁 来自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:你应该使用哪一个?德扬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。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会执行准备的模拟。它也可以让你手动启用这个模拟。要使用本机服务器端准备好的语句,您应该明确地设置它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构建后设置属性吗?

是的,我们也可以在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并只读取错误日志来隐藏生产中的危险错误消息。

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


选择数据

 

所以你在做什么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获取返回的行数,你可以得到一个PDOStatement和做rowCount(),如:

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

获取最后插入的ID

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

插入和更新或删除语句

我们在mysql_*功能上做的是:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

在pdo中,同样的事情可以通过以下方式完成:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

在上面的查询中PDO::exec执行一条SQL语句并返回受影响的行数。

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

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


准备好的陈述

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

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

  1. 准备:语句模板由应用程序创建并发送到数据库管理系统(DBMS)。某些值未指定,称为参数,占位符或绑定变量(?如下所示):INSERT INTO PRODUCT (name, price) VALUES (?, ?)
  2. DBMS解析,编译并在语句模板上执行查询优化,并存储结果而不执行它。
  3. 执行:稍后,应用程序为参数提供(或绑定)值,并且DBMS执行语句(可能返回结果)。应用程序可以根据需要多次执行该语句并使用不同的值。在这个例子中,它可能为第一个参数和1.00第二个参数提供’Bread’ 。

您可以通过在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 /*"));

添加评论

友情链接:蝴蝶教程