想要改进这篇文章?提供这个问题的详细答案,包括引文和解释为什么你的答案是正确的。没有足够细节的答案可能会被编辑或删除。
为什么不应该使用 mysql_*
函数的技术原因是什么? (例如 mysql_query()
、mysql_connect()
或 mysql_real_escape_string()
)?
即使它们在我的网站上工作,我为什么还要使用其他东西?
如果他们在我的网站上不起作用,为什么我会收到类似的错误
警告:mysql_connect():没有这样的文件或目录
MySQL 扩展:
未在积极开发中
自 PHP 5.5(2013 年 6 月发布)起正式弃用。
自 PHP 7.0(2015 年 12 月发布)起已完全删除,这意味着截至 2018 年 12 月 31 日,它不存在于任何受支持的 PHP 版本中。如果您使用的是支持它的 PHP 版本,那么您使用的是没有修复安全问题的版本。
这意味着截至 2018 年 12 月 31 日,它不存在于任何受支持的 PHP 版本中。如果您使用的是支持它的 PHP 版本,那么您使用的是没有修复安全问题的版本。
缺少 OO 接口
不支持: 非阻塞、异步查询 准备语句或参数化查询 存储过程 多语句 事务 “新”密码验证方法(在 MySQL 5.6 中默认启用;在 5.7 中需要) MySQL 5.1 或更高版本中的任何新功能
非阻塞、异步查询
准备好的语句或参数化查询
存储过程
多个语句
交易
“新”密码验证方法(在 MySQL 5.6 中默认启用;在 5.7 中需要)
MySQL 5.1 或更高版本中的任何新功能
由于它已被弃用,因此使用它会使您的代码不那么面向未来。
缺乏对准备好的语句的支持尤其重要,因为它们提供了一种更清晰、更不容易出错的方法来转义和引用外部数据,而不是使用单独的函数调用手动转义它。
请参阅the comparison of SQL extensions。
PHP 提供了三种不同的 API 来连接 MySQL。这些是 mysql
(自 PHP 7 起已删除)、mysqli
和 PDO
扩展。
mysql_*
函数曾经非常流行,但不再鼓励使用它们。文档团队正在讨论数据库安全情况,教育用户远离常用的 ext/mysql 扩展是其中的一部分(检查 php.internals: deprecating ext/mysql)。
后来的 PHP 开发团队决定在用户连接到 MySQL 时生成 E_DEPRECATED
错误,无论是通过 mysql_connect()
、mysql_pconnect()
还是 ext/mysql
中内置的隐式连接功能。
ext/mysql
曾经是 officially 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)。
存储过程(不能处理多个结果集)
准备好的报表
加密 (SSL)
压缩
完整的字符集支持
不使用 mysql_*
功能的原因:
未在积极开发中
自 PHP 7 起删除
缺少 OO 接口
不支持非阻塞、异步查询
不支持准备好的语句或参数化查询
不支持存储过程
不支持多个语句
不支持交易
不支持 MySQL 5.1 中的所有功能
Above point quoted from Quentin's answer
缺乏对准备好的语句的支持尤其重要,因为它们提供了一种更清晰、更不容易出错的方法来转义和引用外部数据,而不是使用单独的函数调用手动转义它。
请参阅 comparison of SQL extensions。
抑制弃用警告
在将代码转换为 MySQLi
/PDO
时,可以通过在 php.ini 中设置 error_reporting
以排除 E_DEPRECATED:
来抑制 E_DEPRECATED
错误
error_reporting = E_ALL ^ E_DEPRECATED
请注意,这也将隐藏 其他弃用警告,但是,这可能是针对 MySQL 以外的东西。 (from PHP manual)
Dejan Marjanovic 的文章PDO vs. MySQLi: Which Should You Use? 将帮助您做出选择。
更好的方法是PDO
,我现在正在编写一个简单的PDO
教程。
一个简单而简短的 PDO 教程
问:我想到的第一个问题是:什么是`PDO`?
A. “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
(数据源名称)和可选的 username
、password
。
这里我想你对除了 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。
好读:
错误和错误处理 ¶
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
并阅读您的错误日志来隐藏生产中的危险错误消息。
现在,在阅读了以上所有内容之后,您可能会想:当我只想开始学习简单的 SELECT
、INSERT
、UPDATE
或 DELETE
语句时,这到底是什么鬼?别着急,我们来:
选择数据
https://i.stack.imgur.com/AhIlC.png
因此,您在 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 您希望如何获取数据。在下面的部分中,我将对此进行解释。
获取模式
请注意在上面的 fetch()
和 fetchAll()
代码中使用 PDO::FETCH_ASSOC
。这告诉 PDO
将行作为关联数组返回,其中字段名称作为键。还有许多其他的获取模式,我将一一解释。
首先,我解释一下如何选择获取模式:
$stmt->fetch(PDO::FETCH_ASSOC)
在上面,我一直在使用 fetch()
。您还可以使用:
PDOStatement::fetchAll() - 返回一个包含所有结果集行的数组
PDOStatement::fetchColumn() - 从结果集的下一行返回单个列
PDOStatement::fetchObject() - 获取下一行并将其作为对象返回。
PDOStatement::setFetchMode() - 设置此语句的默认获取模式
现在我来获取模式:
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();
插入和更新或删除语句
https://i.stack.imgur.com/yQhPC.png
我们在 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 就是这样。
准备好的报表
问:什么是准备好的陈述,我为什么需要它们? A. 预编译语句是预编译的 SQL 语句,可以通过只向服务器发送数据来执行多次。
使用准备好的语句的典型工作流程如下 (quoted from Wikipedia three 3 point):
准备:报表模板由应用程序创建并发送到数据库管理系统 (DBMS)。某些值未指定,称为参数、占位符或绑定变量(下面标记为 ?): INSERT INTO PRODUCT(名称、价格) VALUES(?、?) DBMS 对语句模板进行解析、编译和执行查询优化,并存储结果而不执行它。执行:稍后,应用程序为参数提供(或绑定)值,DBMS 执行语句(可能返回结果)。应用程序可以使用不同的值多次执行该语句。在此示例中,它可能为第一个参数提供“面包”,为第二个参数提供 1.00。
您可以通过在 SQL 中包含占位符来使用准备好的语句。基本上有三个没有占位符(不要尝试使用上面的变量),一个带有未命名的占位符,一个带有命名的占位符。
问:那么现在,什么是命名占位符以及如何使用它们? A. 命名占位符。使用以冒号开头的描述性名称,而不是问号。我们不关心名称占位符中的位置/价值顺序:
$stmt->bindParam(':bla', $bla);
bindParam(parameter,variable,data_type,length,driver_options)
您也可以使用执行数组进行绑定:
<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
OOP
朋友的另一个不错的功能是命名占位符能够将对象直接插入到您的数据库中,假设属性与命名字段匹配。例如:
class person {
public $name;
public $add;
function __construct($a,$b) {
$this->name = $a;
$this->add = $b;
}
}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);
问:那么现在,什么是未命名的占位符,我该如何使用它们? A. 举个例子:
<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();
和
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));
在上面,您可以看到那些 ?
而不是名称占位符中的名称。现在在第一个示例中,我们将变量分配给各种占位符 ($stmt->bindValue(1, $name, PDO::PARAM_STR);
)。然后,我们为这些占位符赋值并执行语句。在第二个示例中,第一个数组元素转到第一个 ?
,第二个转到第二个 ?
。
注意:在未命名的占位符中,我们必须注意传递给 PDOStatement::execute()
方法的数组中元素的正确顺序。
SELECT、INSERT、UPDATE、DELETE 准备好的查询
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);插入:$stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)"); $stmt->execute(array(':field1' => $field1, ':field2' => $field2)); $affected_rows = $stmt->rowCount();删除:$stmt = $db->prepare("DELETE FROM table WHERE id=:id"); $stmt->bindValue(':id', $id, PDO::PARAM_STR); $stmt->执行(); $affected_rows = $stmt->rowCount(); UPDATE: $stmt = $db->prepare("UPDATE table SET name=?WHERE id=?"); $stmt->execute(数组($name, $id)); $affected_rows = $stmt->rowCount();
笔记:
但是 PDO
和/或 MySQLi
并不完全安全。检查 ircmaxell 的答案Are PDO prepared statements sufficient to prevent SQL injection?。另外,我从他的回答中引用了一部分:
$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 /*"));
IN (...) construct
的任何有意义的使用。
function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();
它适用于抛出异常。
Doesn't support non-blocking, asynchronous queries
列为不使用 mysql_ 的原因 - 您还应该将其列为不使用 PDO 的原因,因为 PDO 也不支持它。 (但 MySQLi 支持它)
首先,让我们从我们给大家的标准评论开始:
请不要在新代码中使用 mysql_* 函数。它们不再被维护并被正式弃用。看到红框了吗?改为了解准备好的语句,并使用 PDO 或 MySQLi - 本文将帮助您决定使用哪个。如果您选择 PDO,这里有一个很好的教程。
让我们逐句解释一下:
它们不再被维护,并且被正式弃用这意味着 PHP 社区正在逐渐放弃对这些非常古老的功能的支持。它们很可能不会出现在 PHP 的未来(最近)版本中!继续使用这些功能可能会在(不是那么)遥远的将来破坏您的代码。新的! - 自 PHP 5.5 起,ext/mysql 现已正式弃用!更新! ext/mysql 已在 PHP 7 中删除。
相反,您应该了解准备好的语句 mysql_* 扩展不支持准备好的语句,这是(除其他外)对 SQL 注入非常有效的对策。它修复了 MySQL 相关应用程序中的一个非常严重的漏洞,该漏洞允许攻击者访问您的脚本并对您的数据库执行任何可能的查询。有关更多信息,请参阅如何防止 PHP 中的 SQL 注入?
看到红盒子了吗?当你进入任何 mysql 函数手册页时,你会看到一个红色框,说明它不应该再使用了。
使用 PDO 或 MySQLi 有更好、更健壮和构建良好的替代方案,PDO - PHP 数据库对象,它为数据库交互提供了完整的 OOP 方法,以及 MySQLi,它是 MySQL 的特定改进。
IN (...) construct
的任何有意义的使用。
使用方便
分析原因和综合原因已经提到。对于新手来说,停止使用过时的 mysql_ 函数有更大的动力。
现代数据库 API 更易于使用。
主要是绑定参数,可以简化代码。使用 excellent tutorials (as seen above) 过渡到 PDO 并不会过于艰巨。
然而,一次重写更大的代码库需要时间。这种中间选择的存在理由:
等效的 pdo_* 函数代替 mysql_*
使用 <pdo_mysql.php>,您可以轻松从旧的 mysql_ 函数切换。它添加了 pdo_
函数包装器来替换它们的 mysql_
对应物。
只需 include_once("pdo_mysql.php");在每个必须与数据库交互的调用脚本中。移除所有地方的 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() 和依此类推...您的代码将相似,并且看起来仍然基本相同: include_once("pdo_mysql.php"); pdo_connect("localhost", "usrABC", "pw1234567"); pdo_select_db(“测试”); $result = pdo_query("SELECT title, html FROM pages"); while ($row = pdo_fetch_assoc($result)) { print "$row[title] - $row[html]"; }
等等瞧。您的代码正在使用 PDO。现在是时候实际使用它了。
绑定参数可以很容易使用
https://i.stack.imgur.com/78Ddi.jpg
您只需要一个不那么笨重的 API。
pdo_query()
为绑定参数添加了非常简单的支持。转换旧代码很简单:
https://i.stack.imgur.com/PeuHl.png
将变量移出 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_* 仍然允许 or 。只是不要转义变量并将其绑定在同一个查询中。
占位符功能由其背后的真实 PDO 提供。
因此以后也允许 :named 占位符列表。
更重要的是,您可以在任何查询后面安全地传递 $_REQUEST[] 变量。当提交的 <form>
字段与数据库结构完全匹配时,它会更短:
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);
如此简单。但是,让我们回到一些重写建议和技术原因,说明为什么您可能想要摆脱 和转义。
mysql_
修复或删除任何 oldschool sanitize() 函数
使用绑定参数将所有 调用转换为 mysql_
pdo_query
后,删除所有多余的 pdo_real_escape_string
调用。
特别是,您应该以一种或另一种形式修复过时的教程所宣传的任何 sanitize
或 clean
或 filterThis
或 clean_data
函数:
function sanitize($str) {
return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}
这里最明显的错误是缺乏文档。更重要的是,过滤顺序完全错误。
正确的顺序是:不推荐使用 stripslashes 作为最里面的调用,然后是 trim,然后是 strip_tags,htmlentities 作为输出上下文,最后是 _escape_string,因为它的应用程序应该直接在 SQL intersparsing 之前。
但作为第一步,只需摆脱 _real_escape_string 调用。
如果您的数据库和应用程序流需要 HTML 上下文安全字符串,您现在可能必须保留 sanitize() 函数的其余部分。添加一条注释,说明它今后仅适用 HTML 转义。
字符串/值处理委托给 PDO 及其参数化语句。
如果您的 sanitize 函数中提到了 stripslashes(),则可能表明存在更高级别的疏忽。这通常是为了从已弃用的 magic_quotes 中撤消损坏(双重转义)。然而,最好集中固定,而不是一串一串地固定。使用一种用户态反转方法。然后删除 sanitize 函数中的 stripslashes() 。关于magic_quotes 的历史记录。该功能已被正确弃用。然而,它经常被错误地描述为失败的安全功能。但是,magic_quotes 是一个失败的安全功能,就像网球作为营养来源失败一样。这根本不是他们的目的。 PHP2/FI 中的原始实现仅使用“引号将被自动转义,从而更容易将表单数据直接传递给 msql 查询”明确地引入它。值得注意的是,它意外地与 mSQL 一起使用是安全的,因为它仅支持 ASCII。然后 PHP3/Zend 为 MySQL 重新引入了 magic_quotes 并错误地记录了它。但最初它只是一个便利功能,而不是为了安全。
这通常是为了从已弃用的 magic_quotes 中撤消损坏(双重转义)。然而,最好集中固定,而不是一串一串地固定。
使用一种用户态反转方法。然后删除 sanitize 函数中的 stripslashes() 。
准备好的语句有何不同
当您将字符串变量打乱到 SQL 查询中时,它不仅会变得更加复杂,而且您需要遵循。 MySQL 再次分离代码和数据也是多余的工作。
https://i.stack.imgur.com/niZ7j.png
SQL 注入只是当数据渗入代码上下文时。数据库服务器以后无法发现 PHP 最初将变量粘合在查询子句之间的位置。
使用绑定参数,您可以在 PHP 代码中分隔 SQL 代码和 SQL 上下文值。但它不会在幕后再次被洗牌(PDO::EMULATE_PREPARES 除外)。您的数据库接收不变的 SQL 命令和 1:1 变量值。
https://i.stack.imgur.com/M7PXp.png
虽然这个答案强调您应该关心删除 的可读性优势。由于这种可见的技术数据/代码分离,有时还会产生性能优势(重复的 INSERT 具有不同的值)。
mysql_
请注意,参数绑定仍然不是针对所有 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 并不能完全削减它。 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' => 'lawl', 'password' => '123', 'is_admin' => 'true');
pdo_real_escape_string()
<- 这甚至是一个真正的功能,我找不到任何文档吗?请为此发布一个来源。
mysql_
函数:
已过时-不再维护它们不允许您轻松移动到另一个数据库后端不支持准备好的语句,因此鼓励程序员使用连接来构建查询,从而导致 SQL 注入漏洞
mysqli_
mysql_*
函数是新 PHP 版本的 mysqlnd 函数的外壳。所以即使不再维护旧的客户端库,mysqlnd 也会被维护 :)
说起技术原因,只有少数,极其具体,很少使用。很可能你永远不会在你的生活中使用它们。也许我太无知了,但我从来没有机会使用它们,比如
非阻塞、异步查询
返回多个结果集的存储过程
加密 (SSL)
压缩
如果您需要它们 - 这些无疑是从 mysql 扩展转向更时尚和现代外观的技术原因。
尽管如此,也有一些非技术问题,这可能会使您的体验更加困难
在现代 PHP 版本中进一步使用这些函数将引发弃用级别的通知。它们可以简单地关闭。
在遥远的将来,它们可能会从默认的 PHP 构建中删除。也没什么大不了的,因为 mysql 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 安装上执行此操作。
与 mysqli 不同,PDO 可以按值绑定占位符,这使得动态构建的查询变得可行,而无需几个屏幕相当混乱的代码。
与 mysqli 不同,PDO 总是可以在一个简单的常用数组中返回查询结果,而 mysqli 只能在 mysqlnd 安装上执行此操作。
因此,如果您是普通的 PHP 用户,并且希望在使用本机准备好的语句时省去很多麻烦,那么 PDO - 再次 - 是唯一的选择。
但是,PDO 也不是灵丹妙药,并且有其困难。
因此,我为 PDO tag wiki 中的所有常见陷阱和复杂情况编写了解决方案
然而,每个谈论扩展的人总是忽略关于 Mysqli 和 PDO 的两个重要事实:
准备好的陈述不是灵丹妙药。有些动态标识符无法使用准备好的语句绑定。存在参数数量未知的动态查询,这使得查询构建成为一项艰巨的任务。 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
- 它可能是参数化的,但您的函数不使用 real 准备好的语句。
Not under active development
仅适用于虚构的“0.01%”吗?如果你用这个静止功能构建一些东西,在一年内更新你的 mysql 版本并最终得到一个非工作系统,我敢肯定会有很多人突然进入那个“0.01%”。我想说 deprecated
和 not under active development
密切相关。您可以说它“没有 [值得的] 理由”,但事实是,当提供选项之间的选择时,no active development
几乎和我所说的 deprecated
一样糟糕?
原因有很多,但最重要的一个可能是这些函数鼓励不安全的编程实践,因为它们不支持预准备语句。准备好的语句有助于防止 SQL 注入攻击。
使用 mysql_*
函数时,您必须记住通过 mysql_real_escape_string()
运行用户提供的参数。如果您只忘记了一个地方,或者您碰巧只转义了部分输入,那么您的数据库可能会受到攻击。
在 PDO
或 mysqli
中使用准备好的语句将使此类编程错误更难发生。
因为(除其他原因外)确保输入数据得到清理要困难得多。如果您使用参数化查询,就像使用 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
之类的东西,用于从数据库中提取本来不应该访问的数据。在某些情况下可以注入子查询,但是仍然不能以这种方式修改数据库。
编写此答案是为了说明绕过编写不佳的 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 对象完成的,其次,它使用的是准备好的语句。现在,你问的准备好的陈述是什么?这是一种在运行查询之前告诉数据库的方法,即我们将要运行的查询是什么。在这种情况下,我们告诉数据库:“嗨,我将运行一个 select 语句,想要 id、userid 和 pass 从表 users 中 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...
这就是为什么您在发布问题时会被大喊大叫 - 这是因为人们可以看到您的代码甚至可以在没有尝试的情况下被绕过。请务必使用此问题和答案来改进您的代码,使其更安全并使用最新的功能。
最后,这并不是说这是完美的代码。您可以做更多的事情来改进它,例如使用散列密码,确保当您在数据库中存储敏感信息时,您不会以纯文本形式存储它,具有多级验证 - 但实际上,如果您只需将旧的易于注入的代码更改为此,您将在编写好代码的过程中做得很好 - 而且您已经走到这一步并且仍在阅读的事实让我有一种希望,您不仅会实现这种类型编写您的网站和应用程序时的代码,但您可能会出去研究我刚刚提到的其他内容 - 等等。写你能写的最好的代码,而不是勉强能用的最基本的代码。
mysql_*
本身并不是不安全的,但它确实通过糟糕的教程和缺乏适当的语句准备 API 来促进不安全的代码。
MySQL 扩展是三者中最古老的,也是开发人员用来与 MySQL 通信的原始方式。由于在 PHP 和 MySQL 的较新版本中进行了改进,此扩展现在 deprecated 有利于其他 two alternatives。
MySQLi 是用于处理 MySQL 数据库的“改进”扩展。它利用了较新版本的 MySQL 服务器中可用的功能,向开发人员公开了面向函数和面向对象的接口,并做了一些其他漂亮的事情。
PDO 提供了一个 API,它整合了以前分布在主要数据库访问扩展(即 MySQL、PostgreSQL、SQLite、MSSQL 等)中的大部分功能。该接口公开了高级对象,供程序员使用数据库连接、查询和结果集和低级驱动程序执行与数据库服务器的通信和资源处理。大量的讨论和工作正在进入 PDO,它被认为是在现代、专业的代码中使用数据库的适当方法。
我发现上面的答案真的很长,所以总结一下:
mysqli 扩展有许多好处,对 mysql 扩展的主要增强是: 面向对象的接口 支持准备好的语句 支持多语句 支持事务 增强的调试功能 嵌入式服务器支持
如上述答案中所述,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 中,尽管在以后的版本中已弃用。
可以使用 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;
}
不要使用 mysql,因为不推荐使用 mysqli 来代替。
弃用的意思:
这意味着不要使用某些特定的功能/方法/软件功能/特定的软件实践,它只是意味着不应该使用它,因为应该使用该软件中(或将会有)更好的替代方案。
使用不推荐使用的函数时可能会出现几个常见问题:
1. 功能完全停止工作:应用程序或脚本可能依赖于不再支持的功能,因此使用它们的改进版本或替代方案。
2. 显示有关弃用的警告消息:这些消息通常不会干扰站点功能。但是,在某些情况下,它们可能会中断服务器发送标头的过程。
例如:这可能会导致登录问题(cookies/sessions 未正确设置)或转发问题(301/302/303 重定向)。
请记住:
- 弃用的软件仍然是软件的一部分。
- 弃用的代码只是代码的状态(标签)。
MYSQL 与 MYSQLI mysql* 的主要区别
旧数据库驱动程序
MySQL 只能在程序上使用
无法抵御 SQL 注入攻击
在 PHP 5.5.0 中已弃用并在 PHP 7 中删除
mysqli
新的数据库驱动程序
目前正在使用中
准备好的语句保护免受攻击
如果您确定不想升级 php 版本,则无需更新,但同时您也不会获得安全更新,这将使您的网站更容易受到黑客攻击,这是主要原因。
不定期副业成功案例分享
Doesn't support: Non-blocking, asynchronous queries
- 这也是不使用 PDO 的原因,它不假设异步查询(与 mysqli 不同)