在前一篇中,我们掌握了基本的 CRUD 操作,能够对数据进行增、查、改、删。但现实中的数据量往往很大,我们很少会一次性取出整张表的所有行,而是需要按照条件筛选、按特定顺序排列,并只返回一部分结果。这篇博客就来深入 SELECT 查询的三大进阶技能:WHERE 过滤、ORDER BY 排序和 LIMIT 分页。
读完本文你将能:
- 灵活运用
WHERE子句进行精确和模糊查询 - 理解比较、逻辑、范围、模糊匹配等多种过滤方式
- 掌握
ORDER BY实现升序、降序及多列排序 - 使用
LIMIT和OFFSET实现分页 - 理解 SELECT 语句的执行顺序
- 实战:实现博客文章列表的分页查询
1. 过滤数据:WHERE 子句
WHERE 子句用于指定查询条件,只返回满足条件的行。它紧跟在 FROM 子句之后。
1.1 比较运算符
最常见的是使用比较运算符对列的值进行筛选:
| 运算符 | 含义 |
|---|---|
= |
等于 |
<> 或 != |
不等于 |
< |
小于 |
<= |
小于等于 |
> |
大于 |
>= |
大于等于 |
示例 (假设有 articles 表,后面实战会创建):
sql
-- 查询作者为 'alice' 的所有文章
SELECT * FROM articles WHERE author = 'alice';
-- 查询阅读量大于 1000 的文章
SELECT title, views FROM articles WHERE views > 1000;
-- 查询不是草稿状态的文章(假设 status=0 表示草稿)
SELECT * FROM articles WHERE status <> 0;
对于 NULL 值的判断,不能使用 = NULL ,必须使用 IS NULL 或 IS NOT NULL:
sql
-- 查询没有填写摘要的文章
SELECT * FROM articles WHERE summary IS NULL;
-- 查询有封面图的文章
SELECT * FROM articles WHERE cover_image IS NOT NULL;
1.2 逻辑运算符:AND、OR、NOT
组合多个条件:
sql
-- 查询 'alice' 写的且阅读量大于 500 的文章
SELECT * FROM articles
WHERE author = 'alice' AND views > 500;
-- 查询作者是 'alice' 或 'bob' 的文章
SELECT * FROM articles
WHERE author = 'alice' OR author = 'bob';
-- 查询不是 'alice' 写的文章
SELECT * FROM articles
WHERE NOT author = 'alice';
优先级 :NOT > AND > OR。建议使用括号明确分组,避免歧义:
sql
-- 查询阅读量大于 1000 且作者是 'alice' 或 'bob'
SELECT * FROM articles
WHERE views > 1000 AND (author = 'alice' OR author = 'bob');
1.3 范围查询:BETWEEN 和 IN
BETWEEN ... AND ...:匹配某个范围内的值(包含边界)。IN (...):匹配列表中的任意值。
sql
-- 查询阅读量在 500 到 1000 之间的文章
SELECT title, views FROM articles
WHERE views BETWEEN 500 AND 1000;
-- 等价于:WHERE views >= 500 AND views <= 1000
-- 查询作者是 'alice'、'bob'、'carol' 之一的文章
SELECT * FROM articles
WHERE author IN ('alice', 'bob', 'carol');
注意:BETWEEN 对于日期类型同样适用。
sql
SELECT * FROM articles
WHERE publish_date BETWEEN '2025-01-01' AND '2025-03-31';
1.4 模糊匹配:LIKE
LIKE 用于模式匹配,通常配合通配符:
%匹配任意多个字符(包括0个)_匹配单个任意字符
sql
-- 查询标题以 'MySQL' 开头的文章
SELECT * FROM articles
WHERE title LIKE 'MySQL%';
-- 查询标题包含 '索引' 的文章
SELECT * FROM articles
WHERE title LIKE '%索引%';
-- 查询作者第二个字母是 'l' 的文章(如 alice, blake)
SELECT * FROM articles
WHERE author LIKE '_l%';
LIKE 对大小写的敏感性取决于列的字符集校对规则。默认的 utf8mb4_general_ci(ci 表示 case insensitive)是不区分大小写的。如果需要区分,可使用 BINARY 关键字或修改列的校对规则为 utf8mb4_bin。
性能提醒 :LIKE '%xxx%' 这种前缀通配会导致全表扫描,在数据量大时应谨慎使用,可以考虑全文索引(FULLTEXT)。
1.5 正则表达式:REGEXP
除了 LIKE,MySQL 还支持正则表达式匹配,功能更强大:
sql
-- 查询标题以数字结尾的文章
SELECT * FROM articles WHERE title REGEXP '[0-9]$';
-- 查询作者名包含 'a' 或 'e' 的文章
SELECT * FROM articles WHERE author REGEXP 'a|e';
正则匹配默认不区分大小写,如需区分可使用 REGEXP BINARY。
2. 排序数据:ORDER BY
查询结果默认顺序是不确定的(通常按照插入顺序或主键顺序,但这不能保证)。使用 ORDER BY 可以指定排序规则。
2.1 基本排序
sql
SELECT title, views FROM articles ORDER BY views; -- 升序(默认)
SELECT title, views FROM articles ORDER BY views ASC; -- 明确升序
SELECT title, views FROM articles ORDER BY views DESC; -- 降序
2.2 多列排序
可以指定多个排序条件,优先级从左到右:
sql
-- 先按作者升序排列,同一作者的按阅读量降序排列
SELECT author, title, views
FROM articles
ORDER BY author ASC, views DESC;
2.3 与 WHERE 结合
ORDER BY 应放在 WHERE 之后:
sql
SELECT * FROM articles
WHERE status = 1
ORDER BY publish_date DESC;
3. 分页查询:LIMIT 与 OFFSET
当结果集很大时,我们需要分页展示。MySQL 使用 LIMIT 子句限制返回的行数。
3.1 LIMIT 基本用法
sql
-- 返回前 10 篇文章
SELECT * FROM articles LIMIT 10;
3.2 带偏移量的分页
语法:LIMIT 偏移量, 行数 或 LIMIT 行数 OFFSET 偏移量。
sql
-- 返回第 11 到 20 条(即第二页,每页 10 条)
SELECT * FROM articles LIMIT 10, 10;
-- 等价写法:
SELECT * FROM articles LIMIT 10 OFFSET 10;
偏移量基于 0,即 OFFSET 0 代表第一行。
3.3 分页性能注意
LIMIT 结合大 OFFSET 时,MySQL 实际上会扫描并丢弃前面的所有行,可能导致性能问题。常见的优化方式包括使用覆盖索引、记录上一页的最大 ID 进行条件查询等,这些我们将在索引优化部分详细讨论。
4. SELECT 语句的执行顺序
理解 SELECT 各子句的执行顺序,可以帮助你写出正确的 SQL,避免"别名在 WHERE 中不可用"等错误。
一个完整的 SELECT 语句如下(省略了一些暂未学到的子句):
sql
SELECT [DISTINCT] 列1, 列2, ...
FROM 表名
[WHERE 条件]
[GROUP BY 列]
[HAVING 分组过滤]
[ORDER BY 列 [ASC|DESC]]
[LIMIT [偏移,] 行数];
逻辑执行顺序(非物理读取顺序):
FROM:确定数据来源WHERE:过滤行GROUP BY:分组HAVING:过滤分组SELECT:选择列(包括别名在此阶段生效)ORDER BY:排序(可以使用 SELECT 中的别名)LIMIT:限制返回行数
因此,你不能在 WHERE 子句中使用 SELECT 中定义的别名,因为 WHERE 执行时别名尚未被定义。但可以在 ORDER BY 中使用别名,因为 ORDER BY 在 SELECT 之后执行。
5. 实战:实现博客文章列表的分页查询
我们模拟一个博客系统的文章表,并用之前学到的过滤、排序和分页来查询。
5.1 创建文章表并插入测试数据
sql
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author VARCHAR(50) NOT NULL,
views INT UNSIGNED DEFAULT 0,
status TINYINT UNSIGNED DEFAULT 1 COMMENT '1:发布 0:草稿',
publish_date DATETIME DEFAULT CURRENT_TIMESTAMP,
summary TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO articles (title, author, views, status, publish_date, summary) VALUES
('MySQL 安装指南', 'alice', 1520, 1, '2025-03-01 10:00:00', 'Windows 与 Linux 安装详解'),
('数据库与表的诞生', 'alice', 980, 1, '2025-03-02 12:30:00', '创建数据库及数据类型'),
('CRUD 入门', 'bob', 2100, 1, '2025-03-03 09:15:00', '数据的增删改查'),
('查询进阶', 'bob', 850, 1, '2025-03-04 14:00:00', '排序、过滤与分页'),
('索引初探', 'carol', 3200, 1, '2025-03-05 16:45:00', '让查询快起来'),
('事务入门', 'dave', 670, 1, '2025-03-06 08:20:00', '数据一致性的保证'),
('JDBC 编程', 'alice', 430, 1, '2025-03-07 11:10:00', 'Java 连接 MySQL'),
('连接池技术', 'carol', 590, 1, '2025-03-08 13:55:00', '提升应用性能'),
('MySQL 架构解析', 'dave', 2800, 1, '2025-03-09 17:30:00', '架构与存储引擎'),
('InnoDB 物理结构', 'dave', 1800, 1, '2025-03-10 10:05:00', '表空间与页'),
('锁机制详解', 'bob', 1500, 1, '2025-03-11 07:50:00', '表锁与行锁'),
('MVCC 原理', 'carol', 2200, 1, '2025-03-12 20:00:00', '无锁读的艺术');
5.2 查询需求与 SQL
需求 1:查询"alice"写的所有已发布文章,按阅读量降序
sql
SELECT title, views, publish_date
FROM articles
WHERE author = 'alice' AND status = 1
ORDER BY views DESC;
需求 2:搜索标题包含"MySQL"的文章,按发布日期升序,只显示前5条
sql
SELECT title, author, publish_date
FROM articles
WHERE title LIKE '%MySQL%'
ORDER BY publish_date ASC
LIMIT 5;
需求 3:实现文章列表分页,每页 5 条,请求第 2 页
sql
SELECT title, author, views, publish_date
FROM articles
WHERE status = 1
ORDER BY publish_date DESC
LIMIT 5 OFFSET 5; -- 第2页:跳过前5条,取后5条
需求 4:查询阅读量在 1000 到 2000 之间且作者不是 'bob' 的文章
sql
SELECT title, author, views
FROM articles
WHERE views BETWEEN 1000 AND 2000
AND author <> 'bob';
5.3 扩展练习
- 查询作者名以
c开头的所有文章,并按标题降序排列。 - 查询发布日期在 2025-03-05 之后的文章,按阅读量升序,取前 3 条。
- 将之前我们创建的用户表
users拿出来,自己设计几个查询条件试试。
6. 小结
本文深入介绍了 SELECT 查询的三大利器:
- WHERE:通过比较、逻辑、范围、模糊匹配筛选行,使用 IS NULL 处理空值。
- ORDER BY:对结果集进行单列或多列排序。
- LIMIT:限制返回行数,结合 OFFSET 实现分页。
我们还了解了 SELECT 各子句的逻辑执行顺序,这对于编写复杂查询非常重要。最后,通过创建文章表并完成多个查询,实际感受了过滤、排序和分页的组合使用。
下一篇将是第一阶段的项目实战篇,我们将综合前四篇文章的知识,搭建一个本地图书管理系统的数据库,动手设计表、插入数据并编写查询。敬请期待!
思考题:
- 如果查询条件
WHERE author = 'alice' AND status = 1中,author列有索引而status列没有,索引能起作用吗?(提示:思考索引的最左匹配原则,先留个悬念) LIMIT 100000, 20为什么可能会很慢?你有什么优化思路?- 尝试用
EXPLAIN查看上述查询的执行计划(EXPLAIN SELECT ...),看看type、rows等字段有什么不同。
参考资料
- MySQL 8.0 Reference Manual - SELECT Statement
- MySQL 8.0 Reference Manual - WHERE Clause
- MySQL 8.0 Reference Manual - ORDER BY Optimization
- MySQL 8.0 Reference Manual - LIMIT Optimization