0%

【译】PDO和MySQLi的对比,你应该用哪个?

原文

译文

当通过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支持的)。

数据库支持

Database Support
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
10
class 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";
}
}

安全

Security

两个库都提供了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,是时间更做出改变了。

译者注:译文到此已全部结束

译者声明

  1. 感谢原作者Dejan Marjanovic,侵权必删
  2. 原文留言部分有很精彩的讨论,也值得学习
  3. 文章时间有点久,可能部分观点现在看有点过时,翻译只为提高语言能力