在MySQL中,加载一个数据页到内存中的成本常数是1.0
,检索一条记录是否匹配的成本常数是0.2
.这两个常数在计算总成本时会经常用到。
(1):表:
1 | CREATE TABLE single_table ( |
(2):SQL:
1 | SELECT |
我们定义凡是索引列能和比较值形成一个范围区间的,都是有可能被使用到的
key1:key1列有索引,也是与常数进行比较的,有可能会被使用到
key2:大于10与小于1000,也是在与常数在进行比较,有可能会被使用到
key3:与key2进行比较,而key2不属于常数,不会被使用到
key_part1:使用到了模糊匹配,不是常数,不会被使用到
common_field:该列本身就没有索引,不会被使用到
综上,有可能被使用到的索引只有idx_key1
和idx_key2.
全表扫描的成本从两方面计算,CPU成本
和I/O成本
。
那么如何计算呢?我们可以通过SHOW TABLE STATSU LIKE TABLE_NAME
来查看我们目标表的信息,我们关心其中的Data_length
和Rows
。在本例中,Data_length
的大小是2637824
,Rows
是9856
。
I/O成本:通过Data_length
计算,Data_length
代表的是我们表所占字节数多少,那么根据默认的页大小16K来计算,2637824/16/1024=162页,那么成本就是162*1.0 + 1.1 = 163.1
,其中1.1是微调常数。
CPU成本:CPU成本我们通过行数计算,成本是9856 * 0.2 + 1.0 = 1972.2
总成本为:1972.2 + 163.1 = 2135.3
使用唯一二级索引idx_key2的成本
第一步:计算二级索引I/O成本,key2的范围区间(10,1000),MySQL的查询优化器认为读取一个范围区间的成本和读取一个数据页的成本是一样的,所以I/O成本是1 * 1.0 = 1.0
第二步:计算二级索引CPU成本(回表数),估算在(10,1000)内记录的数量。这里在进行估算时,取范围最左和最右两条记录,然后判断这两条记录相隔的远不远(是否超过10个数据页),不远的话,就遍历,远的话,就读10个数据页中的数据,然后预估整个区间大概有多少。根据算法得到key2的CPU成本是95 * 0.2 + 0.01 = 19.01
,95是记录条数,0.01是微调常数。
第三步:计算回表成本,MySQL认为回一次表的成本和读取一个数据页的成本一样,所以回表成本是95 * 1.0 = 95
第四步:计算回表后检索是否匹配的CPU成本,有多少条数据检索多少次,成本是95 * 0.2 = 19.0
综上,使用idx_key2进行查询的成本是:1 + 19.01 + 95 + 19 = 134.01
使用普通二级索引idx_key1的成本
使用key1查询的条件是,三个单点区间[‘a’, ‘b’, ‘c’]
第一步:计算二级索引I/O成本,三个区间,所以成本是3 * 1.0 = 3.0
第二步:计算二级索引CPU成本(回表数),获取符合三个区间的记录的最左和左右记录,本例中一共有118条,那么成本就是118 * 0.2 + 0.01 = 23.61
第三步:回表操作的I/O成本,118 * 1.0 = 118
第四步:回表操作后的CPU成本,118 * 0.2 23.6
综上,使用idx_key1进行查询的成本是3.0 + 23.61 + 118 + 23.6 = 168.21
因为 134.01 < 168.21 < 2135.3,所以选择idx_key2来进行查询
这里需要补充一个成本计算的方式–基于索引统计数据
先来说明一下这种计算方式提出的背景:我们上文中提到了,如果我们使用的in查询中参数过多的话,会造成很多的单点区间,我们知道,每一个单点区间都要使用index dive
去估算区间里的记录条数,当我们单点区间特别多的时候,我们要访问B+树的次数也会很多,有时候但是访问B+树耗费的成本已经大于全表扫描了,所以当我们单点区间特别多的时候,我们就会采用基于索引统计数据的方式来估算记录数,这个阈值我们可以通过“SHOW VARIABLES LIKE '%DIVE%'”
查看
统计方式:先总结一下,我们会根据索引的重复比例和总行数来预估。举个例子,我们表的总行数是10000,我们通过SHOW INDEX FORM single_table
查看到我们某个索引的Cardinality
是1000,Cardinality
表示该索引列不重复值的个数,那么该索引中每一个值重复的次数就是10000) / 1000 = 10
,之后我们用10乘以单点区间的个数就得出总共要回表的记录个数。
缺点:显而易见的,这种估算方式的误差率很大很大,很有可能把重复低的场景算的不高。
联表查询的成本计算公式:
连接查询总成本 = 单次访问驱动表的成本 + 驱动表扇出数 x 单次访问被驱动表的成本
新概念:
老概念复习:
为了更好的描述我们联表成本的计算过程,我们复制一张和single_table
一模一样的表single_table2
。
进行查询的sql是:
1 | SELECT |
我们访问s1时使用的条件是 10 < key2 < 1000,是一次range访问。扇出数据后,访问s2使用的条件是 s2.common_field = 常数 和 1000 < s2.key2 < 2000,但是common_field无索引,可以采用的方式就是使用idx_key2和全表扫描。
从之前的计算中,我们知道访问single_table时,使用idx_key2的成本是小于全表扫描的
总成本 = 使用idx_key2访问s1的成本 + s1的扇出数 * 使用idx_key2访问s2的成本
访问s2还是使用idx_key2,但在访问s1的时候,我们用到的条件是 s1.key1 = 常数 和 10 < s1.ke2 < 1000,访问方式是ref,要比range效率更高
总成本 = 使用idx_key2访问s2的成本 + s2的扇出数 * 使用idx_key1访问s1的成本
优化器最后会计算这两种查询方式的具体成本,选择成本低的执行。