工作中的小故事之索引
神奇的现象
对于上述 sql,where 条件其实永远是 true,但是当我们将 where 条件去掉以后,发现执行时间从 0.9s 飙升到 9s 。
SELECT
count(1) AS total
FROM
(
SELECT
b.user,
b.db_name,
a.session_id,
a.gate_id
FROM
general_metrics AS a
LEFT JOIN session_metrics AS b ON a.session_id = b.session_id
AND a.gate_id = b.gate_id
WHERE
(
'' = ''
OR sql_id LIKE concat('%', '', '%')
OR sql_template LIKE concat('%', '', '%')
OR user LIKE concat('%', '', '%')
OR db_name LIKE concat('%', '', '%')
)
GROUP BY
a.sql_id
);
为什么?
通过 explain 展示此 sql 的执行计划(已处理),如下所示:
└─ LookupJoin | LeftJoin, Join Condition: LogicalAnd(ComparisonEqual(#1, #30), ComparisonEqual(#2, #31))
├─ *TableScan | {select a.session_id, a.gate_id, a.sql_id, a.sql_template from general_metrics as a}
└─ *TableScan | {}
可以看到,此 sql 在 KunDB 中使用了 lookup join。lookup join 是一种 join 算法实现,会根据左表的数据构造过滤条件应用到右表上,从而减少右表在网络上的传输量,以达到性能优化的目的。在 lookup join 实现中,对于左表的每批次数据,会按照 join key,本例中是 session_id 和 gate_id, 排序去重,如果左表是按照 join key 排好序的,lookup 的次数也就越少,性能越好。
关键的一列
在本例中,性能差的 lookup join,即去掉 where 条件后,生成的左表查询语句是 select a.session_id, a.gate_id, a.sql_id from general_metrics as a
,相比于性能好的少查询了一列 sql_template。正是少查询了一列 sql_template
导致性能慢了 10 倍。
本例中,对于 general_metrics 表,其主键是: “GATE_ID”, “SESSION_ID”, “SQL_ID”, “START_TIME”,表上还存在一个 SQLID(session_id,gate_id,sql_id) 索引。
当查询 session_id,gate_id,sql_id 时,mysql 会使用 SQLID 索引,返回的数据是按照 sql_id 排序的;如果需要额外查询 sql_template 时,mysql 则不能使用 SQLID 索引,改为使用全表扫描,返回的数据是按照 gate_id, session_id 排序的。这对于 mysql 来说,选取的是最优的执行计划。
对于 KunDB 而言,局部最优却导致了全局变差:
当选择 SQLID 索引扫描数据时,返回给 lookup join 的数据就是乱序的。lookup join 希望按照 session_id 和 gate_id 排序,如此去重效果才会好;另外一方面,使用主键扫描,返回的数据是按照 gate_id, session_id 排序,lookup join 排序去重效果好。去重效果越好,对右表的过滤率越高,需要将右表数据取回本地的比例就越小,性能就越好。所以,根因还是减小了网络上传输的右表的数据量。