0x00 前言

用久了 Laravel ORM 之后真的觉得通过 PDO 操作数据库的方式真的是弱爆了,但是作为基础知识还是要了解一下的。万一面试被搞到哑口无言岂不是白白错过工作机会/(ㄒoㄒ)/~~,所以还是决定整理一下关于 PDO 操作的常识。其实 PDO 的核心其实就只有三个类 PDOPDOStatementPDOException,三者分别提供:数据库的连接和SQL语句的执行;返回的结果集处理;出错的异常。所以 PDO 基本使用还是很容易掌握的 O(∩_∩)O 哈哈。

0x01 PDO 类

首先我们看个 PDO 连接数据库并执行 SQL 语句的基本示例:

1
2
3
4
5
6
7
8
9
10
11
$dsn = 'mysql:host=127.0.0.1:3306;dbname=test';

try {
$pdo = new PDO($dsn, 'root', '123456');
} catch (PDOException $exception) {
$exception->getMessage();
}
// 插入一行的 SQL 语句
$sql = "INSERT INTO `posts` ( `title`, `author`) VALUE ('test', 'test');";

$pdo->exec($sql);

构造 PDO 类需要四个参数分别是 DSN(数据源)、数据库用户名、数据库密码、连接选项(可选)。数据源的基本格式如下:

<数据库驱动>:host=<数据库主机名>[:数据库端口];dbname=<数据库名>

MySQL的参考数据源:mysql:host=127.0.0.1:3306;dbname=test

重要方法

exec

执行一条的 SQL 语句并返回受影响行数,注意:该方法执行 SELECT 语句只会返回 0

query

执行一条的 SQL 语句并返回一个 PDOStatement 对象。示例:

1
2
3
4
$select = "SELECT * FROM `posts`;";
$statement = $pdo->query($select);
echo get_class($statement);
// 输出 PDOStatement

PDOStatement 对象可以理解为「结果集对象」,通过该对象的方法我们可以获取到 SQL 语句的查询结果。

prepare

准备要执行的SQL语句并返回一个 PDOStatement 对象。该函数主要应用为 预处理语句与存储过程

事务相关:beginTransaction / rollBack / commit / inTransaction

beginTransaction:开启一个事务,同时关闭自动提交模式即将连接选项中的 PDO::ATTR_AUTOCOMMIT 设置为 false

rollBack:回滚由 PDO::beginTransaction() 发起的当前事务。如果没有事务激活,将抛出一个 PDOException 异常。如果数据库被设置成自动提交模式,此函数(方法)在回滚事务之后将恢复自动提交模式。注意:包括 MySQL 在内的一些数据库, 当在一个事务内有类似删除或创建数据表等 DLL 语句时,会自动导致一个隐式地提交。隐式地提交将无法回滚此事务范围内的任何更改。

commit:提交一个事务,数据库连接返回到自动提交模式直到下次调用 PDO::beginTransaction() 开始一个新的事务为止。

inTransaction:检查驱动内的一个事务当前是否处于激活。此方法仅对支持事务的数据库驱动起作用。

关于自动提交模式,在实例化 PDO 对象或 PDO 对象的 setAttribute 方法都可以设置 PDO::ATTR_AUTOCOMMIT 选项,该选项默认为 true,即自动提交单独的语句。

事务相关实例代码:

1
2
3
4
5
6
7
8
$pdo->beginTransaction();
$sql = "INSERT INTO `posts` ( `title`, `author`) VALUE ('test1', 'test1');";
$pdo->exec($sql);
sleep(10);
$sql = "INSERT INTO `posts` ( `title`, `author`) VALUE ('test2', 'test2');";
$pdo->exec($sql);
$pdo->commit();
// 十秒后 test1 和 test2 才会被同时插入到数据库
1
2
3
4
5
6
7
8
$pdo->beginTransaction();
$sql = "INSERT INTO `posts` ( `title`, `author`) VALUE ('test3', 'test3');";
$pdo->exec($sql);
sleep(5);
$sql = "INSERT INTO `posts` ( `title`, `author`) VALUE ('test4', 'test4');";
$pdo->exec($sql);
$pdo->rollBack();
// 并未插入任何一条记录
1
2
3
4
5
6
7
8
9
10
11
12
13
$something = false;
$pdo->beginTransaction();
$sql = "INSERT INTO `posts` ( `title`, `author`) VALUE ('test5', 'test5');";
$pdo->exec($sql);
if ($something && $pdo->inTransaction()) {
$pdo->rollBack();
}
$sql = "INSERT INTO `posts` ( `title`, `author`) VALUE ('test6', 'test6');";
$pdo->exec($sql);
if ($pdo->inTransaction()) {
$pdo->commit();
}
// 当 $something 为 false 是 test5 和 test6 均被插入,当 $something 为 true 时只插入 test6

错误模式

PDO 实例对象的 setAttribute 方法可以设置 PDO::ATTR_ERRMODE 选项改变当 PDO 出错时的错误处理方式,包括 PDO::ERRMODE_SILENTPDO::ERRMODE_WARNINGPDO::ERRMODE_EXCEPTION。详细可见 PDO 错误与错误处理

0x02 PDOStatement 类

PDOStatement 实例可以看做是结果集,即查询结果集合的对象。最主要的功能是参数绑定和获取返回结果数据。下面给出个简单的示例,详细使用方法请看 官方手册

1
2
3
4
5
6
7
8
$statement = $pdo->prepare("SELECT `title`, `author` FROM `posts`");
$statement->bindColumn(1, $title);
$statement->bindColumn(2, $author);
$statement->execute();
while ($statement->fetch()) {
echo "$title-$author \n";
}
// 输出全部的 「标题-作者」

bindParam 和 bindValue 的区别

bindParam 的作用是将变量的引用绑定到预编译的 SQL 语句中,当绑定的变量值改变时 SQL 语句随之改变。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$statement = $pdo->prepare("INSERT INTO `posts` (`id`, `title`, `author`) VALUES (:id, :title, :author)");
$id = 11;
$title = 'new post';
$author = 'unknown';
$statement->bindParam(':id', $id, PDO::PARAM_INT);
$statement->bindParam(':title', $title, PDO::PARAM_STR);
$statement->bindParam(':author', $author, PDO::PARAM_STR);
$statement->execute();
$id = 12;
$title = 'new post2';
$author = 'unknown2';
$statement->execute();
// 执行代码后查询数据库显示如下
// +----+-----------+----------+
// | id | title | author |
// +----+-----------+----------+
// | 1 | new post | unknown |
// | 2 | new post2 | unknown2 |
// +----+-----------+----------+

警告:由于 bindParam 是绑定的是变量的引用,所以在 foreach 中会出现意料之外的 BUG。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
$data = [':title' => 'title', ':author' => 'author'];
$statement = $pdo->prepare("INSERT INTO `posts` (`title`, `author`) VALUES (:title, :author)");
foreach ($data as $key => $val) {
$statement->bindParam($key, $val);
}
$statement->execute();
// 执行代码后查询数据库显示如下
// +----+--------+--------+
// | id | title | author |
// +----+--------+--------+
// | 1 | author | author |
// +----+--------+--------+

为何 title 列的值会变成 author 呢?原因是 foreach 在循环过程中 $val 变量的值在不断的变化,当循环结束后 $val 的值即为数组中最后一个元素的值,而 bindParam 方法绑定的是引用而不是值,所以此时所有的占位符绑定的都是循环结束后的 $val 变量值,因此就有了这个结果。 解决这个问题我们可以用 bindValue 方法实现。

bindValue 的作用是绑定值到预编译的 SQL 语句中,一经绑定 SQL 语句就固定不变。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
$data = [':title' => 'title', ':author' => 'author'];
$statement = $pdo->prepare("INSERT INTO `posts` (`title`, `author`) VALUES (:title, :author)");
foreach ($data as $key => $val) {
$statement->bindValue($key, $val);
}
$statement->execute();
// 执行代码后查询数据库显示如下
// +----+--------+--------+
// | id | title | author |
// +----+--------+--------+
// | 1 | title | author |
// +----+--------+--------+

这样就解决了这个问题,当然我们还可以在 foreach 中使用引用的方式解决这个问题,但是不推荐。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
$data = [':title' => 'title', ':author' => 'author'];
$statement = $pdo->prepare("INSERT INTO `posts` (`title`, `author`) VALUES (:title, :author)");
foreach ($data as $key => &$val) {
$statement->bindParam($key, $val);
}
$statement->execute();
// 执行代码后查询数据库显示如下
// +----+--------+--------+
// | id | title | author |
// +----+--------+--------+
// | 1 | title | author |
// +----+--------+--------+

0x03 总结

PDO 的使用不算难,因为比起 ORM 来说封装程度不算高所以学起来也简单。最主要掌握建立连接、事务的使用、SQL 语句查询、预编译语句的参数绑定、获取数据的格式和错误处理等就可以了。遇到问题和一些细节的地方还是推荐查 官方手册