1. MySQL单表查询基础概念
作为数据库操作中最频繁使用的功能,单表查询是每个开发者必须掌握的技能。DQL(Data Query Language)是SQL语言中专门用于数据查询的部分,它允许我们从数据库中提取所需信息而不改变数据本身。
在实际项目中,我见过太多因为查询语句不当导致的性能问题。一个简单的SELECT *操作,在数据量增长后可能成为系统瓶颈。因此,理解单表查询的各个组成部分及其执行逻辑至关重要。
1.1 基础查询结构解析
最基本的查询语句包含两个必选部分:
sql复制SELECT 字段列表 FROM 表名
这里的字段列表可以是:
- 具体列名(如name, price)
- 星号*(表示所有列)
- 计算表达式(如price*0.9)
- 函数调用(如UPPER(name))
实际经验:生产环境中应避免使用SELECT *,明确列出所需字段不仅能减少网络传输量,还能让查询意图更清晰。当表结构变更时,显式字段列表也能避免意外错误。
1.2 字段别名的实用技巧
给字段起别名有三种等效写法:
sql复制SELECT name AS 商品名称, price AS 商品价格 FROM sp;
SELECT name '商品名称', price '商品价格' FROM sp;
SELECT name 商品名称, price 商品价格 FROM sp;
别名在以下场景特别有用:
- 字段名本身不易理解时
- 查询包含计算字段时
- 多表联查存在同名字段时
- 前端展示需要友好名称时
我在电商项目中就遇到过价格字段需要同时显示原价和折扣价的情况:
sql复制SELECT
name 商品名称,
original_price 原价,
discount_price 折扣价,
ROUND((original_price-discount_price)/original_price*100,1) 折扣率
FROM products
2. 条件查询的深度应用
WHERE子句是过滤数据的利器,但许多开发者对其理解停留在表面。让我们深入探讨各种条件表达式的使用场景和注意事项。
2.1 比较运算符的陷阱
常见的比较运算符包括:=, >, <, >=, <=, !=, <>。使用时需注意:
- 字符串比较默认不区分大小写(取决于数据库配置)
- NULL值的比较必须使用IS NULL/IS NOT NULL
- 日期比较要考虑格式一致性
sql复制-- 错误示例:无法正确筛选NULL值
SELECT * FROM orders WHERE cancel_reason = NULL;
-- 正确写法
SELECT * FROM orders WHERE cancel_reason IS NULL;
2.2 逻辑运算符的组合艺术
AND、OR、NOT的组合使用可以实现复杂逻辑,但要注意优先级:
- NOT优先级最高
- AND次之
- OR最低
当逻辑复杂时,使用括号明确优先级是明智之举:
sql复制-- 查询价格在1000-3000之间的自营商品,或评分大于4.8的非自营商品
SELECT * FROM sp
WHERE (is_self = '自营' AND price BETWEEN 1000 AND 3000)
OR (is_self = '非自营' AND score > 4.8);
2.3 模糊查询的性能优化
LIKE操作符配合通配符可以实现灵活匹配:
- % 匹配任意数量字符
- _ 匹配单个字符
但要注意:
- 前导通配符(如'%pro')无法使用索引
- 考虑使用全文索引替代大量模糊查询
- 对确定长度的模式匹配,优先使用_
sql复制-- 使用索引的情况(固定前缀)
SELECT * FROM products WHERE name LIKE '华为%';
-- 无法使用索引的情况
SELECT * FROM products WHERE name LIKE '%Pro%';
3. 排序与分页的实战技巧
3.1 多列排序策略
ORDER BY支持多列排序,这在处理并列情况时特别有用:
sql复制-- 先按评分降序,评分相同再按价格升序
SELECT * FROM sp ORDER BY score DESC, price ASC;
实际项目中,我常用这种技巧处理:
- 商品列表(销量+评分)
- 订单列表(状态+创建时间)
- 用户活跃度(登录次数+最后登录时间)
3.2 分页查询的工业级方案
LIMIT基本语法:
sql复制SELECT * FROM table LIMIT offset, count;
但在大数据量分页时,直接使用大offset会导致性能问题。优化方案包括:
- 使用索引覆盖扫描:
sql复制SELECT * FROM sp WHERE id >= (SELECT id FROM sp ORDER BY id LIMIT 10000,1) LIMIT 10;
- 记录上次查询的边界值:
sql复制-- 假设上页最后一条记录的id是1024
SELECT * FROM sp WHERE id > 1024 ORDER BY id LIMIT 10;
- 使用游标分页(适合无限滚动场景)
4. 聚合与分组的高级用法
4.1 聚合函数的注意事项
常用聚合函数:COUNT(), SUM(), AVG(), MAX(), MIN()
特别注意:
- COUNT(*) vs COUNT(列名):前者统计所有行,后者忽略NULL
- 聚合函数忽略NULL值(AVG只计算非NULL值)
- DISTINCT可与聚合函数组合使用
sql复制-- 计算不同价格商品的数量
SELECT COUNT(DISTINCT price) FROM sp;
4.2 分组查询的深层理解
GROUP BY将数据划分为多个组,每个组返回一行结果。关键点:
- SELECT中的非聚合字段必须出现在GROUP BY中
- WHERE在分组前过滤,HAVING在分组后过滤
- 可以按表达式分组
sql复制-- 按价格区间分组统计
SELECT
CASE
WHEN price < 1000 THEN '低价'
WHEN price < 3000 THEN '中价'
ELSE '高价'
END AS price_range,
COUNT(*) AS product_count
FROM sp
GROUP BY price_range;
5. 执行顺序的底层原理
SQL语句的实际执行顺序与书写顺序不同,理解这点对优化查询至关重要:
- FROM:确定数据来源
- WHERE:初步过滤数据
- GROUP BY:分组数据
- 聚合函数:计算分组统计值
- HAVING:过滤分组结果
- SELECT:选择输出列
- DISTINCT:去重
- ORDER BY:排序结果
- LIMIT:限制返回行数
这个顺序解释了为什么:
- WHERE中不能使用列别名
- HAVING中可以使用聚合函数
- ORDER BY可以引用SELECT中的别名
6. 性能优化实战建议
根据多年经验,我总结出单表查询优化的几个关键点:
-
索引策略:
- WHERE和ORDER BY中的列优先考虑索引
- 避免在索引列上使用函数
- 多列条件考虑组合索引
-
查询设计:
- 只查询需要的列
- 分页查询避免大offset
- 复杂查询考虑拆分为多个简单查询
-
数据类型匹配:
- 比较时保持类型一致
- 避免隐式类型转换
-
执行计划分析:
- 使用EXPLAIN分析查询
- 关注全表扫描和临时表使用
sql复制-- 示例:分析查询执行计划
EXPLAIN SELECT * FROM sp WHERE price > 3000 ORDER BY score DESC;
7. 常见错误排查指南
在教学中我发现新手常犯以下错误:
-
混淆NULL和空字符串:
sql复制-- 错误 SELECT * FROM users WHERE phone = NULL; -- 正确 SELECT * FROM users WHERE phone IS NULL; -
GROUP BY遗漏非聚合字段:
sql复制-- 错误(sql_mode=only_full_group_by时) SELECT name, AVG(price) FROM sp; -- 正确 SELECT name, AVG(price) FROM sp GROUP BY name; -
误用HAVING过滤非聚合条件:
sql复制-- 低效写法 SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING user_id > 100; -- 优化写法 SELECT user_id, COUNT(*) FROM orders WHERE user_id > 100 GROUP BY user_id; -
LIKE通配符滥用导致全表扫描:
sql复制-- 无法使用索引 SELECT * FROM products WHERE name LIKE '%手机%';
掌握单表查询是数据库操作的基石,希望这些实战经验能帮助你避开我当年踩过的坑。记住,每个查询都应该考虑:正确性、可读性、性能这三个维度。