一个 Mysql 索引问题

2019-09-19 17:55:49 +08:00
 dackh

我们都知道 mysql 二级索引的存的是主键值,所以我们一般通过二级索引查询数据时,需要通过主键值再去查一次。

但是现在有一个问题是:例如 sql 是这样的

SELECT id,name,grade from student where grade = 1;

这个 SQL,grade 是加了索引的

但是根据 grade 索引查询的主键,再去通过聚集索引查的时候是如何查的?

1、查询出所有的主键值,再一次性查出 2、一个主键就去查一次,如果是这种那就需要查询很多次

2885 次点击
所在节点    程序员
28 条回复
yumenawei
2019-09-19 18:00:06 +08:00
帮顶
sujin190
2019-09-19 18:07:39 +08:00
感觉理解是不是有偏差,这里应该不是查出所有主键通过主键再去查询数据,正确的意思应该是索引不包含实际数据,只有主键信息,通过主键才能在真实的数据文件中读取出数据,这么设计出于提高性能目的,比如改表结构的时候,数据文件修改了,但是主键不变,此时无需重建索引,其他的比如可变类型修改超出原是长度需要挪动数据位置的时候也无需重建索引
arrow8899
2019-09-19 18:14:20 +08:00
查出所有的 id,再去回表查询 name 的值,由于 id 唯一索引查询时间是 const,所以批量查和单独查时间差不多;
如果你不需要 name 的值得话,就不需要回表了,grade 索引已经包含了 grade 和 id 的值,直接就可以返回。
masironen
2019-09-19 18:16:08 +08:00
我觉得应该是查询出所有的主键值,然后再一次性根据主键去查需要的值,因为如果查到一个主键就去查相应的数据的话,io 次数是要多很多的,在查询速度上快不了,反而可能要比全表查询慢。

以上是我个人的理解,不一定对。
dackh
2019-09-19 20:03:54 +08:00
@arrow8899
我实际的 sql 是这样的
```
select id from xxx.xxx where type = 12 and status in (11,12,13) limit 300000,500;
```
```
select id,user_idfrom xxx.xxx where type = 12 and status in (11,12,13) limit 300000,500;
```

这个表数据量 7000w+,第一条 sql 查询可能 100ms+,第二条 sql 得将近 1min

我想这是为什么
mccreefei
2019-09-19 20:18:16 +08:00
@dackh #5 我想知道你实际 sql 中二级索引是怎么建的
NizumaEiji
2019-09-19 20:19:44 +08:00
覆盖索引?
dackh
2019-09-19 20:41:36 +08:00
@NizumaEiji 第一条因为 id 是 primary key 不需要回表,所以很快,第二条 user_id,需要多回表查一次,但是回表一次为什么这么慢
dackh
2019-09-19 20:42:33 +08:00
@mccreefei type 跟 status 都有单列索引,问题的关键在雨多查一个 user_id 为什么回表查一次慢这么多
mccreefei
2019-09-19 20:53:24 +08:00
@dackh #9 type 和 status 两张二级索引表根据索引值查询很快,是因为要查询的索引值是连续的,查询到的数据也是分布在连续的页上(连续的页之间有指针可快速访问)。但是得到的主键值却不连续,基于你的数据量有 7000w 之多,那么回表查询的数据页跨度也会很大,因此两者查询速度相差大。
NizumaEiji
2019-09-19 20:56:04 +08:00
@dackh #8
你分别 explain 一下看看吧
aliipay
2019-09-19 20:57:59 +08:00
没看太明白,我猜问题原因是 offset 太大
dackh
2019-09-19 20:58:04 +08:00
@mccreefei 按这么说,主键也是有索引的,那根据主键索引回表一次这么慢?我加了一条附言,你看下
dackh
2019-09-19 20:59:52 +08:00
@NizumaEiji explain 出来的结果一模一样的
dackh
2019-09-19 21:02:45 +08:00
@mccreefei 或者你的意思是回表查询的时候不会走主键索引
DonaldY
2019-09-19 21:24:17 +08:00
`select * from T where k between 3 and 5`

在 k 索引表下,3 5 是连续的,可能在同一个分页。

但对应到主键索引表,可能就不在同一个分页,要多次查找分页
woyixinyiyi
2019-09-19 22:43:57 +08:00
@dackh 楼主 贴出你的 explain 里面的信息

第一条 sql 查询可能 100ms+,这个是因为二级索引会保护聚簇索引的值,这个时候是不需要回表的。
第二条 sql 得将近 1min,这个是因为查询的字段的值 user_id,不仅仅只有聚簇索引,包括其他的字段,同时这个字段也不是覆盖索引的字段,所以这个过程肯定是需要根据聚簇索引回表来查询具体的行记录的。

mysql 毕竟只是一个程序,没那么智能,只能尽可能覆盖各种场景遇到的一些问题。

比如你遇到的问题
根据二级索引查询可以获取一个聚簇索引集合(主键集合一般为 id ),
楼主可以用关键字 搜索下 read_rnd_buffer 和 MRR。
如果开启 MRR,大概思路是,根据你二级索引获取到的聚簇索引进行排序,针对排完的聚簇索引去回表查询,
因为聚簇索引是在数据页中是有序的,这样让读取数据是顺序读,理论上是可以减少磁盘数据页的访问。
akira
2019-09-20 00:02:27 +08:00
那这样写呢 ,这样就能保证他是一次过去查了吧

select id , user_id from xxxx
where
id in (select id from xxx.xxx where type = 12 and status in (11,12,13) limit 300000,500)
CRVV
2019-09-20 00:21:19 +08:00
select * from T where k between 3 and 5
用 k 的索引找到 id
再用 id 来查整条记录

我觉得这么查没有问题,请问不蠢的查询方式是什么?
aliipay
2019-09-20 00:31:24 +08:00
@akira subquery 不支持 limit,可以改成 select id, user_id from xx join ( select id from xx limit 300000,50) yy on xx.id = yy.id

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/602261

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX