前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >show index from 及analyze table 详解

show index from 及analyze table 详解

作者头像
保持热爱奔赴山海
修改2019-09-17 10:38:37
1K0
修改2019-09-17 10:38:37
举报
文章被收录于专栏:饮水机管理员饮水机管理员

内容参考:官方文档、老叶茶馆、姜承尧的InnoDB存储引擎2

https://mp.weixin.qq.com/s/1MsyxhtG6Zk3Q9gIV2QVbA

https://dev.mysql.com/doc/refman/5.7/en/show-index.html

https://dev.mysql.com/doc/refman/5.7/en/innodb-analyze-table-complexity.html

https://dev.mysql.com/doc/refman/5.7/en/innodb-statistics-estimation.html

show index from tb1 \G 或者使用select * from information_schema.STATISTICS \G 都可以列出表上面存在哪些索引及其它相关信息。

创建一个测试表:

代码语言:javascript
复制
CREATE TABLE `t1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(20) CHARACTER SET latin1 NOT NULL DEFAULT '',
  `age` int(11) DEFAULT NULL, 
  `sex` enum('F','M') CHARACTER SET latin1 DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`),
  KEY `idx_name_age` (`name`,`age`),
  KEY `idx_name_sub` (`name`(5))
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 ;
INSERT INTO t1 (name,age,sex) VALUES('zhang san',24,'F');
INSERT INTO t1 (name,age,sex) VALUES('li si',26,'F');
INSERT INTO t1 (name,age,sex) VALUES('wang wu',20,'M');
INSERT INTO t1 (name,age,sex) VALUES('zhao liu',34,'M');
INSERT INTO t1 (name,age,sex) VALUES('John',64,'F');
show index from t1 ;
代码语言:javascript
复制
+---------+--------------+--------------+----------------+---------------+-------------+---------------+------------+----------+--------+--------------+-----------+-----------------+
| Table   |   Non_unique | Key_name     |   Seq_in_index | Column_name   | Collation   |   Cardinality |   Sub_part |   Packed | Null   | Index_type   | Comment   | Index_comment   |
|---------+--------------+--------------+----------------+---------------+-------------+---------------+------------+----------+--------+--------------+-----------+-----------------|
| t1      |            0 | PRIMARY      |              1 | id            | A           |             5 |     <null> |   <null> |        | BTREE        |           |                 |
| t1      |            1 | idx_name     |              1 | name          | A           |             5 |     <null> |   <null> |        | BTREE        |           |                 |
| t1      |            1 | idx_name_age |              1 | name          | A           |             5 |     <null> |   <null> |        | BTREE        |           |                 |
| t1      |            1 | idx_name_age |              2 | age           | A           |             5 |     <null> |   <null> | YES    | BTREE        |           |                 |
| t1      |            1 | idx_name_sub |              1 | name          | A           |             5 |          5 |   <null> |        | BTREE        |           |                 |
+---------+--------------+--------------+----------------+---------------+-------------+---------------+------------+----------+--------+--------------+-----------+-----------------

详解:

    1.Table  

    表的名称。

    2.Non_unique 

    如果索引不能包括重复词,则为0。如果可以,则为1。

    3.Key_name 

    索引的名称。

    4.Seq_in_index 

    索引中的列序列号,从1开始。

    5.Column_name 

    列名称。

    6.Collation 

    列以什么方式存储在索引中。在MySQL中,有值‘A’(升序)或NULL(无分类)。[InnoDB都是A,Heap表都是NULL]

    7.Cardinality 

    非常关键的一个参数。表示的是索引中唯一值的数目的估计值。

    Cardinality/n_rows_in_table的值应尽可能接近1,如果非常小,那么用户需要考虑是否可以删除该索引。

    8.Sub_part 

    如果列只是被部分地编入索引,则为被编入索引的字符的数目。如果整列被编入索引,则为NULL。

    9.Packed 

    指示关键字如何被压缩。如果没有被压缩,则为NULL。

    10.Null 

    索引的列中含有NULL。含有NULL则为YES。如果没有,则这里显示为空。

    11.Index_type 

    索引的类型(BTREE, FULLTEXT, HASH, RTREE)。这里是InnoDB存储引擎,所以显示的都是BTREE

    12.Comment 

    多种评注。 

上面这一堆的状态值中,最重要的就是Cardinality。

Cardinality 值:

    表的cardinality(可以翻译为“散列程度”),优化器会根据这个值来判断是否使用这个索引。但是这个值不是实时更新的,因为实时的话代价太大了,因此这个值不是太准确,只是个估值。

    如果cardinality大大少于数据的实际散列程度,那么索引就基本失效了。

    一般经验是,在访问表中很少一部分时使用B+树索引才会有意义。对应性别、地区、这些字段,它们可取值的范围很小,称为低选择性。如:

        SELECT* FROM students WHERE sex='M';

    这种查询的话,在大量样本的情况下一般能返回50% 的数据,这时添加B+树索引是完全没有必要的。

    相反,如果某个字段的取值范围很广,几乎没有重复,即属于高选择性,则此时建议使用B+树索引。

    例如,对应姓名字段,基本上在一个应用中不允许重名的出现。

    在实际应用中,cardinality/n_rows_in_table应尽可能地接近1。如果非常小,那么用户需要考虑是否还有必要创建这个索引了。

    故在访问高选择性属性的字段并从表中去除很少一部分数据时,对这个字段添加B+树索引是非常有必要的。

Cardinality 统计值的更新:

    MySQL对Cardinality的统计是放在存储引擎层进行的。

    在生产环境,索引的更新操作可能会非常的频繁。如果每次索引在发生操作时就对其进行Cardinality统计的话,会给数据库造成很大的负担。

    假如一张表数据非常大,对其进行一次Cardinality统计可能要花费很长的时间,这对于生产环境而言,是不可接受的。

    因此,数据库对于Cardinality的统计都是通过采样(Sample)的方法来完成的。

    在InnoDB存储引擎中,Cardinality统计信息的更新发生在两个操作中:INSERT、UPDATE。但是我们不可能每次变动就去更新Cardinality,这样消耗太大了。

    因此,InnoDB存储引擎内部对更新Cardinality信息的策略为:

        1、 表中1/16的数据已发生过变化。

        2、 stat_modified_counter > 20 0000 0000  【20亿行】

    第一种策略是自从上次统计Cardinality信息后,表中1/16的数据已经发生过变化,这时需要更新Cardinality信息。

    第二种情况考虑的是,如果对表中某一行数据频繁地更新操作,这时表中的数据实际上并没有增加,实际发生变化的还是这一样数据,则第一种更新策略就无法适用这种情况。故在InnoDB存储引擎内部有一个计数器stat_modified_counter,用来表示发生变化的次数,当stat_modified_counter大于20 0000 0000时,则同样需要更新Cardinality信息。

InnoDB存储引擎内部是如何进行Cardinality信息的统计和更新操作的?

方法如下:

   1、取得B+树所以中叶子节点的数量,记为A。

   2、InnoDB存储引擎随机对8个叶子节点(leaf page)进行采样,统计每个页不同记录的个数,记为P1,P2 ... P8。

   3、估算出的Cardinality值为:(P1+P2...+P8) * A/8

        因为是随机取8个叶子节点做的统计,因此每次算出的Cardinality可能都不一样。如下我从脱敏的数据库摘录的例子:

代码语言:javascript
复制
        > use Mobile;
        > show index from wx_table\G
        *************************** 1. row ***************************
                Table: wx_table
           Non_unique: 0
             Key_name: PRIMARY
         Seq_in_index: 1
          Column_name: Id
            Collation: A
          Cardinality: 39129       #第一次计算的Cardinality结果。表示的是存储引擎估算表中有多少个不同的值
             Sub_part: NULL
               Packed: NULL
                 Null: 
           Index_type: BTREE
              Comment: 
        Index_comment: 
        1 row in set (0.00 sec)
        > analyze table wx_table;  #手工执行下,以便更新 Cardinality 值,不然要等到InnoDB的刷新要等好久。
        +--------------------+---------+----------+----------+
        | Table              | Op      | Msg_type | Msg_text |
        +--------------------+---------+----------+----------+
        |    Mobile.wx_table | analyze | status   | OK       |
        +--------------------+---------+----------+----------+
        1 row in set (0.01 sec)
        > show index from wx_table\G
        *************************** 1. row ***************************
                Table: wx_table
           Non_unique: 0
             Key_name: PRIMARY
         Seq_in_index: 1
          Column_name: Id
            Collation: A
          Cardinality: 41417    第二次计算的Cardinality结果
             Sub_part: NULL
               Packed: NULL
                 Null: 
           Index_type: BTREE
              Comment: 
        Index_comment: 
        1 row in set (0.00 sec)

        如果我们再次执行 analyze table wx_table;  然后执行show index from wx_table\G 可以发现Cardinality的值又变了。

        另外,如果用户不管怎么刷新,看到的Cardinality值都是不变的。一般这种情况发生在表足够小,表的叶子节点数小于或等于8。这样无论怎样随机采样,都会全部采样,算出的Cardinality总是一样的。

与之相关的参数有:

代码语言:javascript
复制
             >show VARIABLES like 'innodb_stats%';
            +--------------------------------------+-------------+
            | Variable_name                        | Value       |
            |--------------------------------------+-------------|
            | innodb_stats_auto_recalc             | ON          |
            | innodb_stats_method                  | nulls_equal |
            | innodb_stats_on_metadata             | OFF         |
            | innodb_stats_persistent              | ON          |
            | innodb_stats_persistent_sample_pages | 20          |
            | innodb_stats_sample_pages            | 8           |
            | innodb_stats_transient_sample_pages  | 8           |
            +--------------------------------------+-------------+

        innodb_stats_sample_pages 

            设置采样的页的数量,默认是8

            MySQL5.6.3开始已经废弃,改为使用innodb_stats_transient_sample_pages

        innodb_stats_method

            判断如何对待索引中出现的NULL值记录。

            默认是nulls_equal,表示将NULL值记录视为相等的记录。

            其有效值还有nulls_unequal和nulls_ignored。 nulls_unequal表示将NULL值记录视为不同的记录,nulls_ignored表示忽略NULL值记录。

        举例子:

            假如采样的8个页中索引记录值为 NULL、NULL、1、2、2、3、3、3。

            如果设置innodb_stats_method=nulls_equal,则Cardinality为4; 【采样值:NULL、1、2、3】

            如果设置innodb_stats_method=nulls_unequal,则Cardinality为5; 【采样值:NULL、NULL、1、2、3】

            如果设置innodb_stats_method=nulls_ignored,则Cardinality为3; 【采样值:1、2、3】

        注意:

            当我们执行analyze table xxx; show table status; show index from tb_name; 以及访问information_schema架构下的表tables和statistics时会导致InnoDB存储引擎去重新计算索引的Cardinality值。

            若表中存在多个辅助索引时,执行上述这些操作可能会非常慢,虽然用户可能并不希望去更新Cardinality值。

    在innodb1.2后,对这些参数又做了下扩展及修改:

        innodb_stats_persistent 默认ON

            控制是否使用永久化的统计数据(也就是是否将analyze table计算出的Cardinality值存放到磁盘上)。

设置为ON的好处是可以减少重新计算每个索引的Cardinality值,例如当MySQL数据库重启时。

            此外,用户也可通过命令create table和alter table的选项STATS_PERSISTENT来对每张表进行控制。

            例如:

                关闭某张表的采样统计:

                    CREATE TABLE `t2` (a int(11) NOT NULL AUTO_INCREMENT,b int(11) DEFAULT NULL, PRIMARY KEY (a), KEY idx_b (b)) ENGINE=InnoDB STATS_PERSISTENT=0;

                手动设置采样的page数量:

                    CREATE TABLE `t3` (a int(11) NOT NULL AUTO_INCREMENT,b int(11) DEFAULT NULL, PRIMARY KEY (a), KEY idx_b (b)) ENGINE=InnoDB STATS_SAMPLE_PAGES=30;

            永久化的统计数据存储在 mysql.innodb_index_stats 和 mysql.innodb_table_stats 中,官方文档说统计数据并非实时的,也就是收集统计数据会滞后几分钟,如果想要及时的更新统计可以执行analyze table。

innodb_stats_persistent_sample_pages  默认是20 

            只有在 innodb_stats_persistent=ON时候,innodb_stats_persistent_sample_pages 参数的设置才会生效。 【为了获得更准确的执行计划,我们可以设置my.cnf里面这个值稍微大些,如设置为32或者64】

            官方文档:

                innodb_stats_persistent_sample_pages only applies when innodb_stats_persistent is enabled for a table; when innodb_stats_persistent is disabled, innodb_stats_transient_sample_pages applies instead.

                https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_stats_on_metadata

            该参数表示analyze table更新Cardinality值时每次需要采样的页的数量。默认是20个页。

            增加这个值,可以提高统计信息的精确度,同样也能提高执行计划的准确性,不过也相应增加了analyze table的时间,也会增加在InnoDB表上分析的I/O开销。

       innodb_stats_on_metadata  默认为OFF 【不建议设置为ON】

            注意:这个参数只有在 innodb_stats_persistent=OFF 时候才会起作用。

            【官方文档:https://dev.mysql.com/doc/refman/5.7/en/innodb-statistics-estimation.html

            innodb_stats_on_metadata only applies when optimizer statistics are configured to be non-persistent (when innodb_stats_persistent is disabled).】

            如果这个参数是ON的话, 使用show table status、show index from tb_name、及访问information_schema架构下的表tables和statistics时,会立即导致存储引擎重新计算索引的Cardinality值,这个造成的影响在日常业务时间是不能接受。

        innodb_stats_transient_sample_pages

            表示每次采样页的数量。默认是8。该参数用来取代老版本里面之前版本的参数 innodb_stats_sample_pages

            只有在 某张表  STATS_PERSISTENT=0 时候, innodb_stats_transient_sample_pages 参数的设置才会生效。

            官方文档:

                innodb_stats_transient_sample_pages only applies when innodb_stats_persistent is disabled for a table; when innodb_stats_persistent is enabled, innodb_stats_persistent_sample_pages applies instead.

                https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_stats_on_metadata

        innodb_stats_auto_recalc

            用于决定是否在表上存在大量更新时(超过10%的记录更新)重新计算统计信息。

            默认打开.

            如果关闭该选项,就需要在每次创建索引或者更改列之后,运行一次ANALYZE TABLE命令来更新统计信息,否则可能选择错误的执行计划。

            同样的,也可以在CREATE TABLE/ALTER TABLE命令中指定STATS_AUTO_RECALC值。

analyze table 说明: 

    老叶博客:https://mp.weixin.qq.com/s/1MsyxhtG6Zk3Q9gIV2QVbA

    官网:https://dev.mysql.com/doc/refman/5.7/en/innodb-analyze-table-complexity.html

ANALYZE TABLE 作用:

    ANALYZE TABLE 会统计索引分布信息,并将结果持久化存储;

    对于 MyISAM 表,相当于执行了一次 myisamchk –analyze;

    支持 InnoDB、NDB、MyISAM 等存储引擎,但不支持 视图(view);

    ANALYZE TABLE也可以用在表分区上;

    对InnoDB、MyISAM表执行 ANALYZE TABLE 时,会加上读锁(read lock);

    执行 ANALYZE TABLE 会记录binlog。(这是合理的,因为索引分析这个操作,在MASTER端执行完后,SLAVE端也是需要的)

    ANALYZE TABLE 代价估算:

        影响代价因素:

            innodb_stats_persistent_sample_pages定义值大小;

            表中索引数多少;

            表中分区数多少。

        analye table的代价 = innodb_stats_persistent_sample_pages * 索引数 * 分区数

        而更严谨的计算公式见下:

            O(n_sample * (n_cols_in_uniq_i + n_cols_in_non_uniq_i + n_cols_in_pk * (1 + n_non_uniq_i)) * n_part)

            各项指标解释:

                n_sample 

                    采集的data page数量

                    is the number of pages sampled (defined by innodb_stats_persistent_sample_pages)  

                n_cols_in_uniq_i 

                    所有唯一索引(不含主键索引)中的列总数

                    is total number of all columns in all unique indexes (not counting the primary key columns)

                n_cols_in_non_uniq_i 

                    所有普通索引中的列总数

                    is the total number of all columns in all non-unique indexes

                n_cols_in_pk 

                    主键索引中的列总数(若未显式定义主键,则相当于只有一列的ROWID)

                    is the number of columns in the primary key (if a primary key is not defined, InnoDB creates a single column primary key internally)

                n_non_uniq_i 

                    非唯一索引数量

                    is the number of non-unique indexes in the table

                n_part 

                    表分区数量

                    is the number of partitions. If no partitions are defined, the table is considered to be a single partition.

            mysql官网的实例:

代码语言:javascript
复制
                USE test;
                CREATE TABLE t (
                  a INT,
                  b INT,
                  c INT,
                  d INT,
                  e INT,
                  f INT,
                  g INT,
                  h INT,
                  PRIMARY KEY (a, b),
                  UNIQUE KEY i1uniq (c, d),
                  KEY i2nonuniq (e, f),
                  KEY i3nonuniq (g, h)
                );
                SELECT index_name, stat_name, stat_description
                  FROM mysql.innodb_index_stats
                  WHERE
                  database_name='test' AND
                  table_name='t' AND
                  stat_name like 'n_diff_pfx%';
                  +------------+--------------+------------------+
                  | index_name | stat_name    | stat_description |
                  +------------+--------------+------------------+
                  | PRIMARY    | n_diff_pfx01 | a                |
                  | PRIMARY    | n_diff_pfx02 | a,b              |
                  | i1uniq     | n_diff_pfx01 | c                |
                  | i1uniq     | n_diff_pfx02 | c,d              |
                  | i2nonuniq  | n_diff_pfx01 | e                |
                  | i2nonuniq  | n_diff_pfx02 | e,f              |
                  | i2nonuniq  | n_diff_pfx03 | e,f,a            |
                  | i2nonuniq  | n_diff_pfx04 | e,f,a,b          |
                  | i3nonuniq  | n_diff_pfx01 | g                |
                  | i3nonuniq  | n_diff_pfx02 | g,h              |
                  | i3nonuniq  | n_diff_pfx03 | g,h,a            |
                  | i3nonuniq  | n_diff_pfx04 | g,h,a,b          |
                  +------------+--------------+------------------+

                上面这个结果看起来有点奇怪是不是,其实没错,先科普几点知识:

                    所有的普通索引,实际物理存储时,都要包含主键列的,也就是所谓的 index extensions 特性;

                    统计索引信息时,是根据最左原则,要统计各种组合的。比如(a,b) 索引,要统计(a), (a,b), (a,b,pk) 三种信息,而不是只统计(a,b)这个信息;

                    不过,在 mysql.innodb_index_stats 中存储统计信息时,是不统计唯一索引后面存储主键列信息的,非唯一普通索引后存储主键列信息则会被统计进去;

                    因此,上面 mysql.innodb_index_stats 中存储的统计结果是正确的。

            我们再回来看下索引统计的代价公式,像下面这样计算:

                - n_sample,采集的data page数量,值为 20(默认值);

                - n_cols_in_uniq_i,所有唯一索引(不含主键索引)中的列总数,值为 2(c和d)

                - n_cols_in_non_uniq_i,所有普通索引中的列总数,值为 4 (e、f、g、h)

                - n_cols_in_pk,主键索引中的列总数(若未显式定义主键,则相当于只有一列的ROWID),值为 2 (a、b)

                - n_non_uniq_i,非唯一索引数量,值为 2 (注意,这里指的是索引的数量,而不是不是列的数量。因此这里是i2nonuniq、i3nonuniq)

                - n_part,表分区数量,值为 1(没有表分区,值为1,而不是0)。

                那么根据公式最终需要扫描的data page数结果就是:

                    20 * (2 + 4 + 2 * (1 + 2)) * 1 = 240  (单位是pages)

                    实际需要读取的字节数则是:240 * 16*1024 = 3932160 (即 3.84M)

                    当然了,要读取的data page,有可能已经在buffer pool中了,因此并不全是物理读。

                    从中,我们也可以看到,这个代价和表的数据量并无直接关系。

                    不过,当表数量越大时,聚集索引的 B+ 树也越大,搜索代价肯定也越大。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017-08-19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档