MySQL 是怎样运行的:单表访问方法及基于成本的优化

1. 单表访问方法

访问方法:执行查询的方式

只需于选取代价最小的那种访问方式去执行单表查询语句即可。

1.1 const

主键值或者唯一的二级索引定位一条记录(与常数等值比较时才有效)

需要注意的是,使用唯一二级索引查询列为 NULL 值时不会使用 const 访问方法来执行,使用 ref 访问方式。

key IS NULL

1.2 ref

  1. 普通二级索引与常数进行等值比较。

  2. 二级索引允许存储 NULL 值时,执行包含 key IS NULL

  3. 二级索引中包含多个列时,只有最左边连续的列与常数进行等值比较,就可以使用 ref 访问方法

1.3 ref_or_null

二级索引等于某个常数,会有列值为 NULL 的记录,即
key1 = 'abc' OR key1 IS NULL
值为 NULL 的记录会被放在索引最左边。

1.4 range

使用索引查询时,扫描区间为若干个单点扫描区间(IN)或者范围扫描区间。

注意:不包含仅一个单点扫描区间,并且这里的扫描区间不能为 ( − ∞ , + ∞ ) (-\infty,+\infty) (,+)

1.5 index

扫描全部二级索引记录的访问方法(不用执行回表),即需要查询的数据及搜索条件均在索引列中

特例:人为规定;通过全表扫描执行查询,添加了 ORDER BY 主键

1.6 all

全表扫描,直接扫描全部的聚簇索引记录

1.7 索引合并

一般情况下只会为单个索引生成扫描空间,但也可能为多个索引生成扫描空间。

这里的合并是,先根据二级索引得到二级索引记录,先不进行回表,等合并后,再进行回表操作。

(1)Intersection 索引合并

要求从每个索引中获取到的二级索引记录都是按照主键值排序的。

带来的好处是:两个有序数组求交/并集或去重比较容易;按主键排序后进行回表操作容易形成顺序 IO。

这对二级索引来说,相当于要求等值查询,而非范围查询,并且需要给定所有的索引列。

例如WHERE key1 = 'a' AND id > 9000,对聚簇索引,本身就是按照主键顺序排序的,可以是范围查询,因为二级索引是包含索引列和主键的,在索引列值相同的情况下,是按主键排序的,只需扫描二级索引即可,并不需要扫描聚簇索引。

(2)Union 索引合并

要求从每个索引中获取到的二级索引记录都是按照主键值排序的。

例如WHERE key1 = 'a' OR id > 9000,此时需要扫描聚簇索引

可以组合 Intersection 与 Union 索引合并

(3)Sort-Union 索引合并

针对单独根据搜索条件从某个二级索引中获取记录比较少的情况。

很多情况下无法保证二级索引记录按照主键排序,例如范围扫描,此时要进行额外的排序工作。先获取二级索引记录,按照主键值进行排序后再进行合并。

2. 连接

涉及单表的条件

内外连接的根本区别为:在驱动表中不符合 ON 子句中的连接条件时,是否会加入到最后的结果集中。

(1)每获得一条驱动表(外循环中的表)记录,就立即到被驱动表(内循环中的表)中寻找匹配的记录。

对驱动表而言,会使用只涉及驱动表的条件来进行单表访问。

WHERE 子句中的过滤条件,不论是内连接还是外连接,不符合的记录都不会被加入到最后的结果集。

ON 子句中的过滤条件,专门针对外连接,驱动表中的记录都会被加入到最后的结果集。

对外连接而言,必须使用 ON 子句来指出连接条件。

(2)左(外)连接,LEFT (OUTER) JOIN,左侧的为驱动表;

右(外)连接,RIGHT (OUTER) JOIN,右侧的为驱动表;

内连接,推荐书写为 INNER JOIN

2.1 连接算法

基于块的嵌套循环连接,涉及到 Join Buffer。先把驱动表结果集放在Join Buffer 中,每一条被驱动表记录可以一次性匹配多条驱动表结果集中的记录;需要注意的是: Join Buffer 中不会存放驱动表记录的所有列,只有查询列表中列和过滤条件中的列。

3. 基于成本的优化

MySQL 的执行成本:IO 成本和 CPU 成本。

读取一个页面花费成本为 1.0;
读取及检测一条记录是否符合搜索条件的成本默认为 0.2;

3.1 优化步骤

(1)找出可能使用的索引

索引列与常数使用=、<=>、IN、NOT IN、IS NULL、IS NOT NULL、>、<、>=、<=、BETWEEN、!=(<>)、LIKE 进行前缀匹配时

(2)计算全表扫描的代价(通过查看表的一系列统计信息)

SHOW TABLE STATUS LIKE '表名';

ROW:表中的记录数,对 MyISAM 表来说,该值是准确的。对 InnoDB 来说,是估计值。
Data_length;表占用的字节数。对 MyISAM 表来说,该值是数据文件的大小;对 InnoDB 来说,是聚簇索引占用的存储空间大小。

(3)计算使用不同索引执行查询的代价

MySQL 会先分析使用唯一二级索引的成本,再分析使用普通索引的成本。

对使用二级索引 + 回表的方式,成本主要为:

  1. 扫描区间的数量:认为一个扫描区间的 IO 成本与读取一个页面相同。

  2. 需要回表的记录数:会在 B+ 数中找到区间左右端点记录,统计它们之间的数量。(即使对单点扫描区间也会这样做)
    当相隔不超过 10 个页面时,会根据每页中 Page Header 的 PAGE_N_RECS进行累加。相距太远,也会沿着最左记录向右读 10 个页面,计算平均数量,再乘相隔页面数。

  3. 每次回表操作都相当于访问一个页面。根据上述需要回表的记录数。

  4. 回表操作完成后,再检测其他搜索条件。根据上述需要回表的记录数。

(4)注意事项

在计算二级索引成本的第 2 步时,会少量访问 B+ 数中的数据,这种计算方式称为 index dive。

单点扫描区间大于等于eq_range_index_dive_limit时,不会使用 index dive 的计算方式,使用索引统计数据进行估算。

SHOW INDEX FROM 索引名;主要使用 Cardinality 信息。
单个值的重复次数 Rows / Cardinality ,单个值的重复次数 * 单点扫描区间数量 = 需要回表的记录数

3.2 Join 成本

单次查询驱动表的成本;查询得到的记录条数称为扇出(fanout);

扇出值 × 查询被驱动表的成本

对内连接来说,可以选择左侧或右侧是驱动表;

对被驱动表而言,确定好驱动表后连接条件可以确定,例如s1.key1 = 常数,但在优化过程中是无法得知具体的常数值,那么如何进行估算?这里也是采用统计数据中的单个值的重复次数。

多表 Join,需要考虑连接顺序,采用了一些提前结束某种连接顺序评估等启发式规则。

4. InnoDB 统计数据

innodb_stats_persistent永久性/非永久性地存储统计数据,默认为ON

InnoDB 默认以表为单位来收集和存储统计数据,在创建表时可以指定该表统计数据的存储方式。

… Engine=InnoDB, STATS_PERSISTENT = (1 | 0); 存储到 磁盘 | 内存,默认与innodb_stats_persistent相同
… Engine=InnoDB, STATS_SAMPLE_PAGES = 数量;n_rows统计时的采样数量,默认与innodb_stats_persistent_sample_pages相同

4.1 基于磁盘的永久性统计数据

实际上是把统计数据存储到两个表中:innodb_table_stats 和 innodb_index_stats;

(1)innodb_table_stats ;每条记录对应一个表的统计数据;

主键为(database_name,table_name)

  1. n_rows 项的收集
    从聚簇索引中选取几个叶子节点页面,计算平均包含的记录数量。
  2. clustered_index_size 和 sum_of_other_index_sizes 的收集
    从数据字典中找出索引对应根页面位置。
    从根页面读取对应的 Segment Header,找到对应段信息存储的位置。
    获取叶子节点段和非叶子节点段的 INODE Entry。
    统计段中索引零散页面,及 FREE、NOT_FULL、FULL 这 3 个链表的 List Length 字段占用区的数量。(这里简单的都计算上了,实际上可能某些区中有些页面仍是空闲的,即包括了分配给了段但尚未使用的页面)

(2)innodb_index_stats ;每条记录对应一个索引的统计数据;

该表的主键为(database_name,table_name,index_name,stat_name)

stat_name 是统计项的名称。

n_leaf_pages:该索引叶子节点占用的页面数
size:该索引占用页面数
n_diff_pfxNN:对应索引列不重复的值。例如 01表示的是 key_part1 这一列不重复的值, 02 表示 key_part1、 key_part2 两列组合不重复的数。

在计算某些索引列中包含多少个不重复值时,对叶子节点采样的数量为 sample_size;(对多列索引,需要采样的数量为 innodb_stats_persistent_sample_pages × 索引包含列的个数,最大为该索引所有的叶子页面数量)

(3)更新统计数据

  1. 开启innodb_stats_auto_recalc;默认开启;
    对每个表维护一个变量,记录对该表增删改的记录条数,发生变动的记录超过表大小的 10%,重新计算一次统计数据(异步发生)。
    在创建/修改表时,可以更改该属性 STATS_AUTO_RECALC = ( 1 | 0 )
  2. 手动调用 ANALYZE TABLE 表名;会立即重新计算统计数据,是同步进行的。

你可能感兴趣的:(数据库,mysql,数据库)