美文网首页
MySQL索引详解

MySQL索引详解

作者: 王侦 | 来源:发表于2022-12-01 23:28 被阅读0次

    1.MySQL索引数据结构

    MySQL索引有两种实现,哈希表和B+树。

    1.1 哈希表

    大量唯一等值查询时,哈希索引效率通常更高,但是它也有一些问题:

    • 哈希索引对于范围查询和模糊匹配查询显得无能为力。
    • 哈希索引不支持排序操作,对于多列联合索引的最左匹配规则也不支持。
    • 哈希索引不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。
    • 访问哈希索引的数据非常快,除非有很多哈希冲突(不同的索引列值却有相同的哈希值)当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行进行比较,直到找到所有符合条件的行。

    1.2 B+树

    B+树是B树的变体,也是一种多路搜索树, 它与 B树的不同之处在于

    • B+树内节点不存储数据,所有 data 存储在叶节点导致查询时间复杂度固定为 log n。而B-树查询时间复杂度不固定,与 key 在树中的位置有关,最好为O(1)。
    • B+树叶节点两两相连可大大增加区间访问性,可使用在范围查询等,而B树每个节点 key 和 data 在一起,则无法区间查找。
    • B+树更适合外部存储。由于内节点无 data 域,每个节点能索引的范围更大更精确。
      这个很好理解,由于B树节点内部每个 key 都带着 data 域,而B+树节点只存储 key 的副本,真实的 key 和 data 域都在叶子节点存储。前面说过磁盘是分 block 的,一次磁盘 IO 会读取若干个 block,具体和操作系统有关,那么由于磁盘 IO 数据大小是固定的,在一次 IO 中,单个元素越小,量就越大。这就意味着B+树单次磁盘 IO 的信息量大于B树,从这点来看B+树相对B-树磁盘 IO 次数少。

    2.Explain查看执行计划

    通过explain命令我们了解该条SQL是如何执行的,有没有使用索引,使用的是哪个索引等等。根据其结果,可以指导我们优化SQL语句,尤其是索引优化相关的。

    explain中的列:

      1. id列
        id列的编号是 select 的序列号,有几个 select 就有几个id,并且id的顺序是按 select 出现的顺序增长的。
        id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。
      1. select_type列
        select_type 表示对应行是简单还是复杂的查询。
        1)simple:简单查询。查询不包含子查询和union
        2)primary:复杂查询中最外层的 select
        3)subquery:包含在 select 中的子查询(不在 from 子句中)
        4)derived:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含 义)
        5)union:在 union 中的第二个和随后的 select
      1. table列
        这一列表示 explain 的一行正在访问哪个表。
        当 from 子句中有子查询时,table列是 <derivenN> 格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查询。
        当有 union 时,UNION RESULT 的 table 列的值为<union1,2>,1和2表示参与 union 的 select 行id。
      1. type列
        这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。
        依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL
        一般来说,得保证查询达到range级别,最好达到ref 。
        NULL:mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表。
        const, system:mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。用于 primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是 const的特例,表里只有一条元组匹配时为system。
        eq_ref:primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的联接类型了,简单的 select 查询不会出现这种 type。
        ref:相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。
        range:范围扫描通常出现在 in(), between ,> ,<, >= 等操作中。使用一个索引来检索给定范围的行。
        index:扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接 对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这 种通常比ALL快一些。
        ALL:即全表扫描,扫描你的聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。
      1. possible_keys列
        这一列显示查询可能使用哪些索引来查找。
        explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引 对此查询帮助不大,选择了全表查询。
        如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可以创造一个适当的索引来提 高查询性能,然后用 explain 查看效果。
      1. key列
        这一列显示mysql实际采用哪个索引来优化对该表的访问。
        如果没有使用索引,则该列是 NULL。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force index、ignore index。
      1. key_len列
        这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。
      1. ref列
        这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:film.id)
      1. rows列
        这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。
    • 10.filtered列
      rows * filtered/100 可以估算出将要和 explain 中前一个表进行连接的行数(前一个表指 explain 中的id值比当前表id值小的表)。
      1. Extra列
        1)Using index:使用覆盖索引。
        2)Using where:使用 where 语句来处理结果,并且查询的列未被索引覆盖。
        3)Using index condition:查询的列不完全被索引覆盖,where条件中是一个前导列的范围。
        4)Using temporary:mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索 引来优化。
        5)Using filesort:将用外部排序而不是索引排序,数据较小时从内存排序,否则需要在磁盘完成排序。这种情况下一 般也是要考虑使用索引来优化的。
        6)Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引的某个字段是

    3.索引使用规则

    总结如下:

    • 1.全值匹配
    • 2.最左前缀法则
      如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
    • 3.不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
    • 4.存储引擎不能使用索引中范围条件右边的列
    • 5.尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少 select * 语句
    • 6.mysql在使用不等于(!=或者<>),not in ,not exists 的时候无法使用索引会导致全表扫描
      < 小于、 > 大于、 <=、>= 这些,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引
    • 7.is null,is not null 一般情况下也无法使用索引
    • 8.like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作
    • 9.字符串不加单引号索引失效
    • 10.少用or或in,用它查询时,mysql不一定使用索引,mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引,详见范围查询优化
    • 11.范围查询优化

    4.索引设计原则

    总结如下:

    • 1、代码先行,索引后上
      一般应该等到主体业务功能开发完毕,把涉及到该表相关sql都要拿出来分析之后再建立索引。
    • 2、联合索引尽量覆盖条件
      比如可以设计一个或者两三个联合索引(尽量少建单值索引),让每一个联合索引都尽量去包含sql语句里的where、order by、group by的字段,还要确保这些联合索引的字段顺序尽量满足sql查询的最左前缀原则。
    • 3、不要在小基数字段上建立索引
      一般建立索引,尽量使用那些基数比较大的字段,就是值比较多的字段,那么才能发挥出B+树快速二分查找的优势来。
    • 4、长字符串我们可以采用前缀索引
      order by、group by会失效,没法使用索引
    • 5、where与order by冲突时优先where
      因为大多数情况基于索引进行where筛选往往可以最快速度筛选出你要的少部分数据,然后做排序的成本可能会小很多。
    • 6、基于慢sql查询做优化

    核心思想就是,尽量利用一两个复杂的多字段联合索引,抗下你80%以上的查询,然后用一两个辅助索引尽量抗下剩余的一些非典型查询,保证这种大数据量表的查询尽可能多的都能充分利用索引,这样就能保证你的查询速度和性能了!

    5.索引优化

    5.1 trace工具

    mysql最终如何选择索引,我们可以用trace工具来一查究竟,开启trace工具会影响mysql性能,所以只能临时分析sql使用,用完之后立即关闭。

    mysql> set session optimizer_trace="enabled=on",end_markers_in_json=on;  --开启trace
    mysql> select * from employees where name > 'a' order by position;
    mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE;
    

    查看trace字段:

    {
      "steps": [
        {
          "join_preparation": {    --第一阶段:SQL准备阶段,格式化sql
            "select#": 1,
            "steps": [
              {
                "expanded_query": "/* select#1 */ select `employees`.`id` AS `id`,`employees`.`name` AS `name`,`employees`.`age` AS `age`,`employees`.`position` AS `position`,`employees`.`hire_time` AS `hire_time` from `employees` where (`employees`.`name` > 'a') order by `employees`.`position`"
              }
            ] /* steps */
          } /* join_preparation */
        },
        {
          "join_optimization": {    --第二阶段:SQL优化阶段
            "select#": 1,
            "steps": [
              {
                "condition_processing": {    --条件处理
                  "condition": "WHERE",
                  "original_condition": "(`employees`.`name` > 'a')",
                  "steps": [
                    {
                      "transformation": "equality_propagation",
                      "resulting_condition": "(`employees`.`name` > 'a')"
                    },
                    {
                      "transformation": "constant_propagation",
                      "resulting_condition": "(`employees`.`name` > 'a')"
                    },
                    {
                      "transformation": "trivial_condition_removal",
                      "resulting_condition": "(`employees`.`name` > 'a')"
                    }
                  ] /* steps */
                } /* condition_processing */
              },
              {
                "substitute_generated_columns": {
                } /* substitute_generated_columns */
              },
              {
                "table_dependencies": [    --表依赖详情
                  {
                    "table": "`employees`",
                    "row_may_be_null": false,
                    "map_bit": 0,
                    "depends_on_map_bits": [
                    ] /* depends_on_map_bits */
                  }
                ] /* table_dependencies */
              },
              {
                "ref_optimizer_key_uses": [
                ] /* ref_optimizer_key_uses */
              },
              {
                "rows_estimation": [    --预估表的访问成本
                  {
                    "table": "`employees`",
                    "range_analysis": {
                      "table_scan": {     --全表扫描情况
                        "rows": 10123,    --扫描行数
                        "cost": 2054.7    --查询成本
                      } /* table_scan */,
                      "potential_range_indexes": [    --查询可能使用的索引
                        {
                          "index": "PRIMARY",    --主键索引
                          "usable": false,
                          "cause": "not_applicable"
                        },
                        {
                          "index": "idx_name_age_position",    --辅助索引
                          "usable": true,
                          "key_parts": [
                            "name",
                            "age",
                            "position",
                            "id"
                          ] /* key_parts */
                        }
                      ] /* potential_range_indexes */,
                      "setup_range_conditions": [
                      ] /* setup_range_conditions */,
                      "group_index_range": {
                        "chosen": false,
                        "cause": "not_group_by_or_distinct"
                      } /* group_index_range */,
                      "analyzing_range_alternatives": {    --分析各个索引使用成本
                        "range_scan_alternatives": [
                          {
                            "index": "idx_name_age_position",
                            "ranges": [
                              "a < name"      --索引使用范围
                            ] /* ranges */,
                            "index_dives_for_eq_ranges": true,
                            "rowid_ordered": false,    --使用该索引获取的记录是否按照主键排序
                            "using_mrr": false,
                            "index_only": false,       --是否使用覆盖索引
                            "rows": 5061,              --索引扫描行数
                            "cost": 6074.2,            --索引使用成本
                            "chosen": false,           --是否选择该索引
                            "cause": "cost"
                          }
                        ] /* range_scan_alternatives */,
                        "analyzing_roworder_intersect": {
                          "usable": false,
                          "cause": "too_few_roworder_scans"
                        } /* analyzing_roworder_intersect */
                      } /* analyzing_range_alternatives */
                    } /* range_analysis */
                  }
                ] /* rows_estimation */
              },
              {
                "considered_execution_plans": [
                  {
                    "plan_prefix": [
                    ] /* plan_prefix */,
                    "table": "`employees`",
                    "best_access_path": {    --最优访问路径
                      "considered_access_paths": [   --最终选择的访问路径
                        {
                          "rows_to_scan": 10123,
                          "access_type": "scan",     --访问类型:为scan,全表扫描
                          "resulting_rows": 10123,
                          "cost": 2052.6,
                          "chosen": true,            --确定选择
                          "use_tmp_table": true
                        }
                      ] /* considered_access_paths */
                    } /* best_access_path */,
                    "condition_filtering_pct": 100,
                    "rows_for_plan": 10123,
                    "cost_for_plan": 2052.6,
                    "sort_cost": 10123,
                    "new_cost_for_plan": 12176,
                    "chosen": true
                  }
                ] /* considered_execution_plans */
              },
              {
                "attaching_conditions_to_tables": {
                  "original_condition": "(`employees`.`name` > 'a')",
                  "attached_conditions_computation": [
                  ] /* attached_conditions_computation */,
                  "attached_conditions_summary": [
                    {
                      "table": "`employees`",
                      "attached": "(`employees`.`name` > 'a')"
                    }
                  ] /* attached_conditions_summary */
                } /* attaching_conditions_to_tables */
              },
              {
                "clause_processing": {
                  "clause": "ORDER BY",
                  "original_clause": "`employees`.`position`",
                  "items": [
                    {
                      "item": "`employees`.`position`"
                    }
                  ] /* items */,
                  "resulting_clause_is_simple": true,
                  "resulting_clause": "`employees`.`position`"
                } /* clause_processing */
              },
              {
                "reconsidering_access_paths_for_index_ordering": {
                  "clause": "ORDER BY",
                  "steps": [
                  ] /* steps */,
                  "index_order_summary": {
                    "table": "`employees`",
                    "index_provides_order": false,
                    "order_direction": "undefined",
                    "index": "unknown",
                    "plan_changed": false
                  } /* index_order_summary */
                } /* reconsidering_access_paths_for_index_ordering */
              },
              {
                "refine_plan": [
                  {
                    "table": "`employees`"
                  }
                ] /* refine_plan */
              }
            ] /* steps */
          } /* join_optimization */
        },
        {
          "join_execution": {    --第三阶段:SQL执行阶段
            "select#": 1,
            "steps": [
            ] /* steps */
          } /* join_execution */
        }
      ] /* steps */
    }
    

    5.2 Order by与Group by优化

    • 1、MySQL支持两种方式的排序filesort和index,Using index是指MySQL扫描索引本身完成排序。index效率高,filesort效率低。
    • 2、order by满足两种情况会使用Using index。
      1. order by语句使用索引最左前列。
      2. 使用where子句与order by子句条件列组合满足索引最左前列。
    • 3、尽量在索引列上完成排序,遵循索引建立(索引创建的顺序)时的最左前缀法则。
    • 4、如果order by的条件不在索引列上,就会产生Using filesort。
    • 5、能用覆盖索引尽量用覆盖索引
    • 6、group by与order by很类似,其实质是先排序后分组,遵照索引创建顺序的最左前缀法则。对于group by的优化如果不需要排序的可以加上order by null禁止排序。注意,where高于having,能写在where中的限定条件就不要去having限定了。

    5.3 分页查询优化

    select * from employees limit 10000,10;
    

    这条 SQL 是先读取 10010 条记录,然后抛弃前 10000 条记录,然后读到后面 10 条想要的数据。因此要查询一张大表比较靠后的数据,执行效率是非常低的。

    1、根据自增且连续的主键排序的分页查询
    改成:select * from employees where id > 90000 limit 5;

    2、根据非主键字段排序的分页查询
    改成:select * from employees e inner join (select id from employees order by name limit 90000,5) ed on e.id = ed.id;

    5.4 Join关联查询优化

    关联字段加索引,让mysql做join操作时尽量选择NLJ算法,驱动表因为需要全部查询出来,所以过滤的条件也尽量要走索引,避免全表扫描,总之,能走索引的过滤条件尽量都走索引。

    小表驱动大表,写多表连接sql时如果明确知道哪张表是小表可以用straight_join写法固定连接驱动方式,省去mysql优化器自己判断的时间。

    5.5 in和exsits优化

    原则:小表驱动大表,即小的数据集驱动大的数据集

    5.6 count(*)查询优化

    EXPLAIN select count(1) from employees;
    EXPLAIN select count(id) from employees;
    EXPLAIN select count(name) from employees;
    EXPLAIN select count(*) from employees;
    

    四条语句都走的是二级索引,执行效率本质上无太大差别。

    常见优化方法:

    • 1、查询mysql自己维护的总行数
      对于myisam存储引擎的表做不带where条件的count查询性能是很高的,因为myisam存储引擎的表的总行数会被mysql存储在磁盘上,查询不需要计算。
    • 2、show table status
      这个是估计值。
    • 3、将总数维护到Redis里
      插入或删除表数据行的时候同时维护redis里的表总行数key的计数值(用incr或decr命令),但是这种方式可能不准,很难保证表操作和redis操作的事务一致性。
    • 4、增加数据库计数表
      插入或删除表数据行的时候同时维护计数表,让他们在同一个事务里操作。

    相关文章

      网友评论

          本文标题:MySQL索引详解

          本文链接:https://www.haomeiwen.com/subject/haosfdtx.html