数据库基于成本决定是否走索引

155 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天

通过前面的案例,我们可以看到,查询数据可以直接在聚簇索引上进行全表扫描,也可以走二级索引扫描后到聚簇索引回表。看到这里,你不禁要问了,MySQL 到底是怎么确定走哪种方案的呢。

其实,MySQL 在查询数据之前,会先对可能的方案做执行计划,然后依据成本决定走哪个执行计划。

这里的成本,包括 IO 成本和 CPU 成本:

image.png

那么,MySQL 是实时统计这些信息的吗?其实并不是,MySQL 维护了表的统计信息,可以使用下面的命令查看:

SHOW TABLE STATUS LIKE 'person'

image.png

  • 可以看到:总行数是 100086 行(之前 EXPLAIN 时,也看到 rows 为 100086)。你可能说,person 表不是有 10 万行记录吗,为什么这里多了 86 行?其实,MySQL 的统计信息是一个估算,其统计方式比较复杂我就不再展开了。但不妨碍我们根据这个值估算 CPU 成本,是 100086*0.2=20017 左右。

  • 数据长度是 4734976 字节。对于 InnoDB 来说,这就是聚簇索引占用的空间,等于聚簇索引的页面数量 * 每个页面的大小。InnoDB 每个页面的大小是 16KB,大概计算出页面数量是 289,因此 IO 成本是 289 左右。所以,全表扫描的总成本是 20306 左右。

接下来,我还是用 person 表这个例子,和你分析下 MySQL 如何基于成本来制定执行计划。现在,我要用下面的 SQL 查询 name>‘name84059’ AND create_time>‘2020-01-24 05:00:00’


EXPLAIN SELECT * FROM person WHERE NAME >'name84059' AND create_time>'2020-01-24 05:00:00'

其执行计划是全表扫描:

image.png 只要把 create_time 条件中的 5 点改为 6 点就变为走索引了,并且走的是 create_time 索引而不是 name_score 联合索引:

image.png

我们可以得到两个结论:

  • MySQL 选择索引,并不是按照 WHERE 条件中列的顺序进行的;

  • 即便列有索引,甚至有多个可能的索引方案,MySQL 也可能不走索引。

其原因就是,MySQL 并不是猜拳决定是否走索引的,而是根据成本来判断的。虽然表的统计信息不完全准确,但足够用于策略的判断了。

不过,有时会因为统计信息的不准确或成本估算的问题,实际开销会和 MySQL 统计出来的差距较大,导致 MySQL 选择错误的索引或是直接选择走全表扫描,这个时候就需要人工干预,使用强制索引了。比如,像这样强制走 name_score 索引:

EXPLAIN SELECT * FROM person FORCE INDEX(name_score) WHERE NAME >'name84059' AND create_time>'2020-01-24 05:00:00' 

我们介绍了 MySQL 会根据成本选择执行计划,也通过 EXPLAIN 知道了优化器最终会选择怎样的执行计划,但 MySQL 如何制定执行计划始终是一个黑盒。那么,有没有什么办法可以了解各种执行计划的成本,以及 MySQL 做出选择的依据呢?

image.png