1. MySQL查询基础回顾
作为一名常年与数据库打交道的开发者,我深知查询操作是MySQL最核心也最频繁使用的功能。在实际项目中,大约80%的数据库操作都是查询,而剩下的20%才是增删改。今天我想系统梳理一下MySQL表查询的进阶技巧,这些都是在实际项目中经过验证的高效方法。
MySQL的SELECT语句看似简单,但其中蕴含的优化空间和技巧却不少。从最基本的字段选择到复杂的多表关联,每个环节都有值得注意的细节。特别是在处理大数据量表时,一个不经意的查询可能会成为系统性能瓶颈。
提示:在开始复杂查询前,建议先用EXPLAIN分析查询执行计划,这是避免性能问题的第一步。
2. 基础查询的进阶用法
2.1 精确查询与范围查询
最基本的WHERE子句其实有很多使用技巧。比如在查询条件中使用BETWEEN...AND...时,MySQL会优先使用索引,这比分别使用>=和<=更高效。我曾在处理一个百万级用户表时,通过这个简单的调整将查询时间从1.2秒降到了0.3秒。
sql复制-- 不推荐
SELECT * FROM users WHERE age >= 18 AND age <= 30;
-- 推荐
SELECT * FROM users WHERE age BETWEEN 18 AND 30;
对于枚举类型的查询,IN()操作符比多个OR条件更清晰且性能更好。但要注意IN列表中的值不宜过多,一般建议不超过100个,否则会影响查询优化器的决策。
2.2 模糊查询的优化
LIKE查询是性能杀手,特别是前导通配符查询(如'%keyword')无法使用索引。但在实际业务中,模糊查询又是刚需。我的经验是:
- 尽量避免前导通配符
- 对于必须使用的情况,考虑使用全文索引
- 或者引入专门的搜索引擎如Elasticsearch
sql复制-- 无法使用索引
SELECT * FROM products WHERE name LIKE '%手机%';
-- 可以使用索引
SELECT * FROM products WHERE name LIKE '小米%';
3. 聚合查询与分组统计
3.1 GROUP BY的陷阱
GROUP BY是数据分析的利器,但使用不当会导致严重的性能问题。我曾经遇到一个分组查询拖垮整个数据库的案例,原因是分组字段选择不当。
关键原则:
- 分组字段应该是有索引的
- 避免在大文本字段上分组
- 考虑先WHERE过滤再分组
sql复制-- 不推荐:在无索引的大文本字段上分组
SELECT content, COUNT(*) FROM articles GROUP BY content;
-- 推荐:在索引字段上分组
SELECT category_id, COUNT(*) FROM articles GROUP BY category_id;
3.2 HAVING与WHERE的区别
很多开发者混淆HAVING和WHERE的使用场景。简单来说:
- WHERE在分组前过滤行
- HAVING在分组后过滤组
sql复制-- 查询订单数超过5笔的客户
SELECT customer_id, COUNT(*) as order_count
FROM orders
WHERE status = 'completed' -- 先筛选已完成订单
GROUP BY customer_id
HAVING order_count > 5; -- 再筛选订单数>5的客户
4. 多表关联查询技巧
4.1 JOIN类型选择
MySQL支持多种JOIN类型,每种都有其适用场景:
- INNER JOIN:只返回匹配的行(最常用)
- LEFT JOIN:返回左表所有行,右表不匹配则为NULL
- RIGHT JOIN:与LEFT JOIN相反(较少使用)
- FULL JOIN:MySQL不直接支持,需用UNION模拟
注意:在多表关联时,确保关联字段有索引,这是影响性能的关键因素。
4.2 关联查询优化
我处理过一个典型的性能问题:一个三表关联查询执行时间超过10秒。通过分析发现问题是关联顺序不当和缺少索引。优化后的查询降至0.5秒。
优化技巧:
- 小表驱动大表原则
- 为关联字段建立索引
- 避免在关联条件中使用函数
- 考虑使用STRAIGHT_JOIN强制关联顺序
sql复制-- 优化前(性能差)
SELECT * FROM large_table l
JOIN small_table s ON DATE(l.create_time) = s.date_column;
-- 优化后
SELECT * FROM small_table s
JOIN large_table l ON l.create_time >= s.date_column
AND l.create_time < s.date_column + INTERVAL 1 DAY;
5. 子查询与派生表
5.1 子查询性能对比
子查询可以分为相关子查询和非相关子查询,性能差异很大:
- 非相关子查询:子查询可以独立执行,性能较好
- 相关子查询:子查询依赖外部查询,性能较差
sql复制-- 非相关子查询(性能较好)
SELECT * FROM products
WHERE category_id IN (SELECT id FROM categories WHERE type = 'electronics');
-- 相关子查询(性能较差)
SELECT * FROM orders o
WHERE EXISTS (SELECT 1 FROM order_items i WHERE i.order_id = o.id AND i.quantity > 10);
5.2 派生表的使用
派生表(FROM子句中的子查询)在某些场景下非常有用,特别是需要中间结果的复杂查询:
sql复制-- 计算每个部门的平均工资及与公司平均工资的差值
SELECT
d.department_name,
d.avg_salary,
d.avg_salary - c.company_avg AS diff
FROM
(SELECT
department_id,
department_name,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id, department_name) d
CROSS JOIN
(SELECT AVG(salary) AS company_avg FROM employees) c;
6. 查询结果处理技巧
6.1 分页查询优化
分页查询是Web应用中最常见的场景之一,但LIMIT offset, size在大偏移量时性能很差。我常用的优化方法:
- 使用覆盖索引
- 记录上次查询的最大ID
- 使用JOIN优化
sql复制-- 传统分页(offset大时性能差)
SELECT * FROM products ORDER BY id LIMIT 10000, 20;
-- 优化方案1:使用覆盖索引
SELECT * FROM products
WHERE id >= (SELECT id FROM products ORDER BY id LIMIT 10000, 1)
ORDER BY id LIMIT 20;
-- [优化方案](https://taotoken.net?utm_source=general)2:记住上次的最大ID
SELECT * FROM products WHERE id > last_max_id ORDER BY id LIMIT 20;
6.2 结果排序策略
ORDER BY是另一个性能敏感点,特别是当排序字段没有索引时。处理大数据量排序的技巧:
- 为排序字段建立索引
- 考虑使用文件排序的缓冲区大小
- 避免在WHERE和ORDER BY中使用不同索引
sql复制-- 需要为user_name和create_time建立复合索引
SELECT * FROM users
WHERE user_name LIKE '张%'
ORDER BY create_time DESC;
7. 高级查询模式
7.1 窗口函数应用
MySQL 8.0引入了窗口函数,极大简化了复杂分析查询。常用的窗口函数包括:
- ROW_NUMBER(): 行号
- RANK(): 排名(有并列会跳过序号)
- DENSE_RANK(): 密集排名(有并列不跳过序号)
- NTILE(): 分组
- LEAD()/LAG(): 访问前后行
sql复制-- 计算每个部门工资排名
SELECT
employee_name,
department,
salary,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank
FROM employees;
7.2 公用表表达式(CTE)
CTE (WITH子句) 使复杂查询更易读和维护,特别是需要多次引用同一子查询时:
sql复制-- 计算各部门平均工资及与公司平均的差异
WITH dept_stats AS (
SELECT
department_id,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
),
company_avg AS (
SELECT AVG(salary) AS value FROM employees
)
SELECT
d.department_id,
d.avg_salary,
d.avg_salary - c.value AS diff
FROM dept_stats d
CROSS JOIN company_avg c;
8. 查询性能监控与优化
8.1 EXPLAIN深入解读
EXPLAIN是查询优化的必备工具,但很多人只看type和key列。我通常会关注:
- type:从最好到最差依次是 system > const > eq_ref > ref > range > index > ALL
- key:实际使用的索引
- rows:预估检查的行数
- Extra:额外信息,如"Using filesort"表示需要额外排序
sql复制EXPLAIN SELECT * FROM users WHERE username = 'admin';
8.2 慢查询日志分析
配置慢查询日志是发现性能问题的有效方法。建议设置:
sql复制-- 设置慢查询阈值(秒)
SET GLOBAL long_query_time = 1;
-- 启用慢查询日志
SET GLOBAL slow_query_log = 'ON';
分析慢查询日志时,我通常会关注:
- 执行时间最长的查询
- 扫描行数最多的查询
- 没有使用索引的查询
- 相同模式的高频查询
9. 实际案例:电商查询优化
9.1 商品搜索查询
假设我们有一个电商平台的商品表,需要实现高效搜索:
sql复制-- 原始查询(性能问题)
SELECT * FROM products
WHERE name LIKE '%手机%'
OR description LIKE '%手机%'
ORDER BY price DESC
LIMIT 0, 20;
-- 优化方案
-- 1. 添加全文索引
ALTER TABLE products ADD FULLTEXT INDEX ft_index (name, description);
-- 2. 使用全文搜索
SELECT * FROM products
WHERE MATCH(name, description) AGAINST('手机' IN BOOLEAN MODE)
ORDER BY price DESC
LIMIT 0, 20;
9.2 订单统计报表
生成月度销售报表的查询优化:
sql复制-- 原始查询
SELECT
product_id,
COUNT(*) AS order_count,
SUM(quantity) AS total_quantity,
SUM(amount) AS total_amount
FROM order_items
WHERE order_date BETWEEN '2023-01-01' AND '2023-01-31'
GROUP BY product_id;
-- 优化方案
-- 1. 确保order_date和product_id有复合索引
-- 2. 使用覆盖索引
SELECT
product_id,
COUNT(*) AS order_count,
SUM(quantity) AS total_quantity,
SUM(amount) AS total_amount
FROM order_items
WHERE order_date >= '2023-01-01'
AND order_date < '2023-02-01'
GROUP BY product_id;
在多年的MySQL使用经验中,我发现查询优化的关键在于理解数据特点和业务需求。没有放之四海而皆准的最优方案,只有最适合当前场景的解决方案。建议在优化前先明确查询的瓶颈在哪里,通过EXPLAIN和性能分析工具找出问题根源,再有针对性地进行优化。