目录

一、B+树结构

1. 二分查找法

2. 二叉查找树

3. 平衡二叉树

4. 平衡多路查找树(B-Tree)

5. B+树

二、操作B+树

1. 插入操作

2. 删除操作

三、B+树索引类型

1. 聚集索引(clustered index)

2. 辅助索引(secondary index)

3. 总结

四、索引管理

1. 索引分裂

2. 快速创建辅助索引(FIC)

3. 在线数据定义(Online DDL)

五、索引使用

1. 联合索引 

2. 覆盖索引

3. FORCE/USE INDEX

4. 多范围读(MRR)

5. 索引条件下推(ICP)

六、Cardinality值

1. SHOW INDEX FROM命令

2. Cardinality值

七、参考资料


一、B+树结构

1. 二分查找法

一组有序数组中的某一数据,每次查找与中间(折半)位置数据比较,每通过一次比较,查找区间缩小一半。注意,二分查找的平均查找效率高于顺序查找。

2. 二叉查找树

左子树的键值 < 根的键值 <  右子树的键值。如下图所示是一棵二叉查找树,经过遍历后输出:2、3、5、6、7、8。

联合索引叶子节点是什么 联合索引树结构_数据结构

        如果按照上图查找5,查找步骤如下:

  • step1:找到根,键值6 > 5,往左子树方向查找;
  • step2:找到键值3,3 < 5,往右子树方向查找;
  • step3:找到键值5,5 = 5,查找结束。 

相同高度的查找次数相同)。所以二叉查找树的平均查找效率高于顺序查找。

联合索引叶子节点是什么 联合索引树结构_数据结构_02

想二叉查找树查找效率高,则这棵树是平衡的,即:平衡二叉树(AVL树)。

3. 平衡二叉树

二叉查找树的定义(即:左子树的键值 < 根的键值 <  右子树的键值),其次满足任何节点的两个子树的高度差不能大于1

多用于内存结构对象中,因此维护的开销相对较小。

联合索引叶子节点是什么 联合索引树结构_数据结构_03

4. 平衡多路查找树(B-Tree)

        系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。

磁盘本身没有block的概念,而文件系统不是一个扇区来读,读取太慢,block是文件存取的最小单位。如InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位,默认每个页的大小为16KB(32个扇区)。

 B-Tree是为磁盘等外存储设备设计的一种平衡查找树。B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。如下图所示,每个节点占用一个磁盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。

联合索引叶子节点是什么 联合索引树结构_键值_04

5. B+树

        B+树的特点:

  • 非叶子节点只存储键值信息;
  • 所有叶子节点之间都有一个链指针;
  • 数据记录都存放在叶子节点中。

联合索引叶子节点是什么 联合索引树结构_数据结构_05

每个磁盘块对应页的形式存储。聚集索引是根据表主键顺序的键值存放(索引组织表),它由索引页(非叶子节点)、数据页(叶子节点)组成。每个叶子节点(数据页)用双向指针链接,数据页中的记录用单向指针链接。所以,聚集索引是索引即数据,数据即索引。

        使用工具py_innodb_page_info.py来分析表空间。t_activity_detail的空间共832页,其中B+树节点页有296页,空闲页有533页。而B+树当前高度是2层,最高层page level <0001>,属于索引页;page level <0000>是数据页。

[root@488c1daa7967 py_innodb_page_info]# python py_innodb_page_info.py -v /home/MySQL5.7/mysql-5.7.35/data/test_mysql/t_activity_detail.ibd
page offset 00000000, page type <File Space Header>
page offset 00000001, page type <Insert Buffer Bitmap>
page offset 00000002, page type <File Segment inode>
page offset 00000003, page type <B-tree Node>, page level <0001>
page offset 00000004, page type <B-tree Node>, page level <0001>
page offset 00000005, page type <B-tree Node>, page level <0001>
page offset 00000006, page type <B-tree Node>, page level <0001>
page offset 00000007, page type <B-tree Node>, page level <0001>
page offset 00000008, page type <B-tree Node>, page level <0001>
page offset 00000009, page type <B-tree Node>, page level <0001>
page offset 0000000a, page type <B-tree Node>, page level <0001>
page offset 0000000b, page type <B-tree Node>, page level <0000>
page offset 0000000c, page type <B-tree Node>, page level <0000>
page offset 0000000d, page type <B-tree Node>, page level <0000>
page offset 0000000e, page type <B-tree Node>, page level <0000>
page offset 0000000f, page type <B-tree Node>, page level <0000>
page offset 00000010, page type <B-tree Node>, page level <0000>
page offset 00000011, page type <B-tree Node>, page level <0000>
......
page offset 000000e3, page type <B-tree Node>, page level <0000>
page offset 00000000, page type <Freshly Allocated Page>
page offset 00000000, page type <Freshly Allocated Page>
......
page offset 00000000, page type <Freshly Allocated Page>
page offset 00000100, page type <B-tree Node>, page level <0000>
page offset 00000101, page type <B-tree Node>, page level <0000>
page offset 00000102, page type <B-tree Node>, page level <0000>
......
page offset 00000146, page type <B-tree Node>, page level <0000>
page offset 00000000, page type <Freshly Allocated Page>
page offset 00000000, page type <Freshly Allocated Page>
......
page offset 00000000, page type <Freshly Allocated Page> 
Total number of page: 832:
Freshly Allocated Page: 533
Insert Buffer Bitmap: 1
File Space Header: 1
B-tree Node: 296
File Segment inode: 1

二、操作B+树

1. 插入操作

        B+树插入必须保证插入后叶子节点中记录依然顺序,如下表所示,3种插入情况。


联合索引叶子节点是什么 联合索引树结构_数据结构_06

B+树3种插入情况

联合索引叶子节点是什么 联合索引树结构_数据结构_07

        如上图所示,插入键值28时,属第一种情况:数据页不满 + 索引页不满,下图插入键值28:


联合索引叶子节点是什么 联合索引树结构_数据结构_08

插入键值28

        接着再插入键值70,属第二种情况:数据页满 + 索引页不满。则:50、55、60、65、70,取中间值60,添加到上层索引页中,下图插入键值70:


联合索引叶子节点是什么 联合索引树结构_数据结构_09

插入键值70

        接着再插入键值95,属第三种情况:数据页满 + 索引页满。需要做两次拆分:

  • 第一次拆分数据页:75、80、85、90、95,取中间值85,添加到上层索引页中;
  • 第二次拆分索引页:25、50、60、75、85,取中间值60,添加到上层索引页中;

联合索引叶子节点是什么 联合索引树结构_联合索引叶子节点是什么_10

插入键值95

数据库高并发写的情况下,为了保持平衡,需要做大量的拆分页操作。而页的拆分需要对磁盘操作,所以应尽量避免页的拆分,因此提供了类似于AVL树的旋转功能。 

旋转发生条件:数据页已满,但是左右兄弟页未满。这时B+树不会急于拆分页,将记录移动到所在页的兄弟页上,一般首先用左兄弟页做旋转操作

2. 删除操作

填充因子(fill factor)来控制树的删除,50%是填充因子的最小值。如下表所示,3种删除情况。


联合索引叶子节点是什么 联合索引树结构_联合索引叶子节点是什么_11

B+树3种删除情况

合并页操作

三、B+树索引类型

一般B+树的高度2 ~ 4层,即:找到行记录所在的页最多只需2到4次IO。目前磁盘每秒至少100次IO,因此2 ~ 4次IO查询时间只需0.02 ~ 0.04秒。

        B+索引类型分为聚集索引(clustered index)、辅助索引(secondary index)。无论哪种索引,则都是B+树,且高度平衡。

1. 聚集索引(clustered index)

主键(PRIMARY KEY)构造一个B+树,由叶子节点(数据页)、非叶子节点(索引页)组成,数据页存放行记录的完整数据。聚集索引的特性决定了索引组织表的数据也是索引的一部分,即:数据即索引,索引即数据

每张表只能拥有一个聚集索引。多数情况下,优化器倾向于使用聚集索引,原因是聚集索引能够在叶子节点中直接找到整行数据内容。对于主键的顺序和范围查询速度非常快。

聚集索引的存储不是物理连续,而是逻辑连续的,如下:

  • 叶子节点之间的连接:双向链接
  • 行记录之间的连接:单向链接

2. 辅助索引(secondary index)

非主键的其他键值构造一个B+树,此时叶子节点存放的行记录是该列的主键值,而不是整行数据内容

多个辅助索引;通过辅助索引获取完整数据,则需要再查询聚集索引。

3. 总结

类型

数据页

索引页

描述

聚集索引

(clustered index)

主键顺序

存放完整行数据

主键有序

(无随机读完整数据)

1. 根索引在内存中;

2. 主键顺序存放的B+树,叶子节点(数据页)存放行记录的全部数据;

3. 每张表有且只有一个聚集索引;

4. 逻辑连续,即:页是双向链表、记录是单向链表(不是物理连续);

5. 叶子节点称为数据页,存放整行记录(索引即数据,数据即索引);

6. 不是所有主键都是顺序,如:UUID是随机的;

7. 主键为自增长时,不是NULL值,导致插入并非连续。

辅助索引

(secondary index)

非主键键值顺序

存放主键

键值有序

(离散主键读完整数据)

1. B+树决定非聚集索引的插入的离散性;

2. 每张表可以由多个辅助索引;

3. 读整行数据,则:顺序读辅助索引,再离散读聚集索引

4. 辅助索引有的时候比较顺序,如:时间字段。

四、索引管理

1. 索引分裂

大并发写时,B+树索引的页分裂并不总是从页的中间拆分,导致页空间的浪费。该问题解决,是通过InnoDB存储引擎的Page Header三个参数(用来保存插入的顺序信息)。这些信息,InnoDB存储引擎决定向左还是向右分裂,同时决定分裂点记录为哪一个

  • PAGE_LAST_INSERT:最后插入记录的位置
  • PAGE_DIRECTION:最后插入记录的方向
  • PAGE_N_DIRECTION:一个方向连续插入记录的数量

往同一方向插入记录数量大,如自增主键,则会有如下两种情况:

  • 分裂点为当前待插入记录本身
  • 分裂点为当前待插入记录的后续记录

联合索引叶子节点是什么 联合索引树结构_mysql_12

InnoDB引擎插入记录时,首先需要定位,定位到的记录为待插入记录的前一条记录,cursor record为定位记录;record to be insert为当前待插入记录;split record为分裂点记录。


联合索引叶子节点是什么 联合索引树结构_联合索引叶子节点是什么_13

分裂点为当前待插入记录的后续记录的情况

联合索引叶子节点是什么 联合索引树结构_联合索引叶子节点是什么_14

分裂点为当前待插入记录本身

2. 快速创建辅助索引(FIC)

        MySQL5.5之前创建和删除索引的过程如下,存在很大缺点:创建和删除索引需要很长时间;阻塞大量事务不能访问被修改的表。

  • step1:创建临时表,定义新的表结构;
  • step2:原表数据导入临时表;
  • step3:删除原表;
  • step4:临时表重命名原表的表名。

InnoDB存储引擎对创建辅助索引的表加上S锁,无需重建表,但是大量写事务被阻塞

  • 创建辅助索引:表加S锁(无需重建表),但是写不可用(阻塞)
  • 删除辅助索引:更新内部视图,并将索引空间标记为可用,同时删除该索引定义

3. 在线数据定义(Online DDL)

允许创建辅助索引的同时,还可以进行INSERT、UPDATE、DELETE的DML操作。同时,也可以进行以下的DDL操作。与FIC的区别是:写事务不阻塞

  • 辅助索引的创建与删除
  • 改变自增长值
  • 添加和删除外键约束
  • 列的重命名

DML操作日志写入缓存,待完成后重做日志写入表上,保证了数据的一致性。由参数innodb_online_alter_log_max_size控制缓存大小,默认128MB。同时优化器不会使用正在创建或删除的索引

        下图是创建索引时,两个重要参数:ALGORITHM(创建/删除索引的算法)、LOCK(创建/删除索引时对表添加锁的情况)。

联合索引叶子节点是什么 联合索引树结构_mysql_15

五、索引使用

1. 联合索引 

键值的数量 >= 2,如:(a,b),先对a排序,再对b排序,如下所示。

联合索引叶子节点是什么 联合索引树结构_数据结构_16

         若创建一个(a,b,c)联合索引,有效索引为(a)、(a,b)、(a,b,c);索引失效为(b)、(c)、(a,c)、(b,c)。索引失效原因是创建联合索引时,先根据a排序,再排序b,最后排序c。(b)、(c)、(b,c)都不是以a开头的查询,而(a,c)虽然以a开头,但是没有b而直接查询c,导致联合索引失效。

2. 覆盖索引

覆盖索引是从辅助索引直接获取结果,无需再去查询聚集索引获取完整数据内容。适用于查询主键或统计信息。EXPLAIN执行计划的列Extra为Using index来判定使用了覆盖索引

count(*)、查询主键值都使用覆盖索引

#使用覆盖索引

select p.id from test_range_partition p where p.`name`='张三';

select count(*) from test_range_partition p where p.`name`='张三';

#未使用覆盖索引

select p.* from test_range_partition p where p.`name`='张三';

mysql> show create table test_range_partition\G;
*************************** 1. row ***************************
       Table: test_range_partition
Create Table: CREATE TABLE `test_range_partition` (
  `id` int(11) NOT NULL COMMENT '主键',
  `name` varchar(255) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`),
  KEY `index_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
/*!50100 PARTITION BY RANGE (id)
(PARTITION p0 VALUES LESS THAN (10) ENGINE = InnoDB,
 PARTITION p1 VALUES LESS THAN (20) ENGINE = InnoDB,
 PARTITION p2 VALUES LESS THAN (30) ENGINE = InnoDB) */
1 row in set (0.00 sec)

ERROR: 
No query specified

mysql> EXPLAIN select p.id from test_range_partition p where p.`name`='张三'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
   partitions: p0,p1,p2
         type: ref
possible_keys: index_name
          key: index_name
      key_len: 768
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using index
1 row in set, 1 warning (0.00 sec)

ERROR: 
No query specified

mysql> EXPLAIN select p.* from test_range_partition p where p.`name`='张三'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
   partitions: p0,p1,p2
         type: ref
possible_keys: index_name
          key: index_name
      key_len: 768
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

ERROR: 
No query specified

mysql> EXPLAIN select count(*) from test_range_partition p where p.`name`='张三'\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: p
   partitions: p0,p1,p2
         type: ref
possible_keys: index_name
          key: index_name
      key_len: 768
          ref: const
         rows: 1
     filtered: 100.00
        Extra: Using index
1 row in set, 1 warning (0.00 sec)

3. FORCE/USE INDEX

强制使用指定索引;USE INDEX:告诉优化器使用指定索引,但优化器不一定选择该索引。

mysql> EXPLAIN select d.mgdb_id from t_activity_detail d where d.season_id='1100490203' AND d.mgdb_id like '12000018038%';
+----+-------------+-------+------------+-------+-----------------------+---------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys         | key           | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+-----------------------+---------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | d     | NULL       | range | PRIMARY,idx_season_id | idx_season_id | 517     | NULL |    7 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+-----------------------+---------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN select d.mgdb_id from t_activity_detail d FORCE INDEX(PRIMARY) where d.season_id='1100490203' AND d.mgdb_id like '12000018038%';
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | d     | NULL       | range | PRIMARY       | PRIMARY | 258     | NULL |   30 |    10.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN select d.mgdb_id from t_activity_detail d USE INDEX(PRIMARY) where d.season_id='1100490203' AND d.mgdb_id like '12000018038%';
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | d     | NULL       | range | PRIMARY       | PRIMARY | 258     | NULL |   30 |    10.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN select d.mgdb_id from t_activity_detail d USE INDEX(idx_season_id) where d.season_id='1100490203' AND d.mgdb_id like '12000018038%';
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type  | possible_keys | key           | key_len | ref  | rows | filtered | Extra                    |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
|  1 | SIMPLE      | d     | NULL       | range | idx_season_id | idx_season_id | 517     | NULL |    7 |   100.00 | Using where; Using index |
+----+-------------+-------+------------+-------+---------------+---------------+---------+------+------+----------+--------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> EXPLAIN select d.* from t_activity_detail d USE INDEX(PRIMARY) where d.mgdb_id like '%12000018038%';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | d     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 6596 |    11.11 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

4. 多范围读(MRR)

随机访问转为较为顺序的访问,减少磁盘访问次数。其实现原理为:

  • step1:获取到辅助索引键值,存放到缓存,此时根据辅助索引键值顺序存放
  • step2:缓存中辅助索引键值根据主键(RowId)排序
  • step3:排序后主键访问聚集索引。

mrr=on时,则MRR开启,mrr_cost_based则通过cost_based方式使用mrr。如果mrr=on且mrr_cost_based=off,则表示总是启用MRR。

read_rnd_buffer_size控制键值缓存区的大小,默认256KB。若实际大小 > 该值时,则执行器对已经缓存的数据根据rowId排序,并通过rowId获取数据。

mysql> show variables like 'optimizer_switch'\G;
*************************** 1. row ***************************
Variable_name: optimizer_switch
        Value: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,prefer_ordering_index=on
1 row in set (0.02 sec)

ERROR: 
No query specified

mysql> show variables like 'read_rnd_buffer_size'\G;
*************************** 1. row ***************************
Variable_name: read_rnd_buffer_size
        Value: 262144
1 row in set (0.00 sec)

MRR适用于range、ref、eq_ref的查询类型。range查找时,MRR将范围查找,拆分为键值对,进行批量查询。MRR查询的优点:

  • 主键顺序查找,重复性降低,提升效率
  • 减少缓冲池中页被替换的次数
  • 批量处理对键值的查询

5. 索引条件下推(ICP)

取出索引的同时,进行WHERE条件的过滤,即:WHERE部分过滤条件放在存储引擎层。MySQL5.6之前不支持ICP,则根据索引获取记录,再进行WHERE条件的过滤。

大大减少上层SQL对记录的索取,从而提高数据库的整体性能。通过测试,MySQL5.6 with ICP比MySQL5.5版本提高23%;MySQL5.6 with ICP & MRR时,提高了400%。通过EXPLAIN执行计划的列Extra为Using index condition来判定使用了ICP优化

ICP适用于range、ref、eq_ref、ref_or_null的查询类型。如下所示,看出可能使用的索引(possible_keys)有:idx_end_time,idx_start_time,idx_endtime_starttime,但优化器最终使用了key为idx_end_time的索引。列Extra有Using index condition,则表示使用ICP优化。

mysql> EXPLAIN select d.* from t_activity_detail d where (d.start_time BETWEEN '2021-09-01' AND '2021-09-30') AND (d.end_time BETWEEN '2021-09-15' AND '2021-09-30')\G;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: d
   partitions: NULL
         type: range
possible_keys: idx_end_time,idx_start_time,idx_endtime_starttime
          key: idx_end_time
      key_len: 6
          ref: NULL
         rows: 797
     filtered: 18.72
        Extra: Using index condition; Using where
1 row in set, 1 warning (0.00 sec)

六、Cardinality值

1. SHOW INDEX FROM命令

        SHOW INDEX FROM命令查看表的索引信息,若下表所示,每列的含义。

SHOW INDEX FROM t_activity_detail;

列名

描述

Table

索引所在的表

Non_unique

1. 非唯一索引;

2. 主键索引primary key是0,其他1。

Key_name

索引名称

Seq_in_index

1. 索引中该列的位置;

2. 查看联合索引,比较直观

Column_name

索引列的名称

Collation

1. 排序规则:列以什么方式存储在索引中;

2. B+树索引,Collation总是A;若是Hash索引,则为null。

Cardinality

1. Cardinality值表示索引中不重复的记录数量(动态统计);

2. 作用:优化器根据Cardinality值判断是否使用该索引;

3. Cardinality值是动态统计的结果,不是一个精确值;

4. ANALYZE TABLE命令可以更新该值。

Sub_part

1. 是否是列的部分字符被索引;

2. 若是索引整个列,则该值为null;

示例值是100,则表示列的前100个字符被索引

Packed

是否被压缩,没有则为null

Null

1. 索引的列是否含有NULL值;

2. Yes:表示索引的列可以为NULL。

Index_type

1. 索引类型;

2. B+树索引,则为BTREE。

Comment

注释

Index_comment

索引注释

mysql> SHOW INDEX FROM t_activity_detail;
+-------------------+------------+-----------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+----------------+
| Table             | Non_unique | Key_name              | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment  |
+-------------------+------------+-----------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+----------------+
| t_activity_detail |          0 | PRIMARY               |            1 | mgdb_id          | A         |        6596 |     NULL | NULL   |      | BTREE      |         |                |
| t_activity_detail |          1 | idx_game_id2          |            1 | game_id          | A         |        1990 |     NULL | NULL   | YES  | BTREE      |         |                |
| t_activity_detail |          1 | idx_game_id2          |            2 | language         | A         |        5939 |     NULL | NULL   | YES  | BTREE      |         |                |
| t_activity_detail |          1 | idx_end_time          |            1 | end_time         | A         |        1812 |     NULL | NULL   | YES  | BTREE      |         |                |
| t_activity_detail |          1 | idx_start_time        |            1 | start_time       | A         |        1563 |     NULL | NULL   | YES  | BTREE      |         |                |
| t_activity_detail |          1 | idx_match_start_time  |            1 | match_start_time | A         |        1532 |     NULL | NULL   | YES  | BTREE      |         |                |
| t_activity_detail |          1 | idx_stadium_id        |            1 | stadium_id       | A         |           1 |     NULL | NULL   | YES  | BTREE      |         |                |
| t_activity_detail |          1 | idx_endtime_starttime |            1 | end_time         | A         |        1812 |     NULL | NULL   | YES  | BTREE      |         |                |
| t_activity_detail |          1 | idx_endtime_starttime |            2 | start_time       | A         |        1836 |     NULL | NULL   | YES  | BTREE      |         |                |
| t_activity_detail |          1 | idx_season_id         |            1 | season_id        | A         |         413 |     NULL | NULL   | YES  | BTREE      |         | 赛季ID索引     |
+-------------------+------------+-----------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+----------------+
10 rows in set (0.00 sec)

2. Cardinality值

B+树索引的适用条件是高选择性且取少量数据。某个字段值范围广,几乎没有重复,属于高选择性;如性别、类型、地区字段等,取值范围小,属于低选择性。

Cardinality值非常关键,表示索引中不重复记录数量的预估值。优化器会根据该值判断是否使用该索引。

动态统计的结果,其统计发生在INSERT和UPDATE两操作。默认随机对8个叶子节点进行采样,由参数innodb_stats_sample_pages控制每次采样统计的页数,默认8。

        下图所示,触发Cardinality值更新条件,分为手动、自动触发。

联合索引叶子节点是什么 联合索引树结构_数据结构_17

        下表所示, Cardinality值统计的相关参数。

参数

描述

innodb_stats_sample_pages

Cardinality的每次采样统计的页数,默认8页

innodb_stats_method

1. Cardinality采样统计对Null的处理;

2. 值:默认nulls_equal(NULL值作为相同记录)、

                  nulls_unequal(NULL值作为不同记录)、                 

                  nulls_ignored(忽略NULL)

innodb_stats_persistent

1. analyze table触发Cardinality统计时,其值是否存入磁盘;

2. 默认ON(减少重新计算每个索引的Cardinality值)

innodb_stats_on_metadata

show table status、show index、访问information_schema下的TABLES和STATISTICS时,是否开启Cardinality统计,默认OFF

innodb_stats_persistent_

sample_pages

innodb_stats_persistent为ON时,使用analyze table每次采样统计的页数,默认20

innodb_stats_transient_

sample_pages

代替innodb_stats_sample_pages参数,默认8页

七、参考资料

B+ Tree Visualization

深入解析mysql中的索引(原理详解)-mysql教程-PHP中文网

https://www.jb51.net/article/196415.htm