原文
PDO vs. MySQLi: Which Should You Use?
译文当通过PHP访问一个数据库时,我们有两个选择MySQLi和PDO。那么在做出选择之前,有哪些知识你应该了解呢。本文将概述差异、支持的数据库、稳定性和性能问题等方面。
如果你经常在PHP中操作数据库,你可能想看看在Envato Market上有哪些同时支持MySQLi和PDO的可用脚本和APP。
总结
PDO
MySQLi
数据库支持
12种不同驱动
只支持MySQL
API
面向对象
面向对象、面向过程
连接方式
容易
容易
命名参数
支持
不支持
对象映射
支持
支持
预编译语句(客户端)
支持
不支持
性能
快
快
存储过程
支持
支持
连接方式二者连接数据库都比较容易:
12345678// PDO$pdo = new PDO("mysql:host=localhost;dbname=database", 'username', 'password');// mysqli,面向过程方式$mysqli = mysqli_connect('localhost', 'username', 'password', 'database');// mysqli,面向对象方式$mysqli = new mysqli('localhost', 'username', 'password', 'database');
请注意,在本教程的余下部分,这些连接和资源将被视为已存在。
API支持PDO和MySQLi都提供了面向对象API,但MySQLi也提供了对于新手更容易理解的面向过程API。如果你对原生的PHP MySQL驱动比较熟悉,你会发现迁移到MySQLi的面向过程接口更容易。相反的,一旦你掌握了PDO,你可以通过它访问任何你想访问的数据库(译者注:必须是PDO支持的)。
数据库支持PDO相比于MySQLi的主要优势是它对数据库驱动的支持。截止当前时间(译者注:原文发布于2012年2月),PDO支持12种不同的驱动。相反的,MySQLi只支持MySQL。
打印当前PDO支持的驱动列表,代码如下:
1var_dump(PDO::getAvailableDrivers());
这意味,一旦你的项目需要切换到另一个数据库驱动,PDO使这个过程完全透明。你需要做的只是修改连接标示和少量新数据库不支持的查询语句。如果是MySQLi,你需要重写所有包含查询语句的代码块。
命名参数这是PDO另一个重要的特点,参数绑定相比于顺序绑定更加容易的。
123456789$params = array(':username' => 'test', ':email' => $mail, 'last_login' => time() - 3600);$pdo->prepare(' SELECT * FROM users WHERE username = :username AND email = :email AND last_login > :last_login');$pdo->execute($params);
相反的,如果通过MySQLi的方式:
12345678$query = $mysqli->prepare(' SELECT * FROM users WHERE username = ? AND email = ? AND last_login > ?');$query = bind_param('sss', 'test', $mail, time() - 3600);$query->execute();
问号参数绑定可能看起来更短,但是它却不像命名参数那么灵活。因为事实上开发者必须一直记得参数的顺序,在某些场景下会很奇怪。
可惜,MySQLi不支持命名参数.
对象映射PDO和MySQLi都可以将结果映射到对象。如果想使用类ORM特性,但又不想定制一个数据库虚拟层,对象映射就很方便。让我们假设有一个User类,它的很多属性和数据库的字段名相匹配。
12345678910class User { public $id; public $first_name; public $last_name; public function info() { return '#'.$this->id.': '.$this->first_name.' '.$this->last_name; }}
如果没有对象映射,在正确调用info()方法之前,我们需要赋值每个字段(手动或通过构造函数)。
对象映射允许我们预定义许多属性,即使对象还没有被构造。例如:
123456789101112131415161718192021222324$query = "SELECT id, first_name, last_name FROM users";// PDO$result = $pdo->query($query);$result->setFetchMode(PDO::FETCH_CLASS, 'User');while ($user = $result->fetch()) { echo $user->info()."\n";}// MySQLi, 面向过程方式if ($result = mysqli_query($mysqli, $query)) { while ($user = mysqli_fetch_object($result, 'User')) { echo $user->info(). \n; }}// MySQLi, 面向对象方式if ($result = $mysqli->query($query)) { while ($user = $result->fetch_object('User')) { echo $user->info()."\n"; }}
安全
两个库都提供了SQL注入安全,只要开发者按照他们提供的方式使用(注:转义/通编译语句的参数绑定)
我们假设一个黑客尝试注通过’username’HTTP查询参数(GET)注入一些恶意代码:
1$_GET['username'] = "';DELETE FROM users;/*"
如果我们没有转义,它将照”原样”–删除users表的所有行,被包裹进查询语句(PDO和MySQLi都支持多语句查询)。
1234567// PDO, "手动"转义$username = PDO::quote($_GET['username']);$pdo->query("SELECT * FROM users WHERE username = $username");// mysqli, "手动"转义$username = mysqli_real_escape_string($_GET['username']);$mysqli->query("SELECT * FROM users WHRER username = '$username'");
如你所见,PDO::quote()不仅转义了字符串,还将他们用引号扩起来。相反的,mysqli_read_escape_string()只是转义了字符串,需要你手动添加括号.
12345678// PDO, 预处理语句$pdo->prepare('SELECT * FROM users WHERE username = :username');$pdo->execute(array(':username' => $_GET['username']));// MySQLi, 预处理语句$query = $mysqli->prepare('SELECT * FROM users WHERE username = ?');$query->bind_param('s', $_GET['username']);$query->execute();
我推荐你始终对绑定查询使用预处理语句而不是PDO::quote()和mysqli_real_escape_string()
性能虽然PDO和MySQLi都比较快,但MySQLi在基准测试中表现稍微更快一点,非预处理语句约为2.5%,预处理语句约为6.5%。然而原生的MySQL扩展库比二者都更快。所以如果你真的需要榨取最后一点性能,原生的MySQL扩展库可能是你要考虑的。
总结最终,PDO最终赢得了这场战斗的胜利。基于对12种不同的数据库驱动(18种不同的数据库)的支持和命名参数,我们可以忽略一点点性能的损失,继而习惯于他的API。从安全的角度考虑,只要开发者通过它们提供的方式来操作(注:预处理语句)它们都是安全的。
所以,如果你的还在使用MySQLi,是时间更做出改变了。
译者注:译文到此已全部结束
译者声明
感谢原作者Dejan Marjanovic,侵权必删
原文留言部分有很精彩的讨论,也值得学习
文章时间有点久,可能部分观点现在看有点过时,翻译只为提高语言能力