MySQL 常见问题总结(1)

索引相关

A,B,C的联合索引,按照AB,AC,BC查询,能走索引吗?

AB 和 AC会走索引,但是AB性能好于AC,而BC就走不了索引

这个其实和最左匹配原因有关(什么是最左前缀匹配?为什么要遵守?

就是不能跳过前面的字段

此外就是AB和BA的顺序是一样的,where会优化(where条件的顺序影响使用索引吗?

char和varchar的区别?

char 定长,无论你存储多长,都占用你定义的长度,不足会补空格,然后取出的时候会去除空格

优点:速度快,不会产生页分裂和碎片

缺点:浪费空间,只能存储不可变长度

末尾空格会丢失,比如你存储 'a ' 和 'a' ,取出的效果是一样的

varchar 变长,存多少就占用多少,不过会额外占用一些空间存储一些信息

优点:省空间,适合不固定内容,不丢失空格

缺点:会造成页分裂和产生存储碎片

为什么varchar会导致页分裂?

InnoDB 是吧书记按照页(16KB)进行紧凑存储,如果以使用varchar,一开始存储的内容是"abc" 现在变成"abcde",那多的这部分塞不下,只能另外存储,然后通过指向的方式

结果:产生碎片,并且查询变慢

而char因为是长度固定的,不会有这个问题

什么是存储碎片?

也就是磁盘上出现很多零散,不连续的空间

varchar因为需要经常的update,所以内容会一会长一会短的,那就会造成存储的时候数据东一块西一块的

而char因为都是定长的,从你存储开始就不会出现这个问题

空格问题:

char 会丢失末尾空格,varchar 不会。

复制代码
char(10) 存 'aaa ' → 自动补满 → 取出时空格被删掉
char(10) 存 'aaa'  → 结果和上面一模一样

CHAR 和 VARCHAR 存储相同内容,取出对比结果一样还是不一样

一样的。虽然你char 存储 abc + ' ' 取出来变成了abc,但是因为MySQL字符串比对的时候会去掉尾部的空格,所以只要是使用 = 进行比较,结果是两个字符串相同

除非你是用严格二进制对比

复制代码
SELECT BINARY c_char = BINARY c_varchar AS is_strict_equal;

InnoDB为什么使用B+树实现索引?

B+树具有的特点:

  • 平衡树,不会出现变成链表的情况
  • 所有数据存储在叶子节点,遍历的时候只需遍历叶子节点就行,并且因为非叶子节点不存储数据,所以树能做的更胖更矮
  • B+树的叶子节点使用双向链表维护,可以快速的进行范围查询和倒序查询

B+树的优点:

  • 支持范围查询
  • 支持排序,并且节点本身是有序的,排序效率高
  • 非叶子节点可以存储更多的索引,使树的高度降低,减少IO次数
  • 因为B+树的叶子节点大小固定,所以有利于磁盘预读文件

为什么不使用红黑树或是B树?为什么不用Hash索引?

红黑树太高了,IO次数太多,不适合磁盘读取场景

B树非叶子节点也存储数据,导致树还是太高,并且对于范围查询场景比较麻烦

不用hash索引是因为hash索引只适合等值查询,不支持范围查询,排序以及模糊查询

InnoDB中的索引类型?

按照数据结构划分:

  • B+树索引: 默认、最常用,支持排序、范围查询
  • Hash索引: 仅在 InnoDB 自适应哈希索引中出现,只适合等值查询

按聚集方式 划分(最重要):

  • 聚簇索引(主键索引)
    • 按主键构建 B+ 树
    • 叶子节点存整行数据
    • 一张表只有一个聚簇索引
  • 非聚簇索引(二级索引)
    • 普通索引、唯一索引都属于这类
    • 叶子节点只存索引值 + 主键
    • 查询需要回表

按唯一性约束分

  • 主键索引:非空、唯一,一张表一个,是聚簇索引
  • 唯一索引:值唯一,但允许 NULL,可建多个
  • 普通索引:无约束,仅加速查询

limit0,100和limit10000000,100一样吗?

不一样,虽然查询的数据数量是一样的,但是后者是深分页

假设你 limit m,n 它会查询出 m+n条数据,然后丢掉前m条,再返回n条

那么也就是你的m越大,需要扫描的数据就越多,性能就越差

MySQL针对Limit做了一些优化:

  1. 当你limit少量的数据时,MySQl会更加倾向于走索引;如果limit 大量数据,就会倾向于全表扫描,因为可能会有大量的回表操作。所以如果我们知道要具体多少条并且数量比较少的话,可以通过limit 指定
  2. order by + limit:找到足够行数就立刻停止排序,不用全排
    1. 正常order by需要选对所有数据进行排序,然后返回;那如果我们能明确知道只需要排序完的前几条,就可以使用limit指定,不会全部排序,而是排序到指定条数就直接返回(当然建立在order by 的字段有索引,也就是原本就有序,如果没有索引就是另一种优化(第五点)
    2. 例如:select * from t order by id limit 10;
  1. distinct + limit:找到唯一行数就停止
    1. 例如:select distinct name from t limit 5;
    2. 如果知道只需要多少条的情况下就可以使用,不用扫描全表
  1. limit 0 快速返回空集,用于测试 SQL
    1. 例如:select * from t where ... limit 0;
    2. 不查询任何数据,只是检查是否能正常访问
  1. 无序索引排序+limit:用内存 filesort
    1. 例如:select * from user order by name limit 10;
    2. name 没有索引,如果需要实现排序的话,需要使用磁盘的临时文件进行排序,但是效率很低,现在只需要在内存维护一个10个元素大小的堆,不断地把元素拿进来进行排序就行
    3. 需要注意:依然会对所有元素排序,但是无需通过磁盘临时文件进行排序,而是把元素读取到内存中进行排序,比较快。那为什么加了limit就可以呢,因为我们现在需要的条数是确定且比较少的,mysql发现内存可以放得下就会使用内存堆排序,否则你可以需要的是排序完的全部,内存可能放不下,就会使用临时文件排序(不是减少排序范围,而是改变排序位置

limit + order by 的坑

复制代码
SELECT * FROM ratings ORDER BY category LIMIT 5;

正常执行上面语句,当出现多个category相同的时候,mysql会随机选择其中的几条(每次排序结果不同),但是可能会出现的就是每次选出来的结果不一样,也就是每次执行结果不同

最好的方式就是:加上一个具有唯一性的字段,比如id,这样每次排序结果都一样

复制代码
SELECT * FROM ratings ORDER BY category,id LIMIT 5;

MyISAM的索引结构是怎么样的,它存在的问题是什么?

它采取的是数据和索引分开存储,也就是只有非聚簇索引,全部都是索引文件,真正的数据文件存储在另外的地方,通过索引指向真正的数据文件(索引文件不存数据,而是存储数据的物理地址,和InnoDB的数据即索引不同)

也就是说,MyISAM查询必定会造成回表,就算是基于主键查询也是两次IO,后来的InnoDB就解决了这个问题

MySQL的深度分页如何优化

先看一下MySQL深度分页为什么慢:

复制代码
select * from t limit 1000000,10

就是因为MySQL 必须从第 1 条开始扫描,读到 **1000010 条,扔掉前 1000000 条,**返回最后 10 条

优化的核心就是让MySQL不要读取那么多无用的数据

深分页优化手段:

  1. 延迟关联(JOIN子查询)【最通用,最常用】

    SELECT *
    FROM t
    INNER JOIN (
    SELECT id FROM t
    ORDER BY id
    LIMIT 1000000,10
    ) AS tmp ON t.id = tmp.id

先子查询出LIMIT 1000000,10的id,因为这个过程只查询了id,数据量很少,所以是很快的

然后用查询出来的id进行回表查询完整数据

这个过程虽然会丢失很多数据,但是因为我们只查询了id,数据量本身很小,可以忽略

注意:加上ORDER BY id 是为了保证每次的查询顺序一致,一般使用LIMIT都会加上这个

  1. 主键上拉(ID范围查询)

    SELECT * FROM t
    WHERE id >= (
    SELECT id FROM t
    ORDER BY id
    LIMIT 1000000,1
    )
    ORDER BY id
    LIMIT 10

先查询出LIMIT 1000000,1,也就是目标记录的第一条的ID,把这个作为查询条件,往后查询10条

缺点:主键ID必需是连续递增的

  1. 游标方案(记录上一页ID)

    SELECT * FROM t
    WHERE id > 上一页最后一条ID
    ORDER BY id
    LIMIT 10

这种在一些下拉场景很常见,比如你网盘传输文件记录就可以看到,需要前端单独记录第一条记录的ID

这种是最快的,但是使用场景很局限

缺点:只能上下页一页一页的翻,不能跳页

MySQL的行级锁锁的到底是什么?

行级锁一共有三种:

  • Record Lock表示记录锁,锁的是索引记录。
  • Gap Lock是间隙锁,锁的是索引记录之间的间隙
  • Next-Key Lock是Record Lock和Gap Lock的组合,同时锁索引记录和间隙 。他的范围是左开右闭的。

Record Lock:记录锁

复制代码
SELECT c1 FROM t WHERE c1 = 10 For UPDATE

当执行上面这一条语句的时候会对c1=10这条记录加记录锁

加锁期间,其它事务的插入、更新和删除都无法执行

Gap Lock:间隙锁

间隙就是记录之间的间隙,这些间隙就是还没有数据,但是可以有数据,相当于一个位置,但是还没使用

比如索引值:5 → 10 → 15那么间隙就是:(-∞,5)、(5,10)、(10,15)、(15,+∞)

好处就是防止别人在中间插入数据,可以解决幻读现象

特点就是只锁空隙,不锁记录,并且只有RR隔离级别下才有

无需我们手动执行,而是在特定条件下自动触发

复制代码
已有数据 5 10 15
select * from t where id = 7 for update; 发现记录7不存在,找到7的前后边界(5,10) 将这个间隙锁住
select * from t where id > 10 for update; 这样执行会把10往后都锁住,也就是说连同15也一起锁住,间接实现把间隙锁住

Next-Key Lock 临建锁

左开右闭,同样是RR隔离级别才生效

比如索引 5、10、15next-key 锁就是:(-∞,5]、(5,10]、(10,15]、(15,+∞]

InnoDB 行锁锁的是索引,而不是单独的数据行

复制代码
select * from t where name = 'tom' for update;
  • 也就是如果你锁的时候,这个记录有索引,那么就是把这条记录锁起来,相当于锁数据行
  • 如果没有索引,就需要遍历全表,造成的结果就是直接锁表

这里的索引有无就是说这个name的索引有无

MySQL 的加锁方式

首先默认情况下就是直接加Next-Key Lock,然后再根据具体的情况进行退化成间隙锁或是记录锁

然后只有扫描到的元素才会加锁,没扫描到的并不会加锁

退化的流程:

  • 唯一索引+等值查询:退化成记录锁,只锁一行数据
  • 等值查询向右扫描,最后一条不满足:next-key 退化 → 变成间隙锁

具体例子:

现在有数据 5 10 15

复制代码
select * from t where id = 7 for update

首先就是直接锁住(5,10]

然后开始进行优化,首先这个是等值查询,而10不符合条件,退化成间隙锁 (5,10)

复制代码
select * from t where id>=10 and id<11 for update

先对(5,10]加锁,继续向后走,遍历到15,发现不符合,退化成(10,15] 以及记录锁10

MySQL的优化器的索引成本是怎么算出来的?

一条 SQL 的成本主要就是包含了 CPU 的成本和 IO的成本两部分

那我们想要知道执行一条SQL的真正成本,可以通过 EXPLAIN FORMAT=JSON SQL

复制代码
EXPLAIN FORMAT=JSON select * from tb_blog;

执行的结果就是:

复制代码
{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "0.65"
    },
    "table": {
      "table_name": "tb_blog",
      "access_type": "ALL",
      "rows_examined_per_scan": 4,
      "rows_produced_per_join": 4,
      "filtered": "100.00",
      "cost_info": {
        "read_cost": "0.25",         // IO成本
        "eval_cost": "0.40",				 // CPU成本 
        "prefix_cost": "0.65",       // SQL总成本
        "data_read_per_join": "68K"  // 总的读取记录的字节数
      },
      "used_columns": [
        "id",
        "shop_id",
        "user_id",
        "title",
        "images",
        "content",
        "liked",
        "comments",
        "create_time",
        "update_time"
      ]
    }
  }
}

read_cost 高 → 回表多、磁盘 IO 多

eval_cost 高 → 计算、过滤、临时表多

总成本高 → MySQL 就不会选这个索引

MySQL的优化器是通过估算的方式,不会真正的去跑一遍

MySQL的主键一定是自增的吗?

不是,这个是自己选的

但是一般建议使用自增字段,有以下好处:

  • 插入更高效,每次从索引尾部加上,避免随机插入, 大幅减少页分裂和磁盘 IO。
  • 索引更小,碎片少,查询快
  • 保证唯一性,避免出现重复的情况

然后一般只需要主键自增就行,但是不需要一定是连续自增,一般都是用一些雪花算法之类的去处理

如果没有定义主键,MySQL的选择规则是这样的:

  • 有无唯一非空索引,有的话判定为主键索引
  • 如果没有,就使用隐藏的主键(row_id)作为主键索引

MySQL索引一定遵循最左前缀匹配吗?

这个在MySQL8.0.13以后就不一定,因为引入了跳跃扫描,也就是即使你没遵循最左匹配原则的情况下,也是有可能可以使用索引的

具体的跳跃扫描就是它帮我们拆分成多条sql来执行,再将执行完的结果合并给我们

但是有限制,不能对结果进行去重或是排序操作,并且只适合区分度低的字段,区分度高的不一定生效

MySQL为什么会有存储碎片?有什么危害?

碎片的主要来源就是 insert、update以及 delete,此外如果我们使用 varchar 或者 text 这种可变长度字段存储的时候,更新时如果长度发生变化,也会存在碎片。

delete:

只是标记数据,并不会真正的删除数据,这些空间不会立马还给操作系统,而是下次有insert操作的时候可以往这些空间插入

update:

如果是对text和varChar这种变长字段进行修改的话,可能会出现原位置放不下,行被迁移,留下空隙

insert:

当主键不是有序自增的话会出现页分裂,造成数据物理不连续,形成碎片

碎片过多造成的危害:

  • 物理不连续,导致更多的随机IO,那么查询就会变慢
  • 空间浪费,这些小碎片无法利用起来,造成浪费
  • 索引效率低,页空洞变多,相同数据需要更多页

避免/优化方法:

  1. 使用自增主键,减少页分裂
  2. 合理使用varChar/char
  3. 减少不必要的索引,降低页分裂频率
  4. 定期使用OPTIMIZE table 表名,对指定的表进行瘦身操作
  5. 避免大量频繁删除,多用逻辑删除

MySQL用了函数一定会索引失效吗?

在旧的版本确实,但是在新的版本不一定

旧的版本因为如果你对列使用了函数,那么值就变了,无法匹配索引顺序

但是在8.0之后,引入了单独对函数建索引的方式,这样即使你对字段使用了函数也同样有单独的索引可以对应

复制代码
CREATE INDEX idx_name ON t ((函数(列)));

on和where有什么区别?

on 和 where都是用于指定条件,但是他们在JOIN操作的作用是不同的

JOIN 就是用来做表连接的,会保留你 on 或是 where 指定保留的条件的数据

ON 是【连接时用的条件】,不丢左表数据

WHERE 是【连接后过滤】,不符合直接删掉整行

ON

复制代码
SELECT *
FROM 员工
LEFT JOIN 部门
ON 员工.dept_id = 部门.id
AND 部门.name = 'IT';  -- 写在 ON 里

结果:所有员工都会保留,但是只有部门为IT的员工的部门名字字段才会展示,否则为null

where

复制代码
SELECT *
FROM 员工
LEFT JOIN 部门
ON 员工.dept_id = 部门.id
WHERE 部门.name = 'IT';  -- 写在 WHERE 里

只留下 IT 部门的员工

不是 IT 部门的 → 整行直接删掉

LEFT JOIN 直接变成 INNER JOIN!

之所以会这样的原因是:

ON的时机:是在连接的时候,只能决定右边怎么匹配,不能决定左边的数据要不要留,所以会导致左边的数据全都留下来

where的时机:连接完成的时候,这个时候就会直接留下左右连接之后的数据中符合条件的那些,不符合的直接删除掉

总结:

  • LEFT JOIN 想保留主表全部 → 条件放 ON
  • LEFT JOIN 想过滤最终数据 → 条件放 WHERE
  • INNER JOIN 里 ON 和 WHERE 效果一样

order by是怎么实现的?

order by 就是用来排序的,具体怎么排序其实是取决于优化器,可能会走索引,但是大部分情况下其实是走的 filesort

然后filesort其实也分两种:数据量少的情况下走的内存排序,多的情况下走的临时文件排序

复制代码
 {
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "3.55"
    },
    "ordering_operation": {
      "using_filesort": true,
      "cost_info": {
        "sort_cost": "3.00"
      },
      "table": {
        "table_name": "employees",
        "access_type": "ALL",
        "rows_examined_per_scan": 3,
        "rows_produced_per_join": 3,
        "filtered": "100.00",
        "cost_info": {
          "read_cost": "0.25",
          "eval_cost": "0.30",
          "prefix_cost": "0.55",
          "data_read_per_join": "288"
        },
        "used_columns": [
          "id",
          "name",
          "department_id"
        ]
      }
    }
  }
}

索引排序:

如果order by能基于索引排序,那效率肯定是最高的,索引原本就是有序的

什么情况可以走索引?

  • order by 字段 有索引
  • 联合索引满足最左前缀
  • 查询是覆盖索引(不需要回表)
  • where 锁定前导列,order by 后面列 例:where a=xx order by b(a,b 是联合索引)
  • 有 limit 小数量(优化器倾向走索引)

filesort:

MySQL 自己排序

根据数据量不同选择不同的排序位置:

  • 数据量 < sort_buffer_size → 内存排序(快)
  • 数据量 > sort_buffer_size → 磁盘临时文件 + 归并排序(很慢)

filesort有两种排序方式

全字段排序:

  • 要查询的所有字段放进 sort_buffer
  • 排序后直接返回
  • 只回表一次
  • 优点:快
  • 缺点:字段多 → 占空间大

rowid 排序:

  • 只放排序字段 + 主键 id 进 sort_buffer
  • 排序完再回表查所有字段
  • 回表两次
  • 优点:省内存
  • 缺点:慢

总结:

  • order by 能走索引就最快
  • 不走索引就会 filesort(内存 / 磁盘排序)
  • 联合索引、最左前缀、覆盖索引、where 锁定前导列 → 容易走索引排序
  • select字段过多、数据量大、无索引 → 走 filesort
  • 排序算法:优先全字段排序,字段太长才用rowid 排序

从innodb的索引结构分析,为什么索引的key长度不能太长

InnoDB 是B+树,每个节点都是固定的16KB的数据页

索引的key越长,占用的位置越多,那你一页能存储的数据就越少,那么造成相同的记录数,你的树层数越高,磁盘IO越多,查询越慢

一般需要做平衡,key太短,区分度不高,太长,影响性能

二级索引在索引覆盖时如何使用MVCC?

首先MVCC是绑定聚簇索引的,如果二级索引能做到索引覆盖,那就不会回表,也就是不会查询聚簇索引,是不是也就不能使用MVCC?

MVCC 的所有信息(trx_id、roll_ptr、版本链)都只存在于聚簇索引的行记录里,二级索引本身不存这些东西,所以二级索引自己做不了完整 MVCC。

那既然二级索引自己做不了MVCC,那它是怎么实现MVCC的呢?

它通过一个字段 PAGE_MAX_TRX_ID ,每一个二级索引的头部信息都会记录一个 PAGE_MAX_TRX_ID

PAGE_MAX_TRX_ID = 修改过的这个页的最大事务Id

通过这个字段做一个快速判断当前的ReadView 是否需要回表查询,如果不确定或是需要就会强制回表,否则索引覆盖

联合索引是越多越好吗?

肯定不是的,虽然联合索引能做到索引覆盖,提高查询效率,但是会降低写入效率,每次写入都需要维护索引,那么联合索引太多可能会造成页分裂、页合并等操作

区分度不高的字段建索引一定没用吗?

这个不一定,理论上来说区分度不高确实不建议建索引

比如,男女比例1:1 这种情况下会走全表扫描,不走索引

但是如果数据分布很倾斜,比如比例达到 95:5 那么可以建立,这种情况查询少部分的数据会走左右,并且性能很高

总结就是:区分度不高≠不能建立索引,当数据分布倾斜的时候,查少的那部分数据,索引依然高效

设计索引的时候有哪些原则?

我自己的习惯是看字段查询频率高不高;区分度大不大(当然看区分度的时候需要考虑数据分布倾斜);对于一些经常组合查询的,可以考虑使用联合索引,可以提升效率,避免回表查询(索引覆盖就能避免回表);对于比较长字符串字段,我一般尽量避免建立索引,或者只对前100个字符建立索引

什么情况会导致自增主键不连续?

事务回滚:如果一个事务包含了对自增主键表的插入操作,但事务最终失败并回滚,已经分配的自增值不会被重用。这会导致自增主键序列中出现跳跃。

删除操作:从表中删除行会导致自增主键序列出现间隔。因为当行被删除后,其对应的自增主键值不会被重用。

手动指定自增值:如果在插入数据时手动指定了自增列的值,MySQL将使用该值而非自动生成的下一个自增值。如果指定的值大于当前的自增值,MySQL将更新自增计数器为该值,导致后续自动生成的自增值跳跃。

服务器重启:对于某些数据库引擎(尤其是早期版本的MySQL和InnoDB引擎),自增计数器可能不会持久化到磁盘。如果数据库服务器重启,自增计数器可能会重置为当前最大自增值加一,但未提交的事务占用的自增值会丢失,导致不连续。

更改自增值设置:通过修改表的自增起始值或者直接修改自增计数器的值,也可以导致自增主键不连续。

数据导入:在导入数据时,如果导入的数据中包含自增主键列,并且导入数据的主键值与现有数据不连续,也会导致自增主键不连续。

主键不连续完全不影响,只需要自增就行

什么时候索引失效反而提升效率?

  1. 当数据量很小的时候,这个时候走索引再回表,效率还不如直接全表扫描
  2. 要返回大部分数据,比如你要查询80%的数据,那你走不走索引不还是基本需要查询整个表嘛,所以不如全表扫描,还不需要回表的IO
  3. 区分度低的数据或是数据倾斜的数据,区分度低,你走索引的效果很微乎其微,假设这个时候数据倾斜还比较严重,那就是灾难
  4. MySQl 选错索引,需要我们手动失效:

MySQL 有时候会选错索引

比如:

复制代码
select * from 表
where state = 'INIT'  -- 这个条件能过滤掉 99% 的数据!
order by id
limit 100;

MySQL 看到这里有id主键索引和state索引,然后MySQL会发现,走id索引的话,无需回表并且有序,所以认为走主键索引更快,结果我们这里的数据INIT其实只有几条,而总数据有很多条

那是不是就说明MySQL 选错了

这种情况,我们如果能实现预知到,那么我们可以强制它走state,让这条sql的id索引失效

做法:

复制代码
select * from 表
where state = 'INIT'  -- 这个条件能过滤掉 99% 的数据!
order by id + 0
limit 100;

这样id变成函数运算,直接失效

什么是回表,怎么减少回表的次数?

回表发生在使用非聚簇索引查询的情况下

因为非聚簇索引只包含索引字段和主键Id,那假设要查询的字段中,有聚簇索引不包含的,那就必需回表查询完整数据,这个就是回表

减少回表次数,可以通过索引覆盖,索引下推等,或是直接通过主键索引来查询

什么是聚簇索引和非聚簇索引?

聚簇索引就是主键索引,它包含所有字段,找到这个索引就能拿到最全的数据

非聚簇索引就是普通索引,只包含索引字段和主键Id,如果用它做查询时,不能涵盖所有需要的字段,会触发回表

什么是索引覆盖、索引下推?

索引覆盖就是你的查询语句能从使用的索引中获取到需要的所有字段,无需回表查询额外字段,这个就是所有覆盖

索引下推就是把判断过程下推到索引中进行判断,避免大量回表

比如:现在的字段是 id, name, age, city

联合索引是 idx(name, age)

复制代码
select * from user
where name like '李%'  and  age = 20;

那我们都知道,从模糊的部分开始,后面的索引就无法生效了,也就是这个语句只能用到索引的name字段

如果没有索引下推的情况下,需要先匹配到姓李的所有记录的ID都找出来,一个个回表然后判断age是否等于20

那就会造成很多不符合的记录回表,浪费性能

有了索引下推,可以匹配到姓李的记录之后,因为这个记录本身也包含age字段,所以会顺便判断age=20,符合才返回ID,然后再进行回表

也就是精准回表,避免浪费大量性能

它的使用场景也比较有限,必需是你的所以本身包含了你要判断的字段才行,假设现在索引只有name,那就没办法索引下推

我个人认为,索引下推是用来解决联合索引部分失效的问题

什么是索引合并,原理是什么?

就是把多个单列索引一起使用,形成类似于联合索引的效果

比如现在表:user(id, name, age, city)

索引:idx(age) idx(city)

复制代码
select * from user
where age > 30 and city = '北京'

没有索引合并的时候,只能要么走idx(age) 要么走 idx(city)

但是有了索引合并,可以分别走idx(age) 和 idx(city)

然后再把各自的结果按照一定的规则进行选举出来

使用方式:

  1. 通过and,取出来的是交集

    where age>30 AND city='北京'

  2. 通过or,取出来的是并集

    where age>30 OR city='北京'

不过这个or根据不同情况,也分为普通并集和排序并集

排序并集就是两边只要有一边是范围查询,

复制代码
WHERE age > 30 OR city LIKE '北%'

因为两边各自找出来的Id都是无序的,需要各自排序,再进行合并去重,之所以要先排序再去重而不是直接使用Set就是为了更高效

本质上还是一样的,都是查询出来Id之后进行去重合并,只不过范围查询多了一步先排序

什么是索引跳跃扫描

索引跳跃扫描就是在MySQL8以后提供的一种绕开最多前缀限制的扫描方式

比如idx(f1, f2)

如果是旧版本,select * from t where f2 = 40; 会直接索引失效

但是有了索引跳跃扫描,它会帮我们拆分成

复制代码
where f1=1 and f2=40
union all
where f1=2 and f2=40

也就是先去查询 f1 都有哪些值,然后拆分成多条子查询,再把结果 union all 起来

但是也发现了一个问题,假设f1是区分度很高的(也就是数值很多),比如手机号等,如果硬要拆分的话,性能还不如直接全表扫描,MySQL优化器会判断,发现索引跳跃扫描优化之后性能没有提升,会果断放弃,直接走全表扫描

所以索引跳跃扫描适合那种数据区分度不明显的,比如状态(失效和有效),或是性别(男和女)

绝对不适合区分度高的(手机号/身份证号码)

但是这个的局限性很大,建立索引的时候最好还是把查询频率高的放前面

此外还有一些限制条件:

  • 只能单表查询,不能多表join
  • 不能用 group by /distinct
  • 查询的字段必需在索引里面

什么是最左前缀匹配?为什么要遵守?

最左匹配原则就是联合索引使用的时候必需从最左边第一个字段开始,如果跳过的话,从跳过的字段开始,后面的部分就失效

比如联合索引ABC,如果你从B开始,也就是跳过A,整个就会失效;如果你是AC,也就是跳过了B,那么从B开始失效

总结就是,从开始开始跳过,就从哪开始失效

此外就是where的查询条件顺序不影响结果,比如下面这两个的效果是一样的,where会优化

复制代码
where a=1 and b=2
where b=2 and a=1

最重要的是,联合索引不是创建三个单独的索引,而是只创建一个索引,但是这个索引包含多个字段

也就是创建一颗B+树:

  • 先按 a 排序
  • a 相同再按 b 排序
  • b 相同再按 c 排序

为什么要遵循最左前缀匹配?

因为索引是B+树,B+树的联合索引排序规则是,先排左边,再排右边

就像字典一样:

  • 先按照首字母
  • 首字母相同就按照第二个字母
  • 如果你直接就查询第二个字母,字典就失效了,你需要遍历一遍全部第二个字母,索引同理,一旦跳过就只能遍历全部

模糊匹配是否遵循最左前缀匹配?

同样需要遵循

复制代码
where name like '张%' → 走索引(最左匹配)
where name like '%三' → 不走索引
where name like '%三%' → 不走索引

就是你不能出现把%写在最左边,否则同样会索引失效

范围查询会失效吗?

复制代码
where a=1 and b>2 and c=3

范围查询会失效,也就是a走索引,b也可以走索引,但是c就不行

原因就是范围查询后面的字段只能全量扫描

不过会在前面已经匹配到的范围内进行扫描,也就是会在a=1且b>2的这部分数据中找c=3

索引失效的问题如何排查?

先看一下对应的sql的 explain ,如果type或是key为null说明是索引失效了

然后就检查一下sql是不是不构成走索引的条件,比如不匹配最左原则或是使用了前缀模糊匹配,还是使用函数计算等

还有一个比较坑的,是我之前乱点的时候偶然发现的,就是当你的字段虽然是数值类型,但是你的数据库却是以varchar形式进行存储,然后你进行比较的时候依然用数值,会触发类型转换,同样不会走索引

还有一些情况就是MySQL 可能错误认为走索引没有全表扫描快,同样索引失效,这种的话可以通过通过sql强行走索引

唯一索引和主键索引的区别?

首先唯一索引和主键索引都是具有唯一性的

此外就是对NULL的态度,主键索引不允许值为NULL的情况,唯一索引允许值为NULL,并且没有书来拿那个限制,因为MySQL认为NULL并不是一个具体的数值,而是表示未知

然后主键索引一张表只能有一个,而唯一索引没有数量限制

InnoDB 中,主键索引就是聚簇索引,而唯一索引大部分情况下是非聚簇索引

主键索引无需回表,唯一索引可能需要回表

主键索引可以被其他表引用为外键,唯一索引不行

唯一索引大部分情况下不会成为聚簇索引,只有在你没定义主键索引,并且你的唯一索引列限制了不准为NULL,这种情况下这个唯一索引会被选举为主键索引

为什么MySQL会选错索引,如何解决?

MySQL 选索引主要看四个东西

  • 扫描行数(越少越好)
  • 是否需要排序( order by 很影响选择 )
  • 是否回表(覆盖索引优先)
  • 索引区分度(过滤越狠越好)

选错的几个原因:

  1. 统计信息不准,以为索引A快,但是实际上B更快
  2. 被 order by 骗了,MySQL 特别讨厌 fileSort ,只要看到 order by id 就以为能走主键索引来避免排序,结果可能性能反而是比较差的(可能另一个索引能筛选掉90%的数据)
  3. 查询太复杂,这种情况不好算,会随机选取一个

解决方法:

  1. 强制指定使用索引

    SELECT * FROM t FORCE INDEX(idx_state) WHERE ...

  2. 建立更合理的联合索引,把where条件和order by 字段建成联合索引

  3. 如果是已经发现走了错误的索引,我们可以直接让它失效,比如 id + 0

用了索引还是很慢,可能是什么原因?

  1. 选错索引
  2. 数据分布不均匀,比如大部分数据都是同一个值,那么你用索引并不能很好的筛选出具体的数据
  3. sql本身是不是写的有问题,比如查询不合理之类的
  4. 数据库设计不合理,比如需要大量回表之类的
  5. 网络因素

怎么比较两个索引的好坏?

看执行计划

  • type:越好的索引,级别越高(const > ref > range > index > ALL)
  • rows:扫描行数越少越好
  • filtered:过滤比例越高越好
  • Extra:没有 Using filesortUsing temporary 最好

看实际运行时间,通过强制指定索引的方式,并测试最终执行时间,进行对比

也可以直接看数据区分度高不高,高的一般比较好

const :通过主键或是唯一索引精准查到1条数据

ref:通过普通索引等值匹配到一批数据

range :范围查询

index :遍历整个索引树(没用到索引)

ALL:全表扫描

index 和 all的区别,index 扫描索引,而all需要扫描数据行

执行计划中,key有值,还是很慢怎么办?

key 有值不代表走了索引

复制代码
+----+-------+---------------+----------+--------------------------+                                           
| id | type  | possible_keys | key      | Extra                    |                                           
+----+-------+---------------+----------+--------------------------+                                           
|  1 | index | NULL          | idx_abcd | Using where; Using index |                                           
+----+-------+---------------+----------+--------------------------+ 

比如上面这个,key有值,如果走了索引,Extra应该是Using index,而不是Using where; Using index

再看type,标识的是index,说明走了全索引扫描,并不是我们理解的走索引,这个的性能只比全表扫描快一点点

where条件的顺序影响使用索引吗?

不影响:

复制代码
SELECT * FROM my_table WHERE a = 'value' AND b = 'value2';
SELECT * FROM my_table WHERE b = 'value2' AND a = 'value';

比如上面这两个sql,经过查询优化器处理之后,两者是一样的,也就是会调整sql的顺序

所以在一些联合索引的场景,where的顺序不会导致结果受到影响

相关推荐
GEO索引未来2 小时前
为什么做GEO需要一套好的数据系统?
大数据·人工智能·ai·chatgpt·googlecloud
路由侠内网穿透2 小时前
本地部署开源发票管理系统 Invoice Ninja 并实现外部访问
运维·服务器·数据库·物联网·开源
m0_640309302 小时前
c++如何判断两个文件路径是否物理指向同一个磁盘文件_equivalent【详解】
jvm·数据库·python
CRMEB系统商城2 小时前
国内开源电商系统的格局与演变——一个务实的技术视角
java·大数据·开发语言·小程序·开源·php
AI周红伟2 小时前
《智能体应用交付实操:OpenClaw+Skills+RAG+Agent智能体应用案例实操和智能体交付的方案设计》
大数据·数据库·人工智能·科技·gpt·深度学习·openclaw
yaaakaaang2 小时前
十八、中介者模式
java·中介者模式
爱研究的小梁2 小时前
浅析适配应急与关键场景的三类聚合终端
大数据·网络
一 乐2 小时前
饮食营养信息|基于springboot + vue饮食营养管理信息平台系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·饮食营养管理信息系统
Shorasul2 小时前
Django 信号中为 ImageField 指定自定义保存路径的正确实践
jvm·数据库·python