MySQL no está en no toma el índice? mierda

El artículo de hoy es la serie anterior de artículos "Por qué 100.000 programadores" .

¿A menudo hay preguntas de entrevistas sobre MySQL que no están en la indexación? Ocasionalmente, un colega dirá, no use not in, porque el rendimiento del índice es malo y el rendimiento de not in está relacionado con la discriminación del campo correspondiente, ¿es esto cierto?

Hoy, Xiaojiang lo llevará a comprender este problema en profundidad. En primer lugar, debemos usar la palabra clave de explicación, por lo que debemos comprender esta palabra clave. explicación es el plan de ejecución, que puede generar la información de ejecución de una declaración de MySQL, de modo que podamos juzgar si se alcanza el índice y si se necesita optimización.

Esquema del artículo

  1. explicar con detalle
  2. Principio de indexación
  3. Principio de consulta de declaración de MySQL
  4. no en principio
  5. En conclusión

Primero, creamos una tabla e insertamos algunos datos para facilitar las siguientes pruebas.

CREATE TABLE test (
    id INT NOT NULL AUTO_INCREMENT,
    second_key INT,
    text VARCHAR(100),
    PRIMARY KEY (id),
    KEY idx_second_key (second_key)
) Engine=InnoDB CHARSET=utf8;
复制代码
INSERT INTO test VALUES
    (1, 10, 't1'),
    (2, 20, 't2'),
    (3, 30, 't3'),
    (4, 40, 't4'),
    (5, 50, 't5'),
    (6, 60, 't6'),
    (7, 70, 't7'),
    (8, 80, 't8');
复制代码

Ejecutando el comando de explicación obtenemos lo siguiente

mysql> explain select * from test \G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 13
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)
复制代码

Hay mucho contenido aquí, pero solo estos campos merecen nuestra atención.

  • escribe
  • filas
  • Extra

Vamos a explicar uno a uno
type representa el tipo que ejecuta MySQL al ejecutar la sentencia actual, hay varios valores system, const, eq_ref, ref, fulltext, ref_or_null, index_merge, unique_subquery, index_subquery, range, index, ALL.

  1. system es relativamente raro. Cuando el motor es MyISAM o Memory y solo hay un registro, es system, lo que significa que se puede acceder con precisión a nivel del sistema. Esto es poco común y se puede ignorar.
  2. La consulta const acierta cuando la clave principal o el índice secundario único coinciden. por ejemplowhere id = 1
  3. Cuando eq_ref conecta tablas, puede usar la clave principal o el índice único para la coincidencia de igual valor.
  4. ref y ref_or_null, cuando los índices y las constantes no únicos coinciden de manera equivalente. Solo ref_or_null significa que la condición de consulta eswhere second_key is null
  5. fulltext, index_merge no se suele omitir.
  6. unique_subquery 和 index_subquery 表示联合语句使用 in 语句的时候命中了唯一索引或者普通索引的等值查询。
  7. range 表示使用索引的范围查询,比如 where second_key > 10 and second_key < 90
  8. index 我们命中了索引,但是需要全部扫描索引。
  9. All,这个太直观了,就是说没有使用索引,走的是全表扫描。

接下来说一下 rows,MySQL 在执行语句的时候,评估预计扫描的行数。

最后就是关键的内容 Extra,别看他是扩展。但是它很重要,因为他更好的辅助你定位 MySQL 到底如何执行的这个语句。我们选择一些重点说一说。

  1. Using index,当我们查询条件和返回内容都存在索引里面,就可以走覆盖索引,不需要回表,比如 select second_key from test where second_key = 10
  2. Using index condition,经典的索引下推,虽然命中了索引,但是并不是严格匹配,需要使用索引进行扫描对比,最后再进行回表,比如 select * from test where second_key > 10 and second_key like '%0';
  3. Using where,当我们使用全表扫描时,并且 Where 中有引发全表扫描的条件时,会命中。比如 select * from test where text = 't'
  4. Using filesort,查询没有命中任何索引,需要在内存或者硬盘中排序的,比如 select * from test where text = 't' order by text desc limit 10

你也可以发现,无论是 type 还是 Extra,他们都是从前往后性能越来越差的,所以我们在优化 SQL 的时候,要尽量往前面的优化。好了到这里我们就简单介绍了完了关键词了,但是到我们可以分析 not in 是否命中索引还差点内容。我们需要了解一下 MySQL 的索引原理。下面是一个 B+ Tree 的索引图,也是 MySQL 索引的原理。

MySQL 每一个索引都会构建一棵树,我们也要做能做心中有“树”。那么我心中的两棵树是这个样子。

为了快速讲述本文重点,图片适当的忽略的一些 B+ 树的细节。

  1. 第一棵树是主键索引,每一个 Page 就是 B+树中最重要的概念——页,这里我们也叫它节点。非叶子节点不存储数据,只存储指向子节点的指针,叶子节点存储主键和其他所有列值。其中每个节点通过双向指针链接左右节点组成了双向链表,页内部每个块可以理解为一条记录,页内多条记录通过单向指针链接,组成单链表,所有的页和页内的记录都是根据主键从左到右递增的。
  2. 第二棵树是二级索引,非叶子节点不存储数据,只存储指向子节点的指针,叶子节点存储二级索引和主键,所有的页和页内的记录都是根据二级索引从左到右递增的,这些是和主键索引最大的不同,其余的一样。

那么我们开始分析一下索引的查询原理

select * from test where second_key = 40;
复制代码

这条语句的查询流程是:

  1. 因为 second_key 有索引,所以走的是 idx_second_key 二级索引生成的树。
  2. 通过检查 Page 1 发现我们需要查询的记录在 Page 12 所属的叶子节点内。
  3. 通过查询 Page 12 发现我们需要查询的记录在 Page 27 节点内。
  4. 从 Page 27 的节点内从左向右遍历,得到 40 节点
  5. 获取到 40 节点里面存储的主键 ID 4
  6. 因为二级索引里面没有数据,所以需要回表,回表的时候重新通过 ID 4 查找 primary_key 主键索引树。
  7. 依照刚才的顺序,最终找到内容在 Page 27 里面的节点,返回。

同时我们运行一下 explain 验证一下,type 是 ref,走的是非唯一索引的等值匹配。

explain select * from test where second_key = 40 \G;
复制代码
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ref
possible_keys: idx_second_key
          key: idx_second_key
      key_len: 5
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)
复制代码

上面是一个非常简单的查询,那么我们看一下稍微复杂的。

select * from test where second_key > 10 and second_key < 50;
复制代码

这条语句的查询流程是:

  1. 因为 second_key 有索引,所以走的是 idx_second_key 二级索引生成的树。
  2. 因为索引是从左到右递增的,所以我们先找 second_key > 10,通过前面的讲解,我们会定位到 Page 23 的第 2 个节点。
  3. 因为叶子节点是双向链表,所以我们不需要重新从根节点找其他内容,我们直接从左向右遍历比较,直到内容 >= 50 停止,这样我们会定位到 Page 16 的第 1 个节点停止。
  4. 那么我们拿到的结果就是 Page 23 和 Page 27 的 20,30,40 节点。
  5. 然后回表,分别找到 20,30,40 对应的主键 2,3,4 的内容,返回数据。

我们继续运行一下 explain,type 是 range 表示使用索引的范围查询, Extra 里面有了内容。Using index condition 表示 range 查询的时候使用了索引进行比较以后才进行的回表。

explain select * from test where second_key > 10 and second_key < 50 \G;
复制代码
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: range
possible_keys: idx_second_key
          key: idx_second_key
      key_len: 5
          ref: NULL
         rows: 3
     filtered: 100.00
        Extra: Using index condition
1 row in set, 1 warning (0.00 sec)
复制代码

好的,那么进入了本文的高潮阶段,下面的语句怎么执行的你知道吗?

select * from test where second_key not in(10,30,50);
复制代码

凭着我们的手感,这次先运行 explain 吧,坏了,果不其然,type 是 ALL,全表扫描,小匠你又骗人?这不是没走索引吗?

           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: ALL
possible_keys: idx_second_key
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 8
     filtered: 75.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)
复制代码

好吧,尴尬了。再来,那我们换个语句试试吧。

select second_key from test where second_key not in(10,30,50);
复制代码

再运行一次试试,看能不能搬回来一局。It's Nice。这次就走索引了诶。

           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: range
possible_keys: idx_second_key
          key: idx_second_key
      key_len: 5
          ref: NULL
         rows: 6
     filtered: 100.00
        Extra: Using where; Using index
复制代码

那么为什么第一次没有走索引呢?好了不绕弯子了,我们解密吧。
MySQL 会在选择索引的时候进行优化,如果 MySQL 认为全表扫描比走索引+回表效率高, 那么他会选择全表扫描。回到我们这个例子,全表扫描 rows 是 8,不需要回表;但是如果走索引的话,不仅仅需要扫描 6 次,还需要回表 6 次,那么 MySQL 认为反复的回表的性能消耗还不如直接全表扫描呢,所以 MySQL 默认的优化导致直接走的全表扫描。

那么我就是想 select * 还走索引怎么办呢? 好的,安排

select * from test where second_key not in(10,30,50) limit 3;
复制代码
           id: 1
  select_type: SIMPLE
        table: test
   partitions: NULL
         type: range
possible_keys: idx_second_key
          key: idx_second_key
      key_len: 5
          ref: NULL
         rows: 6
     filtered: 100.00
        Extra: Using index condition
复制代码

如释重负啊,这次不就是走索引了吗?因为 limit 的增加,让 MySQL 优化的时候发现,索引 + 回表的性能更高一些。所以 not in 只要使用合理,一定会是走索引的,并且真实环境中,我们的记录很多的,MySQL一般不会评估出 ALL 性能更高。

那么最后还是说一下 not in 走索引的原理吧,这样你就可以更放心大胆的用 not in 了?再次回到我们这个索引图。

select * from test where second_key not in(10,30,50) limit 3;
复制代码

这个语句在真正执行的时候其实被拆解了

select * from test where 
(second_key < 10) 
or 
(second_key > 10 and second_key < 30) 
or 
(second_key > 30 and second_key < 50) 
or 
(second_key > 50);
复制代码

Ya hemos hablado sobre cómo usar el índice en el caso de > y <, entonces, ¿analizará usted mismo la declaración desensamblada? Una vez que se completa la descomposición de esta declaración, es equivalente a 4 intervalos abiertos, respectivamente, para encontrar el nodo de inicio una vez y luego buscar de acuerdo con el índice, de modo que cuando alguien le diga que not inno use el índice, ¿sabe qué hacer? ¿decir?

Este artículo es una serie de artículos en "Por qué 100,000 programadores" que planeé antes . Si tiene más preguntas, puede dejarme un mensaje, o puede consultar mi número de suscripción "Notas del codificador" para ver la serie de artículos.

Referirse a

Consulte el folleto de Nuggets "Understanding MySQL from the Roots"

Supongo que te gusta

Origin juejin.im/post/7102968550540705799
Recomendado
Clasificación