Bootstrap

如何系统优化MySQL【SQL语句优化】(下篇)

🍰 个人主页:不摆烂的小劉
🍞文章有不合理的地方请各位大佬指正。
🍉文章不定期持续更新,如果我的文章对你有帮助➡️ 关注🙏🏻 点赞👍 收藏⭐️

在这里插入图片描述
上篇文章 如何系统优化MySQL【表结构优化、索引优化】(上篇) 讲了表结构优化、索引优化、SQL 优化。这一篇主要讲一下SQL语句优化。

一.联表查询

  • MySQL的联接操作,MySQL先在一个表中循环取出单条数据,然后再嵌套循环到下一个表中寻找匹配的行,依次下去,直到找到所有表中匹配的行为止。最后根据各个表匹配的行,返回查询中需要 的各列。(MySQL 8.0.20后使用哈希联接)
  • 关联表的字段加索引,加快查询效率
为什么不推荐联表数量超过3个。

多表联接的时候,联接优化器可能会尝试会遍历每一个表,然后逐个做嵌套循环,选择成本最低联表顺序的来生成执行计划树。n个表的联接可能有n的阶乘种联接顺序,即使此时使用“贪婪”搜索的方式查找“最优”的联接顺序,但是联表越多往往越耗时。

二.联表查询如何高效排序?

第一种情况:ORDER BY子句中的所有列都来自联接的第一个表

  • 那么MySQL在联接处理第一个表的时候就进行文件排序。EXPLAINExtra显示Using filesort

其他情况

  • MySQL都会先将联接的结果存放到一个临时表中,然后在所有的联接都结束后,再进行文件排序。在这种情况下,EXPLAINExtra显示Using temporary;Using filesort。即使是LIMIT查询,LIMIT也会在文件排序之后应用,所以即使需要返回较少的数据,临时表和需要排序的数据量仍然会非常大

举例说明

  1. 建表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);
  1. 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;
  1. 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);
非最左前缀列
  1. 根据最左前缀法则,条件中若有order_daterestaurant_id 。只会匹配order_date条件再回表查询。
  2. 索引下推可以跳过中间列order_id仍能在索引层过滤数据,利用order_dateorder_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?

MySQLIN()列表中的数据先进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个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高性能优化规范建议总结

🍉文章不定期持续更新,如果我的文章对你有帮助 关注🙏🏻 👍点赞👍 收藏⭐️

;