公司有个SQl根据时间判断是否变化进而更新到主表通知下游服务告知变化
首先看下优化前和优化后的sql:
优化前:执行很慢
UPDATE vrs_video
SET updated_at = now()
WHEREvrs_video.id IN (SELECT DISTINCTvideo_id FROMvrs_play_source WHEREvrs_play_source.updated_at > DATE_ADD( NOW(), INTERVAL - 5 MINUTE ))
优化后:速度很快
UPDATE vrs_video v
INNER JOIN (SELECT DISTINCTvideo_id FROMvrs_play_source WHEREvrs_play_source.updated_at > DATE_ADD( NOW(), INTERVAL - 5 MINUTE )) AS subquery ON v.id = subquery.video_id SET v.updated_at = NOW()
说明:vrs_video是视频表、vrs_play_source 是播放源表 如果播放源表变化那就更新到 视频表中,最后通过查询 视频表的变化 通知下游
实际上业务比这个复杂不止这一个sql,一个视频 他有剧集表、分类表、人物表、标签表等。需要把每个副表的变化都更新到主表,然后主表拿到更新的视频ID去通知下游。
执行顺序比较
优化前的SQL:
我们看下他的执行计划
执行顺序:
-
扫描主表
首先,数据库引擎扫描 vrs_video 表。根据执行计划,它使用了 PRIMARY 索引,可能会遍历大量行(计划中显示约 5702245 行)。 -
执行依赖子查询
对于 vrs_video 表中的每一行,都会对子查询select distinct video_id from vrs_play_source where vrs_play_source.updated_at > DATE_ADD(NOW(),INTERVAL -5 MINUTE)
进行求值。执行计划中显示该子查询为 “DEPENDENT SUBQUERY”,这意味着它与外层查询有关联(尽管查询本身没有引用外层字段,但 MySQL 优化器可能判定其依赖性),可能在每行判断时重复计算或部分重复计算。
-
条件判断与更新
如果当前 vrs_video.id 在子查询返回的 video_id 集合中,则满足条件,接着执行更新操作,将 updated_at 字段设置为 NOW()。
优化后的SQl执行顺序
-
构造派生表(子查询)
首先,执行派生子查询select distinct video_id from vrs_play_source where vrs_play_source.updated_at > DATE_ADD(NOW(),INTERVAL -5 MINUTE)
该子查询的结果会被物化成一个临时表(或称派生表),通常数据量较小(如执行计划显示只有几行)。
-
JOIN 连接
使用派生表与 vrs_video 表进行内连接,连接条件为 v.id = subquery.video_id。由于 vrs_video 表的主键索引被利用(执行计划中显示为 eq_ref),连接过程非常高效。 -
更新操作
对于连接成功的记录,直接更新 vrs_video 表中的 updated_at 字段为 NOW()。
总结
-
第一个 SQL:
执行顺序为先扫描 vrs_video 表,然后对每一行执行依赖子查询进行判断,最后更新满足条件的记录。由于子查询被标记为“DEPENDENT”,可能会在外层每行判断时多次执行,从而影响效率。 -
第二个 SQL:
执行顺序是先独立执行子查询,将满足条件的 video_id 物化为一个临时表,再将该结果与 vrs_video 表进行高效的索引连接,最后更新匹配的记录。整体流程较清晰,且子查询只执行一次。
这种执行顺序的差异是性能差异的重要原因之一,尤其是在数据量较大的情况下,第二种写法往往会更高效。