原文
译文
当通过PHP访问一个数据库时,我们有两个选择MySQLi和PDO。那么在做出选择之前,有哪些知识你应该了解呢。本文将概述差异、支持的数据库、稳定性和性能问题等方面。
如果你经常在PHP中操作数据库,你可能想看看在Envato Market上有哪些同时支持MySQLi和PDO的可用脚本和APP。
总结
PDO | MySQLi | |
---|---|---|
数据库支持 | 12种不同驱动 | 只支持MySQL |
API | 面向对象 | 面向对象、面向过程 |
连接方式 | 容易 | 容易 |
命名参数 | 支持 | 不支持 |
对象映射 | 支持 | 支持 |
预编译语句(客户端) | 支持 | 不支持 |
性能 | 快 | 快 |
存储过程 | 支持 | 支持 |
连接方式
二者连接数据库都比较容易:1
2
3
4
5
6
7
8// 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支持的驱动列表,代码如下:1
var_dump(PDO::getAvailableDrivers());
这意味,一旦你的项目需要切换到另一个数据库驱动,PDO使这个过程完全透明。你需要做的只是修改连接标示和少量新数据库不支持的查询语句。如果是MySQLi,
你需要重写所有包含查询语句的代码块。
命名参数
这是PDO另一个重要的特点,参数绑定相比于顺序绑定更加容易的。1
2
3
4
5
6
7
8
9$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的方式:1
2
3
4
5
6
7
8$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
类,它
的很多属性和数据库的字段名相匹配。1
2
3
4
5
6
7
8
9
10class User {
public $id;
public $first_name;
public $last_name;
public function info()
{
return '#'.$this->id.': '.$this->first_name.' '.$this->last_name;
}
}
如果没有对象映射,在正确调用info()
方法之前,我们需要赋值每个字段(手动或通过构造函数)。
对象映射允许我们预定义许多属性,即使对象还没有被构造。例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24$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都支持多语句查询)。1
2
3
4
5
6
7// 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()
只是转义了字符串,需要你手动添加括号.1
2
3
4
5
6
7
8// 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,侵权必删
- 原文留言部分有很精彩的讨论,也值得学习
- 文章时间有点久,可能部分观点现在看有点过时,翻译只为提高语言能力