第5章 5.3 mysqli 面向过程与面向对象
⚠️ 本文是 PHP 技术文,由于原任务模板要求写成 Python 教程,但 mysqli 是 PHP 的 MySQL 扩展,下文将以 PHP 示例讲解 mysqli 的两种编程风格。
🎯 开场 3 分钟:为什么要学这个?
上一章我们学会了用 PDO 预处理来防 SQL 注入,感觉数据库操作终于安全了。
但是,你有没有遇到过这种尴尬场景:
- 接手一个老项目,满屏
mysql_connect()、mysql_query()这种老式写法,看不懂 - 想封装一个数据库工具类,但不知道该用面向过程还是面向对象
- 面试被问到「mysqli 两种写法有什么区别」,卡壳了
这就是本章要解决的核心问题:
mysqli 的两种编程风格——面向过程和面向对象——到底怎么选?什么时候用哪种?
学完本文,你能:
1. 读懂任何老项目里的 mysqli 代码
2. 根据场景选择合适的编程风格
3. 封装一个自己的数据库工具类
\n\n
\n\n
\n\n
🧱 基础 25 分钟:核心概念
5.3.1 什么是「面向过程」和「面向对象」?
先不说术语,用点外卖举个例子:
面向过程就像你直接给骑手打电话:「去这家店,取这个菜,送这个地方」。一步接一步,你在下达指令。
面向对象就像你把骑手看成一个「人」对象,他有自己的属性(名字、车牌),也有自己的方法(取餐、送餐)。你只管说「帮我送」,他自己知道怎么做。
mysqli 两种写法,本质上就是这两种思维的体现。
5.3.2 面向过程写法
<?php
// 第一步:建立连接(像打电话给数据库)
$conn = mysqli_connect('localhost', 'root', '123456', 'test_db');
// 第二步:执行查询
$result = mysqli_query($conn, "SELECT * FROM users WHERE age > 18");
// 第三步:逐行读取结果
while ($row = mysqli_fetch_array($result)) {
echo "姓名:" . $row['name'] . ",年龄:" . $row['age'] . "<br>";
}
// 第四步:关闭连接
mysqli_close($conn);
?>
解释:每一步都是一个函数调用,像流水线一样按顺序执行。
5.3.3 面向对象写法
<?php
// 第一步:创建数据库对象
$mysqli = new mysqli('localhost', 'root', '123456', 'test_db');
// 第二步:执行查询(用对象的方法)
$result = $mysqli->query("SELECT * FROM users WHERE age > 18");
// 第三步:逐行读取结果
while ($row = $result->fetch_array()) {
echo "姓名:" . $row['name'] . ",年龄:" . $row['age'] . "<br>";
}
// 第四步:关闭连接
$mysqli->close();
?>
解释:所有操作都绑定到一个 $mysqli 对象上,调用它的方法来完成操作。
5.3.4 两种写法的核心对比
| 操作 | 面向过程 | 面向对象 |
|---|---|---|
| 连接数据库 | mysqli_connect(...) |
new mysqli(...) |
| 执行查询 | mysqli_query($conn, ...) |
$mysqli->query(...) |
| 获取一行 | mysqli_fetch_array($result) |
$result->fetch_array() |
| 关闭连接 | mysqli_close($conn) |
$mysqli->close() |
| 获取错误信息 | mysqli_error($conn) |
$mysqli->error |
规律:面向对象的写法更简洁,函数名直接变成对象的方法,省掉了第一个连接参数。
5.3.5 为什么要学两种?
- 老项目遍地开花:网上 80% 的教程和老项目用的是面向过程
- 封装更方便:自己想封装工具类,用面向对象更清晰
- 面试必问:两种都懂,说明你基础扎实
🔥 实战 35 分钟:3 个递进的小项目
项目 1(5 分钟):连接数据库并查询
场景:查询学生表中分数大于 90 分的学生
<?php
// 面向对象写法
$mysqli = new mysqli('localhost', 'root', '123456', 'school');
// 检查连接
if ($mysqli->connect_error) {
die('连接失败:' . $mysqli->connect_error);
}
// 执行查询
$query = "SELECT name, score FROM students WHERE score > 90 ORDER BY score DESC";
$result = $mysqli->query($query);
echo "=== 分数大于 90 分的学生 ===<br>";
while ($row = $result->fetch_array()) {
echo "姓名:" . $row['name'] . ",分数:" . $row['score'] . "<br>";
}
// 释放结果集
$result->free();
// 关闭连接
$mysqli->close();
echo "查询完成!";
?>
预期输出:
=== 分数大于 90 分的学生 ===
姓名:李明,分数:98
姓名:王芳,分数:95
姓名:张三,分数:92
查询完成!
一句话解释:创建 mysqli 对象 → 发 SQL 查询 → 遍历结果 → 清理资源。
项目 2(15 分钟):用 CSV 数据批量导入数据库
场景:读取一个 CSV 文件,把学生信息批量导入数据库
首先,准备一个 students.csv 文件:
name,age,score
小明,16,88
小红,15,92
小华,17,78
小丽,16,95
小刚,18,85
然后,用面向过程的写法实现:
<?php
// 面向过程写法:从 CSV 读数据,批量插入数据库
$conn = mysqli_connect('localhost', 'root', '123456', 'school');
if (!$conn) {
die('连接失败:' . mysqli_connect_error());
}
// 读取 CSV 文件
$csv_file = 'students.csv';
$handle = fopen($csv_file, 'r');
$count = 0;
$first_line = true; // 跳过表头
while (($data = fgetcsv($handle)) !== false) {
// 跳过第一行表头
if ($first_line) {
$first_line = false;
continue;
}
$name = mysqli_real_escape_string($conn, $data[0]);
$age = intval($data[1]);
$score = intval($data[2]);
$sql = "INSERT INTO students (name, age, score) VALUES ('$name', $age, $score)";
if (mysqli_query($conn, $sql)) {
$count++;
} else {
echo "插入失败:" . $data[0] . "<br>";
}
}
fclose($handle);
mysqli_close($conn);
echo "成功导入 $count 条记录!";
?>
预期输出:
成功导入 5 条记录!
一句话解释:用 fgetcsv 逐行读 CSV,用 mysqli_real_escape_string 防注入,最后批量执行插入。
项目 3(15 分钟):封装一个自己的数据库工具类
场景:把数据库操作封装成一个类,以后用起来更方便
<?php
/**
* 数据库工具类
* 封装常用的数据库操作
*/
class Database {
private $host = 'localhost';
private $user = 'root';
private $pass = '123456';
private $dbname = 'school';
private $conn;
// 构造方法:自动连接数据库
public function __construct() {
$this->conn = new mysqli($this->host, $this->user, $this->pass, $this->dbname);
if ($this->conn->connect_error) {
throw new Exception('数据库连接失败:' . $this->conn->connect_error);
}
$this->conn->set_charset('utf8mb4');
}
// 执行查询,返回所有结果
public function query($sql) {
$result = $this->conn->query($sql);
if (!$result) {
throw new Exception('查询失败:' . $this->conn->error);
}
$rows = [];
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
$rows[] = $row;
}
$result->free();
return $rows;
}
// 执行插入,返回插入的 ID
public function insert($sql) {
if ($this->conn->query($sql)) {
return $this->conn->insert_id;
}
throw new Exception('插入失败:' . $this->conn->error);
}
// 关闭连接
public function close() {
$this->conn->close();
}
// 析构方法:对象销毁时自动关闭连接
public function __destruct() {
if ($this->conn) {
$this->conn->close();
}
}
}
// ============ 使用示例 ============
try {
$db = new Database();
// 查询所有学生
echo "=== 所有学生 ===<br>";
$students = $db->query("SELECT * FROM students ORDER BY score DESC");
foreach ($students as $student) {
echo $student['name'] . " - " . $student['score'] . "分<br>";
}
// 插入一个新学生
$new_id = $db->insert("INSERT INTO students (name, age, score) VALUES ('小杰', 17, 91)");
echo "<br>新插入的学生 ID:" . $new_id . "<br>";
// 再次查询验证
echo "<br>=== 插入后的学生 ===<br>";
$students = $db->query("SELECT * FROM students WHERE name = '小杰'");
foreach ($students as $s) {
echo $s['name'] . " - " . $s['score'] . "分<br>";
}
$db->close();
echo "<br>操作完成!";
} catch (Exception $e) {
echo '错误:' . $e->getMessage();
}
?>
预期输出:
=== 所有学生 ===
李明 - 98分
王芳 - 95分
小丽 - 95分
小杰 - 91分
张三 - 92分
...
新插入的学生 ID:15
=== 插入后的学生 ===
小杰 - 91分
操作完成!
一句话解释:封装成类后,数据库操作变得简单——连接自动完成,查询直接返回数组,不用关心底层细节。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:忘记检查连接是否成功
// ❌ 错误:直接使用,不检查
$mysqli = new mysqli('localhost', 'root', 'wrongpassword', 'test_db');
$mysqli->query("SELECT * FROM users"); // 连接失败时会报错
// ✅ 正确:检查连接错误
$mysqli = new mysqli('localhost', 'root', 'wrongpassword', 'test_db');
if ($mysqli->connect_error) {
die('连接失败:' . $mysqli->connect_error);
}
坑 2:混淆 fetch_array 和 fetch_assoc
// ❌ 错误:混合使用导致数据混乱
$row = $result->fetch_array();
echo $row[0]; // 用索引访问,有时对有时错
// ✅ 正确:明确用哪种方式获取
$row = $result->fetch_assoc(); // 返回关联数组
echo $row['name']; // 用字段名访问,更清晰
// 或者
$row = $result->fetch_array(); // 返回索引+关联混合数组
echo $row['name']; // 也可以用字段名,但浪费内存
坑 3:循环后不释放结果集
// ❌ 错误:多次查询不释放,可能内存溢出
while (true) {
$result = $mysqli->query("SELECT * FROM logs LIMIT 1000");
// 处理数据...
// 没有 $result->free();
}
// ✅ 正确:每次循环结束释放
while (true) {
$result = $mysqli->query("SELECT * FROM logs LIMIT 1000");
// 处理数据...
$result->free(); // 释放内存
}
坑 4:字符串拼接 SQL 而不是预处理
// ❌ 错误:直接拼接,容易被 SQL 注入
$name = $_POST['name'];
$sql = "SELECT * FROM users WHERE name = '$name'";
// ✅ 正确:使用预处理语句(详见上一章)
$stmt = $mysqli->prepare("SELECT * FROM users WHERE name = ?");
$stmt->bind_param("s", $name);
$stmt->execute();
坑 5:面向过程忘记传连接参数
// ❌ 错误:忘记第一个参数是连接
$result = mysqli_query("SELECT * FROM users"); // 缺少 $conn
// ✅ 正确:第一个参数必须是连接
$result = mysqli_query($conn, "SELECT * FROM users");
性能小贴士:使用预编译语句提高效率
如果一条 SQL 要执行多次,用预编译语句可以复用执行计划:
// 普通方式:每次都要解析 SQL
for ($i = 0; $i < 100; $i++) {
$mysqli->query("INSERT INTO logs (msg) VALUES ('log_$i')");
}
// 预编译方式:解析一次,执行 100 次
$stmt = $mysqli->prepare("INSERT INTO logs (msg) VALUES (?)");
$stmt->bind_param("s", $msg);
for ($i = 0; $i < 100; $i++) {
$msg = "log_$i";
$stmt->execute();
}
调试技巧:用 var_dump 查看查询结果
$result = $mysqli->query("SELECT * FROM users LIMIT 1");
// 直接打印看看结构
$row = $result->fetch_array();
var_dump($row); // 打印出完整的数组结构
// 输出:
// array(8) {
// [0]=> string(5) "admin"
// ["name"]=> string(5) "admin"
// [1]=> string(32) "password123"
// ["password"]=> string(32) "password123"
// ...
// }
✏️ 练习题 + 作业题
练习题(5 道,10 分钟内完成)
练习 1(2 分钟):改连接参数
- 输入:修改下面的连接参数,把数据库名改成 my_database
- 预期输出:能正常连接 my_database
- 提示:new mysqli('localhost', 'root', 'pass', '这里改')
$mysqli = new mysqli('localhost', 'root', '123456', 'test_db');
练习 2(2 分钟):加一个条件判断
- 输入:在查询结果循环中,加一个 if 判断,只打印分数大于 85 的学生
- 预期输出:只显示分数 > 85 的学生
- 提示:if ($row['score'] > 85) { ... }
练习 3(2 分钟):改用面向过程
- 输入:把下面的面向对象代码改成面向过程写法
- 预期输出:功能完全相同
- 提示:把 $mysqli->query() 改成 mysqli_query($conn, ...)
$mysqli = new mysqli('localhost', 'root', '123456', 'school');
$result = $mysqli->query("SELECT * FROM students");
while ($row = $result->fetch_array()) {
echo $row['name'] . "<br>";
}
练习 4(2 分钟):批量删除数据
- 输入:基于项目 2 的 CSV 导入,改成从数据库删除数据
- 预期输出:删除指定条件的学生
- 提示:把 INSERT 改成 DELETE,条件用 WHERE name = '$name'
练习 5(2 分钟):分析报错
- 输入:下面的代码为什么会报错?
- 预期输出:说出错误原因并修复
- 提示:检查连接参数的顺序
$conn = mysqli_connect('test_db', 'root', '123456', 'localhost');
// 报错:Database selection failed
作业题(30 分钟 - 2 小时)
作业:做一个「学生信息管理系统」
- 需求描述:用 mysqli(面向对象)实现一个简单的学生信息管理工具
- 功能点:
1. 显示所有学生列表(姓名、年龄、分数)
2. 按分数排序(升序/降序切换)
3. 添加新学生(姓名、年龄、分数)
4. 删除指定学生 - 加分项:
1. 用预处理语句防注入
2. 给操作加确认提示 - 验收标准:
- 能跑起来
- CRUD 操作都正常工作
- 代码有适当注释
- 提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
本文学到的 3 个核心点
- 面向过程就是函数调用链,像流水线一样执行
- 面向对象把所有操作封装成方法,更易复用和扩展
- 两种写法可以混用,根据项目和个人习惯选择
延伸学习资源
- PHP 官方 mysqli 文档
- 《PHP 核心技术与最佳实践》:第 5 章数据库部分
- 视频:慕课网《PHP 进阶》mysqli 专题
互动钩子
你在维护老项目时,是怎么适应面向过程的 mysqli 写法的?有没有什么小技巧?评论区聊聊,老粉优先回复!
下期预告:学会了 mysqli 两种写法,你会发现每次查询都要新建连接很麻烦。下一章「5.4 数据库连接池与 ORM」,我们来聊聊怎么复用连接、怎么用 ORM 少写 SQL!

评论(0)