首页 文章

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

提问于
浏览
2271

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

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

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

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

14 回答

  • 1898

    可以使用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;
    }
    
  • 204

    mysql_* 函数已被弃用(截至 PHP 5.5 ),因为开发了更好的函数和代码结构 . 该功能被弃用的事实意味着在性能和安全性方面不再需要付出努力来改进它, which means it is less future proof .

    如果您需要更多理由:

    • mysql_* 函数不支持预准备语句 .

    • mysql_* 函数不支持参数绑定 .

    • mysql_* 函数缺少面向对象编程的功能 .

    • 列表继续......

  • 98

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

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

    PDOmysqli 中使用预准备语句会使这些编程错误更难以制作 .

  • 30

    mysql_connect()mysql_query() 类似的函数是PHP的先前版本(PHP 4)函数,现在尚未使用 .

    在最新的PHP5中,它们被 mysqli_connect()mysqli_query() 取代 .

    这就是错误背后的原因 .

  • -7

    这个答案是为了说明绕过写得不好的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
    

    惊人!全面的Hi-fives,代码正确验证了管理员 . 这是完美的!

    嗯,不是真的 . 让我们说用户是一个聪明的小人物 . 让我们说这个人就是我 .

    输入以下内容:

    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 . 有亲's and con',但我个人觉得亲's far outweigh the con' . 它可以在多个数据库引擎中移植 - 无论您使用的是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_* 功能 . 它是's all done via a PDO object, secondly, it is using a prepared statement. Now, what'你要问的准备好的声明吗?这是一种在运行查询之前告诉数据库的方法,我们将要运行的查询是什么 . 在这种情况下,我们告诉数据库:"Hi, I am going to run a select statement wanting id, userid and pass from the table users where the userid is a variable and the pass is also a variable." .

    然后,在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...
    

    这就是为什么你在发布问题时被大吼大叫的原因 - 这是因为人们可以看到你的代码可以绕过而不用尝试 . 请使用此问题和答案来改进您的代码,使其更安全并使用当前的功能 .

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

  • 62

    我觉得上面的答案真的很冗长,总结一下:

    mysqli扩展有许多好处,对mysql扩展的关键改进是:面向对象的接口支持准备语句支持多语句支持事务增强的调试功能嵌入式服务器支持

    资料来源:MySQLi overview


    正如上面的答案所解释的,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之前引入的 . 值得注意的是,MySQL已包含在PHP5.x中,但在以后的版本中已弃用 .

  • 1193

    MySQL扩展:

    • 未进行积极开发

    • 自PHP 5 . 5(2013年6月发布)起 officially deprecated .

    • 自PHP 7.0起已于 removed entirely (2015年12月发布)

    • 这意味着从31 Dec 2018开始,它将不存在于任何受支持的PHP版本中 . 目前,它只获得安全更新 .

    • 缺少OO接口

    • 不支持:

    • 非阻塞,异步查询

    • Prepared statements or parameterized queries

    • 存储过程

    • 多个声明

    • 交易

    • "new"密码验证方法(MySQL 5.6中默认启用; 5.7中要求)

    • MySQL 5.1中的所有功能

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

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

    the comparison of SQL extensions .

  • 88

    MySQL扩展是三者中最老的,是开发人员用来与MySQL通信的原始方式 . 由于PHP和MySQL的新版本中的改进,此扩展现在正在deprecated支持其他two alternatives .

    • MySQLi是用于处理MySQL数据库的'improved'扩展 . 它利用了较新版本的MySQL服务器中提供的功能,向开发人员公开了面向功能的界面和面向对象的界面,并且做了一些其他很好的事情 .

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

  • 2

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

    请不要在新代码中使用mysql_ *函数 . 它们不再维护,并且已被正式弃用 . 看到红色的盒子?转而学习预备语句,并使用PDO或MySQLi - 本文将帮助您确定哪些 . 如果您选择PDO,这是一个很好的教程 .

    让我们逐句逐句解释:

    • They are no longer maintained, and are officially deprecated

    这意味着PHP社区正在逐渐放弃对这些非常旧的功能的支持 . 它们很可能不存在于PHP的未来(最新)版本中!继续使用这些函数可能会破坏(不是那么)远期的代码 .

    NEW! - ext/mysql is now officially deprecated as of PHP 5.5!

    更新!在PHP 7中删除了ext / mysql .

    • Instead, you should learn of prepared statements

    mysql_* 扩展名不支持 prepared statements ,这是(除其他事项外)对 SQL Injection 的一个非常有效的对策 . 它修复了MySQL依赖应用程序中的一个非常严重的漏洞,它允许攻击者访问您的脚本并在您的数据库上执行 any possible query .

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

    • See the Red Box?

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

    • Use either PDO or MySQLi

    有更好,更强大和精心构建的替代方案 PDO - PHP Database Object ,它提供了完整的OOP方法来进行数据库交互,而 MySQLi 则是MySQL特定的改进 .

  • 136

    易于使用

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

    Contemporary database APIs are just easier to use.

    它主要是可以简化代码的绑定参数 . 而excellent tutorials (as seen above)过渡到PDO并不过分艰巨 .

    一次重写更大的代码库需要时间 . Raison d'être为这个中间选择:

    等效的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("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }
    

    Etvoilà .
    您的代码正在使用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_和逃避 .

    修复或删除任何oldschool 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间距之前 .

    • 但是第一步只是 get rid of the _real_escape_string 来电 .

    • 如果您的数据库和应用程序流期望HTML上下文安全的字符串,您可能必须暂时保留 sanitize() 函数的其余部分 . 添加一条评论,它仅适用于今后的HTML转义 .

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

    • 如果在您的清理功能中有任何 stripslashes() 的提及,则可能表示更高级别的疏忽 .

    • 这通常用于从已弃用的magic_quotes撤消损坏(双重转义) . 然而,这是best fixed centrally,而不是逐字符串 .

    • 使用userland reversal方法之一 . 然后删除 sanitize 函数中的 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_* 包装函数构成了一个编码友好的止差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一样,你现在不必保持原始的数据库接口 .

  • 285

    mysql_ 函数:

    • 已过期 - 它们不再维护

    • 不允许您轻松移动到另一个数据库后端
      因此,

    • 不支持预备语句

    • 鼓励程序员使用连接来构建查询,从而导致SQL注入漏洞

  • 71

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

    mysql_* 函数曾经很受欢迎,但它们的使用并非如此鼓励了 . 文档小组正在讨论数据库安全情况,并教育用户远离常用的ext / mysql扩展是其中的一部分(检查php.internals: deprecating ext/mysql) .

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

    ext/mysqlofficially deprecated as of PHP 5.5并且一直是removed as of PHP 7 .

    See the Red Box?

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

    为什么


    远离 ext/mysql 不仅涉及安全性,还涉及访问MySQL数据库的所有功能 .

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

    Reason to not use mysql_ function* :

    • 未进行积极开发

    • 从PHP 7开始删除

    • 缺少OO接口

    • 不支持非阻塞的异步查询

    • 不支持预准备语句或parameterized queries

    • 不支持存储过程

    • 不支持多个语句

    • 不支持transactions

    • 不支持MySQL 5.1中的所有功能

    Above point quoted from Quentin's answer

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

    comparison of SQL extensions .


    Suppressing deprecation warnings

    当代码转换为 MySQLi / PDO 时,可以通过在 php.ini 中设置 error_reporting 来排除 E_DEPRECATED 错误以排除 E_DEPRECATED:

    error_reporting = E_ALL ^ E_DEPRECATED
    

    请注意,这也会隐藏 other deprecation warnings ,但是,这可能是MySQL以外的东西 . (from PHP manual

    Dejan Marjanovic的文章PDO vs. MySQLi: Which Should You Use?将帮助您选择 .

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


    一个简单而简短的PDO教程


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

    答:“ PDO – PHP Data Objects - 是一个数据库访问层,提供访问多个数据库的统一方法 . ”


    连接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');
    

    Note: 你也可以使用 charset=UTF-8 ,但有时它会导致错误,所以最好使用 utf8 .

    如果有任何连接错误,它将抛出一个 PDOException 对象,可以捕获该对象以进一步处理 Exception .

    Good readConnections and Connection management ¶

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

    另一种方法是关闭默认情况下在 MySQL 驱动程序中启用的准备仿真,但应该关闭准备仿真以安全地使用 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));
    

    Can we set attributes after PDO construction?

    Yes ,我们还可以在使用 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 提供了更好的解决方案:例外 . 我们用_74330做的任何事情都应该包含在 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 可以被优雅地捕获和处理 .

    Good read

    喜欢:

    $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 . 你可以随时 grab 它,但我强烈建议你使用 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 语句时,到底是什么?别担心,我们走了:


    选择数据

    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
    

    Note :如果使用如下方法( 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() . 您还可以使用:

    现在我来获取模式:

    • PDO::FETCH_ASSOC :返回由结果集中返回的列名索引的数组

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

    还有更多的选择!在PDOStatement Fetch documentation.中了解所有这些内容 .

    Getting the row count

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

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

    Getting the Last Inserted 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语句并返回受影响的行数 .

    稍后将介绍插入和删除 .

    只有在查询中不使用变量时,上述方法才有用 . 但是当你需要在查询中使用变量时,不要尝试上面那样的prepared statement or parameterized statement .


    准备好的陈述

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

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

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

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

    • DBMS在语句模板上解析,编译和执行查询优化,并存储结果而不执行它 .

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

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

    Q. 所以现在,有什么名字占位符以及如何使用它们?
    A. 命名占位符 . 使用以冒号开头的描述性名称,而不是问号 . 我们不关心名称持有人的 Value 位置/顺序:

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

    Q. 那么现在,什么是未命名的占位符以及如何使用它们?
    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); ) . 然后,我们为这些占位符分配值并执行该语句 . 在第二个示例中,第一个数组元素转到第一个 ? ,第二个转到第二个 ? .

    NOTE :在 unnamed placeholders 中,我们必须注意我们传递给 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);
    
    • INSERT
    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    
    • DELETE
    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
    
    • UPDATE
    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($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 /*"));
    
  • 19

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

    • 非阻塞,异步查询

    • 存储过程返回多个结果集

    • 加密(SSL)

    • 压缩

    如果你需要它们 - 这些毫无疑问是技术上的原因,从mysql扩展转向更时尚和现代化的东西 .

    Nevertheless, there are also some non-technical issues, which can make your experience a bit harder

    • 在现代PHP版本中进一步使用这些函数将引发弃用级别的通知 . 他们只是可以关闭 .

    • 在遥远的未来,它们可能会从默认的PHP构建中删除 . 也没什么大不了的,因为mydsql ext将被转移到PECL中,每个主机都很乐意用它来编译PHP,因为他们不想失去那些网站工作了几十年的客户 .
      来自Stackoverflow社区的强大阻力 . Еverytime你提到这些诚实的功能,你被告知他们是严格的禁忌 .

    • 是普通的PHP用户,很可能你使用这些函数的想法容易出错并且错误 . 正因为所有这些教程和手册教你错误的方法 . 不是功能本身 - 我必须强调它 - 但它们的使用方式 .

    后一个问题是一个问题 .
    但是,在我看来,提议的解决方案也不是更好 .
    在我看来,所有这些PHP用户都将学习如何正确处理SQL查询 . 很可能他们只是将mysql_ *机械地改为mysqli_ *, leaving the approach the same . 特别是因为mysqli使准备好的语句使用令人难以置信的痛苦和麻烦 .
    更不用说SQL注入的原生语句 aren't enough to protect ,mysqli和PDO都没有提供解决方案 .

    所以,与其打击这种诚实的延伸,我宁愿采取错误的做法,并以正确的方式教育人们 .

    Also, there are some false or non-significant reasons, like

    • 不支持存储过程(我们多年来一直使用 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 is the only choice ,因为这个扩展假装不只是API而是半DAL,仍然不完整但是提供了许多重要的功能,其中两个使得PDO与mysqli有着明显的区别:

    • 与mysqli不同,PDO可以按值绑定占位符,这使得动态构建的查询可行而没有几个相当混乱的屏幕码 .

    • 与mysqli不同,PDO总是可以在一个简单的常用数组中返回查询结果,而mysqli只能在mysqlnd安装上执行 .

    因此,如果您是普通的PHP用户并希望在使用本机预处理语句时节省大量的麻烦,那么再次使用PDO是唯一的选择 .
    然而,PDO也不是银弹,并且有其艰辛 .
    所以,我为PDO tag wiki中的所有常见陷阱和复杂案例编写了解决方案 .

    然而,每个人都在谈论扩展总是错过关于Mysqli和PDO的 2 important facts

    • 准备好的声明 isn't a silver bullet . 有动态标识符不能使用预准备语句绑定 . 存在具有未知数量的参数的动态查询,这使得查询构建成为困难的任务 .

    • Neither mysqli_ nor PDO functions should have appeared in the application code.*
      它们和应用程序代码之间应该有一个 abstraction layer ,它将完成内部绑定,循环,错误处理等所有脏工作,使应用程序代码干燥和清理 . 特别是对于像动态查询构建这样的复杂情况 .

    所以,仅仅切换到PDO或mysqli是不够的 . 必须使用ORM,查询构建器或任何数据库抽象类,而不是在其代码中调用原始API函数 .
    相反 - 如果你的应用程序代码和mysql API之间有一个抽象层 - it doesn't actually matter which engine is used. 你可以使用mysql ext直到它被弃用,然后轻松地将你的抽象类重写为另一个引擎, having all the application code intact.

    以下是基于我的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处理这种实际案例的例子 .
    而且它太过于罗嗦而且很可能不安全 .

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

  • 0

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

    例如,有人可以使用 "enhzflep); drop table users" 作为用户名 . 旧函数将允许每个查询执行多个语句,因此类似讨厌的bugger可以删除整个表 .

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

    bobby-tables.com .

相关问题