访问方法:执行查询的方式
只需于选取代价最小的那种访问方式去执行单表查询语句即可。
主键值或者唯一的二级索引定位一条记录(与常数等值比较时才有效)
需要注意的是,使用唯一二级索引查询列为 NULL 值时不会使用 const 访问方法来执行,使用 ref 访问方式。
key IS NULL
普通二级索引与常数进行等值比较。
二级索引允许存储 NULL 值时,执行包含 key IS NULL
二级索引中包含多个列时,只有最左边连续的列与常数进行等值比较,就可以使用 ref 访问方法
二级索引等于某个常数,会有列值为 NULL 的记录,即
key1 = 'abc' OR key1 IS NULL
值为 NULL 的记录会被放在索引最左边。
使用索引查询时,扫描区间为若干个单点扫描区间(IN
)或者范围扫描区间。
注意:不包含仅一个单点扫描区间,并且这里的扫描区间不能为 ( − ∞ , + ∞ ) (-\infty,+\infty) (−∞,+∞)
扫描全部二级索引记录的访问方法(不用执行回表),即需要查询的数据及搜索条件均在索引列中
特例:人为规定;通过全表扫描执行查询,添加了 ORDER BY 主键
全表扫描,直接扫描全部的聚簇索引记录
一般情况下只会为单个索引生成扫描空间,但也可能为多个索引生成扫描空间。
这里的合并是,先根据二级索引得到二级索引记录,先不进行回表,等合并后,再进行回表操作。
(1)Intersection 索引合并
要求从每个索引中获取到的二级索引记录都是按照主键值排序的。
带来的好处是:两个有序数组求交/并集或去重比较容易;按主键排序后进行回表操作容易形成顺序 IO。
这对二级索引来说,相当于要求等值查询,而非范围查询,并且需要给定所有的索引列。
例如WHERE key1 = 'a' AND id > 9000
,对聚簇索引,本身就是按照主键顺序排序的,可以是范围查询,因为二级索引是包含索引列和主键的,在索引列值相同的情况下,是按主键排序的,只需扫描二级索引即可,并不需要扫描聚簇索引。
(2)Union 索引合并
要求从每个索引中获取到的二级索引记录都是按照主键值排序的。
例如WHERE key1 = 'a' OR id > 9000
,此时需要扫描聚簇索引
可以组合 Intersection 与 Union 索引合并
(3)Sort-Union 索引合并
针对单独根据搜索条件从某个二级索引中获取记录比较少的情况。
很多情况下无法保证二级索引记录按照主键排序,例如范围扫描,此时要进行额外的排序工作。先获取二级索引记录,按照主键值进行排序后再进行合并。
涉及单表的条件
内外连接的根本区别为:在驱动表中不符合 ON 子句中的连接条件时,是否会加入到最后的结果集中。
(1)每获得一条驱动表(外循环中的表)记录,就立即到被驱动表(内循环中的表)中寻找匹配的记录。
对驱动表而言,会使用只涉及驱动表的条件来进行单表访问。
WHERE 子句中的过滤条件,不论是内连接还是外连接,不符合的记录都不会被加入到最后的结果集。
ON 子句中的过滤条件,专门针对外连接,驱动表中的记录都会被加入到最后的结果集。
对外连接而言,必须使用 ON 子句来指出连接条件。
(2)左(外)连接,LEFT (OUTER) JOIN,左侧的为驱动表;
右(外)连接,RIGHT (OUTER) JOIN,右侧的为驱动表;
内连接,推荐书写为 INNER JOIN
基于块的嵌套循环连接,涉及到 Join Buffer。先把驱动表结果集放在Join Buffer 中,每一条被驱动表记录可以一次性匹配多条驱动表结果集中的记录;需要注意的是: Join Buffer 中不会存放驱动表记录的所有列,只有查询列表中列和过滤条件中的列。
MySQL 的执行成本:IO 成本和 CPU 成本。
读取一个页面花费成本为 1.0;
读取及检测一条记录是否符合搜索条件的成本默认为 0.2;
(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 会先分析使用唯一二级索引的成本,再分析使用普通索引的成本。
对使用二级索引 + 回表的方式,成本主要为:
扫描区间的数量:认为一个扫描区间的 IO 成本与读取一个页面相同。
需要回表的记录数:会在 B+ 数中找到区间左右端点记录,统计它们之间的数量。(即使对单点扫描区间也会这样做)
当相隔不超过 10 个页面时,会根据每页中 Page Header 的 PAGE_N_RECS
进行累加。相距太远,也会沿着最左记录向右读 10 个页面,计算平均数量,再乘相隔页面数。
每次回表操作都相当于访问一个页面。根据上述需要回表的记录数。
回表操作完成后,再检测其他搜索条件。根据上述需要回表的记录数。
(4)注意事项
在计算二级索引成本的第 2 步时,会少量访问 B+ 数中的数据,这种计算方式称为 index dive。
单点扫描区间大于等于eq_range_index_dive_limit
时,不会使用 index dive 的计算方式,使用索引统计数据进行估算。
SHOW INDEX FROM 索引名;
主要使用 Cardinality 信息。
单个值的重复次数 Rows / Cardinality ,单个值的重复次数 * 单点扫描区间数量 = 需要回表的记录数
单次查询驱动表的成本;查询得到的记录条数称为扇出(fanout);
扇出值 × 查询被驱动表的成本
对内连接来说,可以选择左侧或右侧是驱动表;
对被驱动表而言,确定好驱动表后连接条件可以确定,例如s1.key1 = 常数
,但在优化过程中是无法得知具体的常数值,那么如何进行估算?这里也是采用统计数据中的单个值的重复次数。
多表 Join,需要考虑连接顺序,采用了一些提前结束某种连接顺序评估等启发式规则。
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
相同
实际上是把统计数据存储到两个表中:innodb_table_stats 和 innodb_index_stats;
(1)innodb_table_stats ;每条记录对应一个表的统计数据;
主键为(database_name,table_name)
(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)更新统计数据
innodb_stats_auto_recalc
;默认开启;STATS_AUTO_RECALC = ( 1 | 0 )
ANALYZE TABLE 表名
;会立即重新计算统计数据,是同步进行的。