【MySQL】之 SQL 优化案例

前言

我们在开发过程中难免会遇到慢 SQL 的问题,常常需要我们去优化 SQL 以提高效率,就是我们常说的 SQL 调优。在这里,我记录一些实际开发过程中解决过的慢 SQL 问题,作为一些 SQL 优化案例。

案例一:有条件推翻小表驱动大表的原则


场景:

在一个电商项目中,对目标用户信息进行筛选分组分类,脱敏 SQL 如下:

SELECT z.zoneid,
       z.zonename,
       ...
FROM zone z
LEFT JOIN affiliates s ON s.affiliateid = z.affiliateid
LEFT JOIN new_pub p ON p.id = s.new_pub_id
LEFT JOIN agency a ON a.agencyid = p.agency_id 
WHERE z.enabled = 1
  AND s.enabled = 1
  AND p.enabled = 1
  AND a.active = 1
 GROUP BY z.zoneid

问题分析:

  • 通过研究 MySQL 语句的执行计划,会发现查询语句需要找出所有 zone 表的记录,并且以 zone-id 进行分组聚合。
  • 通过执行计划的反馈,此 SQL 语句使用了 using temporary 以及 using filesort。这块是一个性能损失明显的信号。
  • 但观察执行计划,MySQL 引擎使用 “小表驱动大表的原则”,直接调整了 join 表之间的执行顺序。将 agency (它的记录是1000+条记录)设置为驱动表,而 zone(130W+条记录)调整为被驱动表。
  • 这个原则对于很多普通 MySQL 查询语句没有问题。在我们场景下,需要推翻这个原则,才能达成优化。原因很简单,我们这个场景最后需要以 zone-id 进行分组聚合,这是导致 using temporary和 using filesort 的原因。换言之,如果能确保 zone 是一个驱动表,并且使用它的 primary 索引进行查询(它的输出结果本身就是有序列的),那么后续的 join 查询,就可以避免 using temporary 和 using filesort 的额外负担。
  • 而事实证明,返回54W条记录,优化前查询花费 18 秒,优化后查询时间 3.5 秒。

优化后:

SELECT z.zoneid,
       z.zonename,
       ...
FROM zone z
STRAIGHT_JOIN affiliates s ON s.affiliateid = z.affiliateid
LEFT JOIN new_pub p ON p.id = s.new_pub_id
LEFT JOIN agency a ON a.agencyid = p.agency_id 
WHERE z.enabled = 1
  AND s.enabled = 1
  AND p.enabled = 1
  AND a.active = 1
 GROUP BY z.zoneid

问题总结

  • 本案例优化的目标是消除由 group by zone-id 执行语句中引入的 using temporary 与 using file-sort。
  • MySQL的优化器证明也不是万能的。它始终坚守一个 小表驱动大表原则,结果将大表 zone 调度为被驱动表,从而额外引入了 using temporary 与 using filesort 的步骤,延长的查询语句的执行时间。
  • 我们需要在特殊的场景来有条件地推翻小表驱动大表原则

案例二:使用区分度大的字段作为索引


场景:

在一次日志与配置的查询业务当中,有个复杂的联合查询,脱敏 SQL 如下:

SELECT `a`.`bannerId` AS `ad_id` FROM `new_bn` AS `a`
INNER JOIN `upload_config` AS `uc` ON uc.id = '2337'
LEFT JOIN `upload_log` AS `ul` ON ul.config_id = uc.id
AND ul.output_id IS NULL
AND ul.updated =
  (SELECT MAX(updated)
   FROM upload_log
   WHERE config_id = ul.config_id
     AND ad_id = ul.ad_id
     AND output_id IS NULL)
WHERE (a.bannertype = 1)
  AND (a.filename NOT REGEXP "^https?:\/\/")
UNION
SELECT `a`.`bannerId` AS `ad_id` FROM `new_bn` AS `a`
INNER JOIN `upload_config` AS `uc` ON uc.id = '2337'
LEFT JOIN `upload_log` AS `ul` ON ul.config_id = uc.id
AND ul.output_id = at.id
AND ul.updated =
  (SELECT MAX(updated)
   FROM upload_log
   WHERE config_id = ul.config_id
     AND ad_id = ul.ad_id
     AND output_id = at.id)
WHERE (a.bannertype = 1)
  AND (a.filename NOT REGEXP "^https?:\/\/");

问题分析:

  • 这个 SQL 优化问题花了几个小时来排查定位。
  • 我们知道级联 join 表查询,最内层表记录数目如果很大,并且索引区分度很底的话,与外层循环做迪卡尔积的效率是非常低的。
  • 而通过执行计划可以发现,最内层 upload-log 表使用了 output_id 索引,这张表有 5W+ 条记录。由于 output-id 字段是允许为 null 的,所以它作为索引本质上区分度是非常低的

优化:

  • 通过观察查询语句,我们发现 config-id 与 output-id 在至少二个 join 表中有重复使用,二者联合索引的区分度也是明显高于单独一个 output-id 索引的区分度。
  • 所以我们引入这个复合索引 <config_id, output_id>,带来的好处如下:
    • 复合索引的区分度比以前要高;
    • using whereusing index condition,这个会将 config-id 与 output-id 的查询进行索引下推到 InnoDB 的存储引擎,极大提升效率;
    • 二张 join 表从旧索引切换到新索引。
  • 经过优化之后,从之前查询花费 25 秒,到查询时间 1.8 秒

问题总结:

  • 引入 ICP 索引下推;
  • 选择区分度(选择率)大的列建立索引。组合索引中,区分度(选择率)大的字段放在最前面。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL SQL优化是指对MySQL数据库的查询语句进行优化,以提高查询效率和性能。下面是一个MySQL SQL优化案例: 假设有一个订单表(order)和一个商品表(product),订单表有订单ID、订单时间和订单金额等字段,商品表有商品ID、商品名称、商品价格等字段。现在要查询订单表中某个时间段的订单总金额,以及该时间段内销售额最高的商品。 初始查询语句如下: ``` SELECT SUM(order_amount) AS total_amount, product_name FROM order JOIN product ON order.product_id = product.product_id WHERE order_time BETWEEN '2022-01-01 00:00:00' AND '2022-01-31 23:59:59' GROUP BY product_id ORDER BY total_amount DESC LIMIT 1; ``` 这个查询语句的问题在于,在JOIN操作时没有使用索引,导致查询效率低下。为了提升性能,可以对查询语句进行优化优化后的查询语句如下: ``` SELECT SUM(order_amount) AS total_amount, product_name FROM order JOIN product ON order.product_id = product.product_id WHERE order_id IN ( SELECT order_id FROM order WHERE order_time BETWEEN '2022-01-01 00:00:00' AND '2022-01-31 23:59:59' ) GROUP BY product_id ORDER BY total_amount DESC LIMIT 1; ``` 优化后的查询语句在子查询中使用了索引,将order_time的条件先筛选出符合要求的订单ID,再使用该子查询进行JOIN操作,可以提高查询效率。同时,由于只需要查询销售额最高的商品,使用LIMIT 1可以限制结果集的大小,减少数据处理的负担。 通过这个优化案例,可以看到,MySQL SQL优化需要根据具体需求进行,合理使用索引、子查询、LIMIT等操作,以提高查询效率和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值