本篇为子查询优化系列第五篇,参考自 MariaDB 博客: https://mariadb.com/kb/en/loosescan-strategy/,建议先看 MySQL子查询优化 和 Semi-join Materialization 子查询优化策略
实现思路
LooseScan 是子查询 semijoin 优化的一种实现策略(还是为了去重)。下面通过一个示例来说明其实现原理,假如有个SQL要查询有卫星的国家:
select * from Country
where
Country.code in (select country_code from Satellite)
Satellite 表上的 country_code 字段有索引,使用这个索引可以得到有序的(country_code,satellite_code)数据:


执行成本
下面我们换一个查询来展示 Semi-join LooseScan 优化策略的执行计划,以及扫描行数。查询员工号小于 10277 的员工所在的部门:
select dept_name from departments where dept_no in \
(select dept_no from dept_emp where emp_no<10227);
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: dept_emp
partitions: NULL
type: index
possible_keys: PRIMARY,dept_no
key: dept_no
key_len: 4
ref: NULL
rows: 331570
filtered: 0.00
Extra: Using where; Using index; LooseScan
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: departments
partitions: NULL
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: employees.dept_emp.dept_no
rows: 1
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.01 sec)
执行计划 Extra 列中驱动表所在行会有 LooseScan 标识。其执行流程为:
- 对驱动表的关联字段 dept_no 所在索引进行全索引扫描,分组后每组取出第一行记为 R(这里扫描行数为全表行数 331570,这里即使 emp_no 字段有索引也没法使用,除非不用LooseScan策略);
- 从R 中取出 dept_no 字段值,与 departments 进行联接,符合条件的行放入结果集,这里走索引查找,每次扫描 1 行;
- 因为一共有 9 个部门,第 1 步分组后得到 9 行R,所以第 2 步重复9次,直到结束。
一共扫描行数为 331570+9*1.
小结
- 在执行计划中,LooseScan 策略显示在驱动表的 Extra 信息中 LooseScan;
- 通过参数 optimizer_switch 中的 semijoin=on 和 loosescan=on 开启 Semi-join LooseScan,默认就是开启的。
网友评论