导读
在《MySQL为什么选择执行计划A而不选择B》的上下篇中我详细讲解了MySQL选择执行计划的策略,其中,讲到了3种查询成本计算方式:
- 基于索引扫描的查询成本计算
- 基于索引统计的查询成本计算
- 基于全表扫描的查询成本计算
我们都希望能有一个计算查询成本效率又高,又准确的方案,而目前MySQL只支持这3种查询成本计算方案,那么,结合我们的期望,再看看上面3种方案是否有可能通过某种方法实现我们的期望呢?
-
基于索引扫描的查询成本计算
通过《MySQL为什么选择执行计划A而不选择B(上)?》的讲解,我们知道这是一个效率低,但是计算最准确的方案。
而方案的效率低是由于必须扫描索引树来确定查询成本,如果索引树的分叉很多,势必会降低扫描的效率,从而降低了查询成本计算的效率。所以,不能满足我们的期望。
-
基于索引统计的查询成本计算
通过《MySQL为什么选择执行计划A而不选择B(上)?》的讲解,我们知道这是一个效率高,但是计算不准确的方案。
而方案的计算不准确是由于MySQL是通过采样部分索引树的节点,然后对这些节点做相关计算,从而生成索引统计结果,最后,得出查询成本的。所以,也不能满足我们的期望。
那么,如果我们可以提升采样节点的数量,是不是就可以更加精准地计算查询成本了?
的确,MySQL给了我们相关的参数优化,可以让我们更加精准地计算查询成本。计算结果更准确了,该方案效率又高,正好可以满足我们的期望。
-
基于全表扫描的查询成本计算
通过《MySQL为什么选择执行计划A而不选择B(下)?》中全表扫描的查询成本计算过程,我们可以知道该方案是计算效率高,但是计算结果不准确的方案。
而在大多数情况下,它的成本计算结果往往都比上面两个基于索引计算查询成本的方案要差很多,所以,对这个的方案的进一步分析的意义不是很大,我在这里就不做更进一步的分析和讲解了。
综上所述,只有第2种方案,即基于索引统计的查询成本计算是有进一步优化空间的,所以,下面,我将详细讲解在基于索引统计的查询成本计算
中,MySQL是如何生成索引相关统计结果的,在讲解过程中,我会告诉大家MySQL的哪个参数可以提升查询成本计算的准确性?
INFORMATION_SCHEMA
在讲解MySQL是如何生成索引相关统计结果前,我先讲解一个数据库:INFORMATION_SCHEMA
,因为它是MySQL生成索引统计结果时需要的基础数据来源。
现在主流使用的存储引擎就是InnoDB,所以,我就详细讲一下在INFORMATION_SCHEMA
这个数据库中,跟InnoDB引擎有关的几张表,这几表,一般我们把它们叫做InnoDB数据字典(Data Dictionary),其中,索引统计结果的基础信息主要来源于2张字典表:INNODB_SYS_TABLES
和INNODB_SYS_INDEXES
,我以user表
为例详细讲解这两张表:
为了方便理解2张字典表的列的概念,我把user表
结构和表记录再贴一下:
user表结构:
user表记录
INNODB_SYS_TABLES
这张表存放了MySQL中所有表的基础信息,我们来看一下user表
的信息:
我们来看一下该表中的核心字段:
列名 | 说明 |
---|---|
TABLE_ID | 所有的表自增排序的一个序号,每个表都是唯一的,比如上图:user表序号为40 |
NAME | 表名,结构为数据库/表 ,比如上图,user_center/user,表示user_center数据库中的user表 |
N_COLS | 表中的列的个数,其中包含3个MySQL自带的隐藏列,即DB_ROW_ID、DB_TRX_ID和DB_ROLL_PTR,比如,上面user表结构中看到,user表包含7列,加上隐藏列,一共是10列 |
SPACE | 表所在的表空间ID,每个表空间在一个MySQL实例中是唯一的,比如上图,user表的表空间ID为57 |
ROW_FORMAT | 行记录格式,主要包含4种:Compact, Redundant, Dynamic或Compressed,比如上图,user表的行记录格式为Dynamic |
INNODB_SYS_INDEXES
这张表存放了MySQL中所有索引的基础信息,我们来看一下user表
的索引信息:
列名 | 说明 |
---|---|
INDEX_ID | 索引ID,在一个MySQL实例中,索引ID是唯一的,比如上图,user表的primary主键索引ID=41,index_age_birth 索引ID=42 |
NAME | 索引名,其中,主键索引名为PRIMARY,还有一个名为index_age_birth 的辅助索引 |
TABLE_ID | 索引所在的表ID,比如上图,user表中的两个索引所在的表ID都为40,即user表 |
TYPE | 索引类型,包含聚簇索引、辅助索引、唯一索引、普通索引等等。比如上图,user表中primary主键索引,它的类型为3,即聚簇索引。而索引index_age_birth 类型为0,即辅助索引 |
N_FIELDS | 索引包含的列数,比如上图,primary主键索引包含1列,即id。index_age_birth 索引包含2列,见表结构图中的INDEXES部分,即age 和birthday |
PAGE_NO | 索引所在的B+Tree的根节点号。比如上图,主键索引所在B+Tree的根节点号为3,index_age_birth 索引所在的B+Tree的根节点号为4 |
SPACE | 索引所在的表空间ID,比如上图,主键索引和index_age_birth 索引都在user表中,所以,对应user表的表空间ID为57 |
索引统计表
接着,我们再看一下MySQL的索引统计表信息,因为这张表存储MySQL生成的索引统计结果。
我们还是以InnoDB引擎为例,InnoDB引擎的索引统计表在mysql数据库下面,执行如下SQL查询:
SELECT * FROM mysql.innodb_index_stats WHERE table_name = 'user'
我们以user表
为例,看下索引统计表innodb_index_stats
的数据:
其中,列database_name
和table_name
分别表示某个数据库下的哪张表,我就不详细说了。重点看一下下面这些列:
-
index_name
该列记录索引名,如上图,
user表
中包含两个索引,PRIMARY主键索引和index_age_birth
索引。 -
stat_name/stat_value
针对
index_name
列相同的行,stat_name
表示针对该索引的统计项名称,stat_value
展示的是该索引在该统计项上的值。我们来具体看一下一个索引都有哪些统计项:-
n_leaf_pages
:表示该索引中叶子节点的数量。比如,user表primary主键索引叶子节点数为1,
index_age_birth
索引叶子节点数也为1 -
size
:表示该索引所有节点的数量。比如,user表primary主键索引所有节点数为1,index_age_birth索引所有节点数也为1
-
n_diff_pfxNN
:表示对应的索引列不重复的值有多少。其中的NN
是什么意思呢?其实
NN
可以被替换为01
、02
、03
... 这样的数字。比如上图,对于index_age_birth
索引来说:n_diff_pfx01
表示该索引中age
这单个列不重复的记录有多少。结合上面user表记录图,我们发现,user表中age
列不重复的记录有6条。n_diff_pfx02
表示该索引中age,birthday
这两个列组合起来不重复的记录有多少。结合上面user表记录的图,我们发现,user表中age,birthday
列组合后不重复的记录有9条。n_diff_pfx03
表示该索引中age,birthday,id
这三个列组合起来不重复的记录有多少。结合上面user表记录的图,我们发现,user表中age,birthday,id
列组合后不重复的记录有10条。
-
-
sample_size
在计算某些索引列中包含多少不重复的记录时,需要对一些叶子节点进行采样,
sample_size
列就表明了采样的叶子节点的数量是多少。如上图,primary
主键索引id的采样叶子节点数为1index_age_birth
索引中age
列的采样叶子节点数为1index_age_birth
索引中age,birthday
列组合的采样叶子节点数为1index_age_birth
索引中age,birthday,i
d列组合的采样叶子节点数为1
构建InnoDB字典缓存
在《INFORMATION_SCHEMA》这一部分中,我讲到MySQL索引统计结果中的基础数据来源于InnoDB数据字典,其中主要来源于两张表INNODB_SYS_TABLES
和INNODB_SYS_INDEXES
。现在,我们来看一下这个场景:
由于INNODB_SYS_TABLES
和INNODB_SYS_INDEXES
是持久化在磁盘上的,为了保证索引统计结果的准确性,MySQL在构建索引统计结果时,为了获取基础数据,必须从磁盘上频繁读取这两张表,此时,磁盘IO会增加,导致MySQL性能下降。这一定不是我们想看到的。
所以,为了解决这个问题,MySQL对InnoDB数据字典做了缓存,那么,在构建索引统计结果时,MySQL就可以从缓存中读取基础数据,相比磁盘读取,提升了读取的性能。
下面,我们就来看下MySQL是如何构建InnoDB数据字典缓存的?
缓存结构
在讲解缓存构建过程之前,我们先看一下这个数据字典缓存长什么样?我以user表
为例,看下INNODB_SYS_TABLES
和INNODB_SYS_INDEXES
两张字典表的缓存结构。
INNODB_SYS_TABLES缓存
INNODB_SYS_TABLES缓存
主要包含3个结构:table_hash
、table_id_hash
和table_LRU
。
我们先看一下table_hash
:
如上图,table_hash
是一个hash表,它的结构主要包含:
-
cells:hash表的node节点列表,每个node节点的结构如下:
-
node节点的hash值通过对表名做fold运算得到。如上图左侧:
-
第一个node节点hash值通过对t1表名做
fold(t1)
运算得到。 -
第二个node节点hash值通过对user表名做
fold(user)
运算得到。 -
第三个node节点hash值通过对t2表名做
fold(t2)
运算得到。
-
-
node节点存储table对象的单向链表,链表中的每个节点存储table对象,该对象包含table的基础信息。存储单向链表是为了解决hash冲突。
-
第一个node节点的链表中,头部节点存储
t1表
对象,中间节点存储t2表
对象,最后一个节点存储t3表对象。 -
第二个node节点的链表中,头部节点存储
user表
对象,中间节点存储user2表
对象,最后一个节点存储user3表对象。
-
-
下面,我在看一下table_id_hash
结构:
如上图,与table_hash
结构相似,唯一的区别就是node节点的hash值是通过对table_id
,即图中的30,40和45
做fold运算得到,而不是对table表名做fold运算得到。
最后,我们看一下table_LRU
结构:
table_LRU
是一个最少使用的table逐出链表,这是一个双向链表,当有table长期不使用,MySQL会将其从该链表中移除,同时,将其对应的table_hash
和table_id_hash
中的节点删除,保证内存的有效利用率。
如上图,该链表从前到后,包含user
、t1
和t2
三个表对象。
关于逐出算法的机制,因为不是本章的重点,我就先不详细说了。
INNODB_SYS_INDEXES缓存
接着,我们再看一下INNODB_SYS_INDEXES缓存
的结构。
MySQL为每张表的索引维护了一个双向链表,我们把它叫做INNODB_SYS_INDEXES链表
,如上图,有2张表的索引组成的双向链表,该链表的结构如下:
-
链表节点内存储索引相关信息。如上图,
-
table1表
的链表中包含4个节点,从前到后分别存储primary主键索引信息、Index2
索引信息、Index3
索引信息和Index4
索引信息。 -
user表
的链表中包含2个节点,从前到后分别存储primary主键索引信息和index_age_birth
索引信息。
-
-
链表的头部节点一定是存储table的主键索引信息。如上图,
-
table1表
的链表的头部节点存储primary主键索引。 -
user表
的链表头部节点存储primary主键索引。
-
-
链表有一个start指针指向头部节点,一个end指针指向尾部节点。如上图,
-
table1表
的链表的start指针指向头部的primary主键索引节点,end指针指向尾部的Index4
索引节点。 -
user表
的链表的start指针指向头部的primary主键索引节点,end指针指向尾部的index_age_birth
索引节点。
-
现在,我们来看一下InnoDB数据字典缓存构建的过程。该过程主要有以下两种方式:
Alert/Create触发构建
第一种方式是在我们执行创建或修改表/索引的SQL语句时,触发构建InnoDB数据字典缓存。
创建表时触发
先来看一下创建表时,INNODB_SYS_TABLES字典表缓存
是如何构建的?
我以创建user表的SQL语句为例,讲解一下INNODB_SYS_TABLES缓存
的构建过程。
CREATE TABLE `user` (
`id` int(11) NOT NULL,
`user_id` int(8) DEFAULT NULL COMMENT '用户id',
`user_name` varchar(29) DEFAULT NULL COMMENT '用户名',
`user_introduction` varchar(498) DEFAULT NULL COMMENT '用户介绍',
`sex` tinyint(1) DEFAULT NULL COMMENT '性别',
`age` int(3) DEFAULT NULL COMMENT '年龄',
`birthday` date DEFAULT NULL COMMENT '生日',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
具体过程如下:
- 解析SQL语句,得到表相关信息,比如,表名、列名、列信息等,对应上面的SQL,即user、7个列和对应的列信息。
- 生成
user表
的table_id
- 根据
表名user
、table_id
、7个列名、7个列信息等构建user表对象
,该对象包含前面的这些基础信息 - 将
user表对象
写入table_hash
、table_id_hash
- 将
user表对象
添加到table_LRU
链表头部
创建索引时触发
下面我们再看一下创建索引时,INNODB_SYS_INDEXES缓存
是如何构建的?
我以创建索引index_age_birth
的SQL语句为例,讲解一下INNODB_SYS_INDEXES缓存
的构建过程。
ALTER TABLE `user` ADD UNIQUE INDEX `index_age_birth` (`age`, `birthday`);
具体过程如下:
- 解析SQL语句,得到索引及表相关信息,比如,索引名、索引列、表名等,对应上面的SQL,即
index_age_birth
、(age, birthday)
和user
。 - 根据
表名user
从table_hash
中取出表对象 - 根据索引名
index_age_birt
h、索引列(age, birthday)
等信息构建索引基础信息 - 将索引基础信息更新到
user表对象
- 将
user表对象
重新写入table_hash
和table_id_hash
- 将索引基础信息添加到
user表
对应的INNODB_SYS_INDEXES链表
的末尾
启动时构建
第二种方式是当MySQL重启时,InnoDB数据字典缓存会丢失,所以,MySQL会扫描InnoDB数据字典表,将数据加载进数据字典缓存。具体过程如下:
-
扫描InnoDB数据字典表,逐张字典表读取相关数据,并加载到缓存
(1) 逐行读取
INNODB_SYS_TABLES表
中记录,根据记录构建table对象
,逐个将对象写入table_hash
和table_id_hash
,以及将对象添加到table_LRU
链表头部(2) 逐行读取
INNODB_SYS_INDEXES表
中记录:-
根据记录中的
table->id
,根据table_id
从table_id_hash
中获取table对象
-
将记录中的索引信息写入
该table对象
-
将记录中的索引信息添加到
该table对象
对应的INNODB_SYS_INDEXES链表
尾部 -
将
该table对象
重新写入table_hash
和table_id_hash
-
我们发现通过上面两种方式写InnoDB数据字典缓存,可以有效保障缓存中总是拥有最新的数据字典,从而保证了后续更新索引统计表的时效性。
索引统计表构建
现在,我们最后看一下MySQL是如何生成索引统计结果,并更新索引统计表的?
MySQL采用两种方式更新索引统计表:
- 定时更新方式:每隔10秒更新一次索引统计表。
ANALYZE TABLE
语句方式:手动执行该语句更新一次索引统计表。
从上面的讲解中,我们知道索引统计表的基础信息来源于两个InnoDB数据字典缓存:INNODB_SYS_TABLES缓存
和INNODB_SYS_INDEXES缓存
。现在结合索引统计表定时更新方式,我们看一下这个场景:
- MySQL扫描所有table对应的
INNODB_SYS_INDEXES
链表 - 逐个读取index索引,获取索引基础信息
- 逐个根据索引基础信息计算索引统计项
- 将统计项写入索引统计表
仔细看下这个过程,我们发现如果INNODB_SYS_INDEXES链表
随着表越来越多,早期更新的表可能要花很长时间才能扫描到,导致该表更新索引统计表周期变长,该表对应的索引统计表统计项更新变慢,最终影响该表相关SQL的查询的性能。因为在本章《导读》中,我说过索引统计项更新越快,统计结果越准,MySQL根据统计结果选择某条SQL执行计划就越准确,该条SQL执行的效率越高。
为了解决上面这个问题,MySQL引入了recalc_pool这个东西,可以让MySQL总是从最早变化的表取出索引的基础信息,然后,用这些信息计算,得到索引统计结果。
所以,我们就先来看一下这个recalc_pool
。
recalc_pool
recalc_pool
结构如下:
recalc_pool
是一个列表,列表内存储表id。如上图,从前到后依次存储user表id: 40,t1表id: 45和t2表id: 80。
recalc_pool
的作用:将最近变更的table->id添加到recalc_pool
尾部,定时任务从recalc_pool
中取最早变更的table->id进行索引统计。
定时更新
下面,我们来看一下定时更新索引统计表的过程。
从recalc_pool
的作用来看,我们发现,它主要包含两部分:添加table->id到recalc_pool和从recalc_pool中取table->id。
添加recalc_pool
那么,我们先看一下添加table->id
到recalc_pool
尾部的过程:
如上图,假设用户A现在对user表执行insert操作,插入20条记录,用户B对account表做update操作,更新表中一条记录,此时,MySQL会做如下判断:
如果变更表的记录数 > 表总记录数 * 10%,那么,将该表的id添加到
recalc_pool
。
结合之前user表记录这张图,我们知道user表总记录数为10,现在,要插入20条记录,20 > 10 * 10%,所以,如上图,user表的id,即40,添加到recalc_pool
尾部。
account表更新操作只影响一条记录,假设account表总共有20条记录,1 < 20 * 10%,所以,如上图,account表的id不添加到recalc_pool
。
读取recalc_pool
下面,我们再看一下从recalc_pool
读取table->id
,并计算索引统计项的过程,还是以user表
为例,见下图:
-
从
recalc_pool
中读取第一条table->id
,即图中从head头部读取user表id: 40。 -
根据user表id从
table_id_hash
读取user表对象。即图中上面从table_id_hash
中取出user。 -
如果当前时间 - 上次计算user表的时间 > 10秒,更新索引统计项,否则,继续将user表id添加到
recalc_pool
尾部 -
更新索引统计项
(1) 由于在上文
创建索引时触发
时,已经将user表的所有索引更新到user表对象,所以,根据user表对象,我们可以获取该表所有索引(2) 遍历
INNODB_SYS_INDEXES缓存
中user表的链表,获取每个索引,即图中左下方,依次读取primary
和index_age_birth
索引,这里我就以index_age_birth
索引为例,开始计算索引统计项,即图中compute部分,我们结合上文索引统计表的统计项来看下面的计算流程:-
开启
index_age_birth
索引级别mtr
,关于mtr
,我会在《MySQL是如何平衡日志写入的性能和数据可靠性的?》详细讲解。 -
根据
index_age_birth
索引id找到内存中的对应的索引树,即图中右下角,计算该索引树总节点的个数,将个数写入stat_index_size
,所以,index_age_birth
索引的stat_index_size = 1
-
根据
index_age_birth
索引id找到内存中的对应的索引树,即图中右下角,计算该索引树叶子节点的个数,将个数写入stat_n_leaf_pages
,所以,index_age_birth索引的stat_n_leaf_pages = 1
-
根据
index_age_birth
索引id找到内存中的对应的索引树,即图中右下角,计算该索引树叶子节点的个数:-
如果
index_age_birth索引树的叶子节点数 < 采样的叶子节点数参数 * 索引列个数
,stat_n_sample_sizes
为该索引树的所有叶子节点个数。其中采样的叶子节点数参数默认为20,可以通过调整innodb_stats_persistent_sample_pages
参数变更。后面我会讲到变更方法。比如,现在通过遍历
index_age_birth
索引树叶子节点,我们知道该索引树的总叶子节点数为1,所以,-
index_age_birth
索引总叶子节点数1 < 20 * 1
,其中,右边的1代表1个索引列age
,所以, 该索引列age
的stat_n_sample_sizes
为1 -
index_age_birth
索引总叶子节点数1 < 20 * 2
,其中,右边的2代表1个索引列组合(age,birthday)
,所以,该索引列组合(age,birthday)
的stat_n_sample_sizes
为1 -
index_age_birth
索引总叶子节点数1 < 20 * 3
,其中,右边的3代表1个索引列组合(age,birthday,id)
,所以,该索引列组合(age,birthday,id)
的stat_n_sample_sizes
为1
-
-
-
根据
index_age_birth
索引id找到内存中的对应的索引树,即图中右下角,扫描所有叶子节点记录的链表:-
遍历
index_age_birth
索引列的个数,从1->2->3(分别代表age
,age,birthday
,age,birthday,id
):-
如果前一条记录和后一条记录匹配列的个数小于1,说明两条记录在
age
列上的值不相同,则stat_n_diff_key_vals[0] + 1
-
如果前一条记录和后一条记录匹配列的个数小于2,说明两条记录在
age,birthday
列组合上的值不相同,则stat_n_diff_key_vals[1] + 1
, -
如果前一条记录和后一条记录匹配列的个数小于3,说明两条记录在
age,birthday,id
列组合上的值不相同,则stat_n_diff_key_vals[2] + 1
-
-
-
得到
stat_n_diff_key_vals[0] = 6
stat_n_diff_key_vals[1] = 9
stat_n_diff_key_vals[2] = 10
-
关闭
index_age_birth
索引级别mtr
-
-
持久化索引统计表,按照如下统计项对应关系,更新
n_leaf_pages
、size
、sample_size
和n_diff_pfxNN
到索引统计表中,即图中右边写统计表。stat_index_size
=>size
stat_n_leaf_pages
=>n_leaf_pages
stat_n_sample_sizes
=>sample_size
stat_n_diff_key_vals[0]
=>n_diff_pfx01
stat_n_diff_key_vals[1]
=>n_diff_pfx02
stat_n_diff_key_vals[2]
=>n_diff_pfx03
ANALYZE TABLE
该方式就是手动触发并执行《定时更新》这部分内容讲到的更新索引统计项和持久化索引统计表两个步骤。
以user表为例,我们看一下用该语句更新索引统计表的写法:
ANALYZE TABLE user;
参数配置
现在我们再回到《导读》提到的一个问题:针对基于索引统计的查询成本分析,如果我们可以提升采样节点的数量,是不是就可以更加精准地计算查询成本了?
通过上面内容的讲解,我们清楚地了解了MySQL索引统计结果的计算和构建过程,其中,采样叶子节点的数量是由MySQL参数innodb_stats_persistent_sample_pages
决定的,所以,如果我们可以调大这个参数,就可以保证精确计算索引统计表中的各统计项,从而使得MySQL能够更加正确地选择执行计划。
调整方式如下:
SET GLOBAL innodb_stats_persistent_sample_pages=30;
小结
本章详细讲解了基于索引统计分析查询成本前,索引统计项构建的过程,通过这个过程的讲解,你应该知道了以下内容:
-
两张核心的数据字典表:
INNODB_SYS_TABLES
和INNODB_SYS_INDEXES
表名 说明 INNODB_SYS_TABLES 存放了MySQL中所有表的基础信息 INNODB_SYS_INDEXES 存放了MySQL中所有索引的基础信息 -
两张核心的数据字典表缓存:
INNODB_SYS_TABLES缓存
和INNODB_SYS_INDEXES缓存
缓存名 说明 INNODB_SYS_TABLES缓存 主要包含3个结构:table_hash、table_id_hash和table_LRU INNODB_SYS_INDEXES缓存 MySQL为每张表的索引维护了一个双向链表 -
索引统计表
索引统计表存储MySQL生成的索引统计结果。看到这张表,似乎和《MySQL为什么选择执行计划A而不选择B(上)?》基于索引统计的查询这个部分内容的统计表长得不一样,其实,后者的数据来源于前者,所以,从数据上看是一样的。
-
两张核心的数据字典表缓存的构建过程
构建方式 说明 创建表时构建 创建表时触发构建INNODB_SYS_TABLES缓存 创建索引时构建 创建索引时触发构建INNODB_SYS_INDEXES缓存 MySQL重启时构建 在MySQL重启时,会全量加载INNODB_SYS_TABLES和INNODB_SYS_INDEXES表数据到缓存中 -
索引统计表的构建过程
-
recalc_pool
:一个存储table->id
的列表,将最近变更的table->id
添加到recalc_pool
尾部,定时任务从recalc_pool
头部取最早变更的table->id
进行索引统计 -
构建过程
过程 说明 定时更新 每隔10秒更新一次索引统计表 ANALYZE TABLE 手动执行该语句更新一次索引统计表 -
参数配置
参数 作用 innodb_stats_persistent_sample_pages 调整该参数可以变更MySQL采样索引树叶子节点的个数
-
思考题
在《定时更新》部分中,有一个步骤是计算stat_n_sample_sizes
的流程,其中,我讲解了索引树总叶子节点数 < 采样的叶子节点数参数 * 索引列个数时,stat_n_sample_sizes
的计算过程。
那么,如果索引树总叶子节点 > 采样的叶子节点数参数 * 索引列个数时,MySQL是如何计算stat_n_sample_sizes
的?
提示:结合《MySQL为什么选择执行计划A而不选择B(上)?》中,我讲到的基于扫描索引树的查询成本分析的过程,看一下怎么回答这个问题。