🍰 个人主页:不摆烂的小劉
🍞文章有不合理的地方请各位大佬指正。
🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍 收藏⭐️
上篇文章 如何系统优化MySQL【表结构优化、索引优化】(上篇) 讲了表结构优化、索引优化、SQL 优化。这一篇主要讲一下SQL语句优化。
文章目录
一.联表查询
MySQL
的联接操作,MySQL
先在一个表中循环取出单条数据,然后再嵌套循环到下一个表中寻找匹配的行,依次下去,直到找到所有表中匹配的行为止。最后根据各个表匹配的行,返回查询中需要 的各列。(MySQL 8.0.20
后使用哈希联接)- 关联表的字段加索引,加快查询效率
为什么不推荐联表数量超过3个。
多表联接的时候,联接优化器可能会尝试会遍历每一个表,然后逐个做嵌套循环,选择成本最低联表顺序的来生成执行计划树。n个表的联接可能有n的阶乘种联接顺序,即使此时使用“贪婪”搜索的方式查找“最优”的联接顺序,但是联表越多往往越耗时。
二.联表查询如何高效排序?
第一种情况:ORDER BY
子句中的所有列都来自联接的第一个表
- 那么
MySQL
在联接处理第一个表的时候就进行文件排序。EXPLAIN
中Extra
显示Using filesort
其他情况
MySQL
都会先将联接的结果存放到一个临时表中,然后在所有的联接都结束后,再进行文件排序。在这种情况下,EXPLAIN
的Extra
显示Using temporary;Using filesort
。即使是LIMIT
查询,LIMIT
也会在文件排序之后应用,所以即使需要返回较少的数据,临时表和需要排序的数据量仍然会非常大
举例说明
- 建表SQL
CREATE TABLE customers
(
customer_id INT PRIMARY KEY COMMENT '顾客id',
customer_name varchar(100) COMMENT '顾客名称',
phone_number varchar(20) COMMENT '手机号'
);
INSERT INTO customers (customer_id, customer_name, phone_number) VALUES
(1, 'John Doe', '1234567890'),
(2, 'Jane Smith', '9876543210'),
(3, 'Alice Johnson', '5556667777'),
(4, 'Bob Brown', '1112223333');
CREATE TABLE orders (
order_id INT PRIMARY KEY,
restaurant_id INT,
order_no VARCHAR(50),
order_date DATE,
order_status VARCHAR(20),
customer_id INT
);
CREATE INDEX idx_customer_id ON `orders` (customer_id);
INSERT INTO orders (order_id, restaurant_id, order_no, order_date, order_status, customer_id) VALUES
(1, 101, 'ORD123', '2025-02-01', 'Completed', 1),
(2, 102, 'ORD124', '2025-02-03', 'Pending', 2),
(3, 101, 'ORD125', '2025-02-05', 'Completed', 3),
(4, 103, 'ORD126', '2025-02-06', 'Cancelled', 4),
(5, 101, 'ORD127', '2025-02-07', 'Pending', 1),
(6, 102, 'ORD128', '2025-02-08', 'Completed', 2),
(7, 103, 'ORD129', '2025-02-09', 'Completed', 3),
(8, 101, 'ORD130', '2025-02-10', 'Completed', 4);
ORDER BY
来自第一个联接表(Using filesort
)
EXPLAIN SELECT c.customer_name,c.phone_number
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
ORDER BY c.customer_name;
ORDER BY
来自第二个联接表(Using temporary; Using filesort
)
EXPLAIN SELECT c.customer_name,c.phone_number
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
ORDER BY o.order_date;
三.索引下推
在读取索引时直接应用WHERE
子句中的部分条件,而不是将所有数据返回给服务器层后再进行过滤
根据上面第二节的建表语句,再添加如下复合索引
create index idx_order_date_order_id_restaurant_id
on orders (order_date, order_id,restaurant_id);
非最左前缀列
- 根据最左前缀法则,条件中若有
order_date
和restaurant_id
。只会匹配order_date
条件再回表查询。 - 索引下推可以跳过中间列
order_id
仍能在索引层过滤数据,利用order_date
和order_id
条件
# Using index condition 说明 ICP 被启用
EXPLAIN SELECT order_id, restaurant_id, order_no, order_date, order_status, customer_id
FROM orders
WHERE order_date >= '2025-02-07'
AND restaurant_id = 101;
索引下推排序
四.查询时,需要扫描大量数据但只返回少数行时?
- 索引覆盖扫描:通过将查询中所有需要的列放入索引中,避免回表操作,直接从索引中获取数据。
- 改变库表结构:使用汇总表或预计算结果的方式。
- 重写查询:通过调整查询语句,更高效查询,例如优化连接方式或使用合适的索引。
五.不推荐使用 SELECT *
- 增加不必要的数据传输
- 避免未来表结构改变时导致的潜在问题
- 避免不小心返回敏感字段减少数据泄露的风险
六.一个复杂查询还是多个简单查询?
复杂查询:
- 优点:较少的客户端与数据库之间的交互次数,信息密度高
- 缺点:需求变更,后期维护成本相比较高
- 使用场景: 数据量不大,关联简单
多个简单查询:
- 优点: 易于理解和维护;单个查询可以减少锁的竞争;应用层做联接,可以更容易对数据库进行拆分
- 缺点:多次查询会导致更高的网络延迟、
- 使用场景:大数据量,且需要分页、分批次处理
七.IN()
替代OR
?
MySQL
将IN()
列表中的数据先进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个O(logn)
复杂度的 操作,等价地转换成OR
查询的复杂度为O(n)
,对于IN()
列表中有大量取值的时候,MySQL
的处理速度将会更快。
八.LIMIT
分页优化
分页操作通常会使用LIMIT
加上偏移量的办法实现,在偏移量非常大的时候,可能是LIMIT 1000,20
这样的查询前面10000条记录都将被抛弃,这样的代价非常高
select restaurant_id, order_no, order_date, order_status from `orders` LIMIT 10000, 10;
子查询优化
限制需要检索的 order_id
范围
# 查询分页数据第一条数据id
select restaurant_id, order_no, order_date, order_status from orders
where order_id >= (select order_id from orders LIMIT 1000000, 1)
LIMIT 10;
延迟关联 优化
子查询 subquery
来限制范围,再将orders
表与子查询的结果关联
select o.restaurant_id, o.order_no, o.order_date, o.order_status
from orders o
join (
select order_id
from orders
order by order_id
LIMIT 10000, 10
) as subquery on o.order_id = subquery.order_id;
九.为什么推荐UNION ALL
而不是UNION
?
UNION
会去除重复的记录,MySQL
需要执行额外的 排序 或 哈希操作,增加计算和存储成本。需要去重且查询数据量不大UNION ALL
返回所有的记录,包括重复行。适用于查询结果中不可能有重复项,或者不在乎是否去重需要去重且查询数据量不大,UNION
是适当的选择
参考:
https://dev.MySQL.com/doc/refman/5.7/en/
《阿里巴巴java开发手册》
《高性能MySQL(第四版)》
MySQL高性能优化规范建议总结
🍉文章不定期持续更新,如果我的文章对你有帮助 关注🙏🏻 👍点赞👍 收藏⭐️