数据库里存了一堆数据,你总得把它捞出来用。PHP里捞数据就靠SELECT语句,配合MySQLi或者PDO这两个扩展。
老规矩,mysql_query()那一套别用了。PHP 5.5开始就标记过时,后面直接砍了。现在正经干活就用MySQLi(面向过程或面向对象)和PDO。
本文用到的两个函数:
-
mysqli_num_rows() — 查查查出来多少条记录
-
mysqli_fetch_assoc() — 把当前行转成关联数组,数组的键就是表的字段名,没有更多行的时候返回NULL
SELECT语句长啥样
SELECT 字段1, 字段2 FROM 表名;
想把所有字段都捞出来,用星号:
SELECT * FROM 表名;
一个真实建议: 实际项目里尽量别用SELECT *。字段多了影响查询效率,而且你根本用不到所有字段。我们之前有个接口,一张表50多个字段,前端只用了5个,DBA找过来骂了一顿。明确写字段名,性能更好,代码也更清晰。
示例数据表
演示用的两张表:
表1:employees(员工表)
| id | name | salary |
|---|---|---|
| 1 | 张明 | 9000 |
| 2 | 李芳 | 40000 |
| 3 | 王磊 | 90000 |
表2:MyGuests(访客表)
| id | firstname | lastname |
|---|---|---|
| 1 | Peter | Parker |
| 2 | John | Rambo |
| 3 | Clark | Kent |
MySQLi面向过程查询
这是最基础的写法,适合小型脚本和快速测试。
<?php
$host = 'localhost:3306';
$user = 'root';
$pass = '123456';
$dbname = 'company_db';
$conn = mysqli_connect($host, $user, $pass, $dbname);
if (!$conn) {
die('连不上数据库:' . mysqli_connect_error());
}
$sql = 'SELECT id, name, salary FROM employees';
$result = mysqli_query($conn, $sql);
if (mysqli_num_rows($result) > 0) {
while ($row = mysqli_fetch_assoc($result)) {
echo "工号:" . $row['id'] . "<br>";
echo "姓名:" . $row['name'] . "<br>";
echo "工资:" . $row['salary'] . "<br>";
echo "-------------------<br>";
}
} else {
echo "没有查到数据";
}
mysqli_close($conn);
?>
输出结果:
工号:1
姓名:张明
工资:9000
-------------------
工号:2
姓名:李芳
工资:40000
-------------------
工号:3
姓名:王磊
工资:90000
-------------------
原始表数据:
| id | name | salary |
|---|---|---|
| 1 | 张明 | 9000 |
| 2 | 李芳 | 40000 |
| 3 | 王磊 | 90000 |
查询后得到的数据和原表一样,因为是全量查询。
个人经验: 面向过程写法简单直接,但多人协作时容易出问题。比如你和同事都定义了$conn变量,后面的人可能不知道前面已经用过。面向对象和PDO在这一点上更清晰。
MySQLi面向对象查询
<?php
$servername = "localhost";
$username = "root";
$password = "123456";
$dbname = "company_db";
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("连接失败:" . $conn->connect_error);
}
$sql = "SELECT id, firstname, lastname FROM MyGuests";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
echo "<table border='1'>";
echo "<tr><th>ID</th><th>姓名</th></tr>";
while ($row = $result->fetch_assoc()) {
echo "<tr>";
echo "<td>" . $row["id"] . "</td>";
echo "<td>" . $row["firstname"] . " " . $row["lastname"] . "</td>";
echo "</tr>";
}
echo "</table>";
} else {
echo "没有查到数据";
}
$conn->close();
?>
原始表(MyGuests):
| id | firstname | lastname |
|---|---|---|
| 1 | Peter | Parker |
| 2 | John | Rambo |
| 3 | Clark | Kent |
输出表格:
| ID | 姓名 |
|---|---|
| 1 | Peter Parker |
| 2 | John Rambo |
| 3 | Clark Kent |
说一下区别: 面向对象的写法更接近自然语言,$conn->query()一眼就知道是执行查询。而且面向对象可以配合命名空间、自动加载这些现在PHP特性,大型项目里更好维护。
PDO方式查询
PDO较大的优势是数据库抽象层。你的代码写成这样,以后从MySQL换成PostgreSQL或者SQLite,改一行连接字符串就行。
<?php
$servername = "localhost";
$username = "root";
$password = "123456";
$dbname = "company_db";
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $conn->prepare("SELECT id, firstname, lastname FROM MyGuests");
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo "<table border='1'>";
echo "<tr><th>ID</th><th>名</th><th>姓</th></tr>";
foreach ($result as $row) {
echo "<tr>";
echo "<td>" . $row['id'] . "</td>";
echo "<td>" . $row['firstname'] . "</td>";
echo "<td>" . $row['lastname'] . "</td>";
echo "</tr>";
}
echo "</table>";
} catch (PDOException $e) {
echo "查询出错:" . $e->getMessage();
}
$conn = null;
?>
原始表(MyGuests):
| id | firstname | lastname |
|---|---|---|
| 1 | Peter | Parker |
| 2 | John | Rambo |
| 3 | Clark | Kent |
输出结果:
| ID | 名 | 姓 |
|---|---|---|
| 1 | Peter | Parker |
| 2 | John | Rambo |
| 3 | Clark | Kent |
个人见解: 新项目我基本都用PDO。不是说MySQLi不好,而是PDO的错误处理更优雅。上面的try-catch可以统一捕获异常,不需要每个查询都写if判断。另外PDO支持12种数据库驱动,万一公司哪天要换数据库,代码改动量很小。
fetch模式说明:
| 模式 | 作用 |
|---|---|
| PDO::FETCH_ASSOC | 返回关联数组,键是字段名 |
| PDO::FETCH_NUM | 返回索引数组,键是数字 |
| PDO::FETCH_BOTH | 同时返回关联和索引(默认) |
| PDO::FETCH_OBJ | 返回对象,属性名是字段名 |
WHERE条件过滤数据
项目开发中很少查全表,更多是按条件筛选。WHERE子句就是干这个的。
SELECT * FROM employees WHERE salary > 30000;
原始表(employees):
| id | name | salary |
|---|---|---|
| 1 | 张明 | 9000 |
| 2 | 李芳 | 40000 |
| 3 | 王磊 | 90000 |
执行查询后:
| id | name | salary |
|---|---|---|
| 2 | 李芳 | 40000 |
| 3 | 王磊 | 90000 |
工资低于30000的张明被过滤掉了。
代码号学习编程示例 - 带WHERE条件的PDO查询:
<?php
try {
$pdo = new PDO("mysql:host=localhost;dbname=company_db", "root", "123456");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$min_salary = 30000;
$stmt = $pdo->prepare("SELECT id, name, salary FROM employees WHERE salary > :min_salary");
$stmt->bindParam(':min_salary', $min_salary);
$stmt->execute();
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($results as $emp) {
echo $emp['name'] . " - " . $emp['salary'] . "<br>";
}
} catch (PDOException $e) {
echo "查询失败:" . $e->getMessage();
}
?>
常见WHERE条件组合:
-- 等值条件
SELECT * FROM users WHERE status = 'active';
-- 范围条件
SELECT * FROM orders WHERE amount BETWEEN 100 AND 500;
-- 模糊匹配
SELECT * FROM products WHERE name LIKE '%手机%';
-- 多条件
SELECT * FROM employees WHERE department = '技术部' AND salary > 15000;
-- 日期条件
SELECT * FROM logs WHERE created_at > '2026-01-01';
为什么SELECT查询如此重要
大部分应用的核心功能就是展示数据。管理后台的列表页、前端的商品展示、报表系统的统计图表、API接口的JSON输出,背后都是SELECT语句在支撑。
不同场景选不同的技术栈:
-
简单脚本或WordPress插件开发 → MySQLi面向过程
-
中型项目、团队协作 → MySQLi面向对象
-
跨数据库项目、注重安全性 → PDO
-
微服务架构 → PDO + 连接池
本节课程知识要点
-
明确字段名代替SELECT *** —— 减少不必要的数据传输,提高查询效率
-
WHERE条件要加索引 —— 如果经常用某个字段做过滤条件,记得建索引。我们有个订单表,一开始没在status字段建索引,全表扫描200万条数据,接口响应3秒多。加了索引之后降到50毫秒。
-
fetch模式按需选择 —— 不需要数字索引就用PDO::FETCH_ASSOC,省内存
-
预处理语句不仅用于写入 —— SELECT同样可以用预处理,配合WHERE条件防止SQL注入
-
结果集数量限制 —— 查大量数据时用LIMIT分页,别一次全查出来
// 分页查询示例
$page = 1;
$limit = 20;
$offset = ($page - 1) * $limit;
$stmt = $pdo->prepare("SELECT * FROM logs ORDER BY id DESC LIMIT :limit OFFSET :offset");
调试技巧
查不到数据的时候,先做这几步:
-
直接在数据库客户端跑一遍SQL,确认语句没问题
-
检查连接参数(主机、用户名、密码、库名)
-
打印SQL语句看看变量替换后的实际内容
-
用errorInfo()获取PDO的详细错误信息
if (!$stmt->execute()) {
print_r($stmt->errorInfo());
}