转载自:你真的会使用数据库的索引吗?
使用索引也很简单,然而, 会使用索引是一回事, 而深入理解索引原理又能恰到好处使用索引又是另一回事。
一、前言
无论是面试、还是日常工作中,或多或少都会使用或者听到别人谈论索引这个技术。
然而很大一部份程序员对索引的了解仅限于到“加索引能使查询变快”这个概念为止。
使用索引也很简单,然而, 会使用索引是一回事, 而深入理解索引原理又能恰到好处使用索引又是另一回事。
这已经是两个相差甚远的技术层级了。
二、千万级数据表索引和无索引查询效率对比
现在有一个学生表 student
,有 1000 万条数据
未加索引,查询 class_id=2
的学生信息的耗时:SELECT \* FROM student WHERE class_id=2
花费了 3.357 秒
加上索引,查询 class_id=2
的学生信息的耗时:SELECT \* FROM student WHERE class_id=2
花费了 0.017 秒
1000 万条数据下,两个查询的性能差了近 200 倍!!
这个差距是特别大的! 难怪需要加索引!!!
三、什么是索引
网上很多讲解索引的文章对索引的描述是这样的:
索引就像书的目录, 通过书的目录就可以准确的定位到书籍的具体的内容。
这句话概述的非常正确!
但说了跟没说一样,懂的人自然懂!不懂的人感觉懂了,但还是一脸蒙的状态!
其实想要理解索引原理,必须清楚一种数据结构:
「平衡树」(非二叉),也就是 B Tree
或者 B+Tree
当然, 有的数据库也使用哈希桶作用索引的数据结构 , 然而, 主流的 RDBMS 都是把平衡树当做数据表默认的索引数据结构的。
我们平时建表的时候都会为表加上主键, 在某些关系数据库中, 如果建表时不指定主键,数据库会拒绝建表的语句执行。
事实上, 一个加了主键的表,并不能被称之为“表”。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐。
如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的“平衡树”结构,换句话说,就是整个表就变成了一个索引。
没错, 再说一遍, 整个表变成了一个索引!
也就是所谓的“聚集索引”。 这就是为什么一个表只能有一个主键, 一个表只能有一个“聚集索引”,因为主键的作用就是把“表”的数据格式转换成“树(索引)”的格式。
未加索引时,之前执行的查询 SQL 会让数据库系统逐行的遍历整张表,对于每一行都要检查其 class_id
字段是否等于 2
。因为我们要查找所有 class_id
为 2
的员工,所以当我们发现了一条 class_id
是 2
的记录后,并不能停止继续查找,因为可能还有 class_id
等于 2
的其他记录。
这就意味着,对于表中的千万条记录,数据库每一条都要检查。这就是所谓的“全表扫描”(full table scan)
而加上索引的最大作用就是加快查询速度,它能从根本上减少需要扫表的记录/行的数量。
四、Mysql 中的索引
在 MySQL 中, 索引有两种分类方式:逻辑分类和物理分类。
按照逻辑分类,索引可分为:
-
主键索引:一张表只能有一个主键索引,不允许重复、不允许为 NULL;
-
唯一索引:数据列不允许重复,允许为 NULL 值,一张表可有多个唯一索引,但是一个唯一索引只能包含一列,比如身份证号码、卡号等都可以作为唯一索引;
-
普通索引:一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许数据重复,允许 NULL 值插入;
-
全文索引:让搜索关键词更高效的一种索引。
按照物理分类,索引可分为:
-
聚集索引:一般是表中的主键索引,如果表中没有显示指定主键,则会选择表中的第一个不允许为 NULL 的唯一索引,如果还是没有的话,就采用 Innodb 存储引擎为每行数据内置的 6 字节 ROWID 作为聚集索引。每张表只有一个聚集索引,因为聚集索引的键值的逻辑顺序决定了表中相应行的物理顺序。聚集索引在精确查找和范围查找方面有良好的性能表现(相比于普通索引和全表扫描),聚集索引就显得弥足珍贵,聚集索引选择还是要慎重的(一般不会让没有语义的自增 id 充当聚集索引);
-
非聚集索引:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同(非主键的那一列),一个表中可以拥有多个非聚集索引。
在目前用的最多的 mysql 的 InnoDB 存储引擎中,是使用 B+Tree 索引方法来进行索引建立的。
B+ 树索引是 B+ 树在数据库中的一种实现,是最常见也是数据库中使用最为频繁的一种索引。
B+ 树中的 B 代表平衡(balance),而不是二叉(binary),因为 B+ 树是从最早的平衡二叉树演化而来的。先了解二叉查找树、平衡二叉树(AVLTree)和平衡多路查找树(B-Tree),B+ 树即由这些树逐步优化而来。
具体的讲解可参考文章:MySQL 索引机制(B+Tree)。
五、索引的优缺点
优点:
- 索引能够提高数据检索的效率,降低数据库的 IO 成本。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性,创建唯一索引
- 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间
- 加速两个表之间的连接,一般是在外键上创建索引
缺点:
- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加
- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大
- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度
六、索引何时应该使用
需创建索引的情况:
- 主键,自动建立唯一索引
- 频繁作为查询的条件的字段
- 查询中与其他表关联的字段存在外键关系
- 查询中排序的字段,排序字段若通过索引去访问将大大提高排序的速度
- 查询中统计或者分组字段
避免创建索引的情况:
-
数据唯一性差的字段不要使用索引
比如性别,只有两种可能数据。意味着索引的二叉树级别少,多是平级。这样的二叉树查找无异于全表扫描。
-
频繁更新的字段不要使用索引
比如登录次数,频繁变化导致索引也频繁变化,增大数据库工作量,降低效率。
-
字段不在 where 语句出现时不要添加索引
只有在 where 语句出现,mysql 才会去使用索引
-
数据量少的表不要使用索引
使用了改善也不大
七、哪些 sql 能命中索引
-
前导模糊查询不能使用索引,如
name like '%涛'
-
union
、in
、or
可以命中索引,建议使用in
。 -
负条件查询不能使用索引,可以优化为
in
查询,其中负条件有!=
、<>
、not in
、not exists
、not like
等 -
联合索引最左前缀原则,又叫最左侧查询,如果在
(a, b, c)
三个字段上建立联合索引,那么它能够加快a | (a, b) | (a, b, c)
三组的查询速度。 -
建立联合查询时,区分度最高的字段在最左边
-
如果建立了
(a,b)
联合索引,就不必再单独建立 a 索引。同理,如果建立了(a,b,c)
索引就不必再建立a,(a,b)
索引 -
存在非等号和等号混合判断条件时,在建索引时,要把等号条件的列前置
-
范围列可以用到索引,但是范围列后面的列无法用到索引。
索引最多用于一个范围列,如果查询条件中有两个范围列则无法全用到索引。范围条件有:<
、<=
、>
、>=
、between
等。
-
把计算放到业务层而不是数据库层。在字段上计算不能命中索引,
-
强制类型转换会全表扫描,如果
phone
字段是varchar
类型,则下面的 SQL 不能命中索引。Select \* fromuser where phone=13800001234
-
更新十分频繁、数据区分度不高的字段上不宜建立索引。
更新会变更 B+ 树,更新频繁的字段建立索引会大大降低数据库性能。
“性别”这种区分度不太大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似。
一般区分度在 80%以上就可以建立索引。区分度可以使用 count(distinct(列名))/count(\*)
来计算。
- 利用覆盖索引来进行查询操作,避免回表。
被查询的列,数据能从索引中取得,而不是通过定位符 row-locator
再到 row
上获取,即“被查询列要被所建的索引覆盖”,这能够加速度查询。
-
建立索引的列不能为
null
,使用not null
约束及默认值 -
利用延迟关联或者子查询优化超多分页场景,
MySQL 并不是跳过 offset
行,而是取 offset+N
行,然后放弃前 offset
行,返回 N
行,那当 offset
特别大的时候,效率非常低下,要么控制返回的总数,要么对超过特定阈值的页进行 SQL 改写。
-
业务上唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
-
超过三个表最好不要用
join
,需要join
的字段,数据类型必须一致,多表关联查询时,保证被关联的字段需要有索引。 -
如果明确知道查询结果只要一条,
limit 1
能够提高效率,比如验证登录的时候。 -
Select 语句务必指明字段名称
-
如果排序字段没有用到索引,就尽量少排序
-
尽量用
union all
代替union
。union
需要将集合合并后在进行唯一性过滤操作,这会涉及到排序,大量的 CPU 运算,加大资源消耗及延迟,当然,使用union all
的前提条件是两个结果集没有重复数据。
八、总结
索引是非常重要的技术!
但每建立一个索引,实际上都需要在硬盘上开辟一块空间用于存储这个索引所需要的数据结构(虽然表述不太准确但是是这个意思),因此不建议对太长的字段建立索引。
而且建立的索引并不是越多越好,因为索引虽然能够提高查询效率,但是会大大得影响插入、删除和修改的效率,因为每一次数据的更新都会牵涉到对索引的修改。
综上所述,往往在对于大量数据的插入的情况的时候,我们需要先删除掉数据表的索引,等插入完毕后重新建立索引,这样才能最大限度地保证数据库的效率!
网友评论