1.索引的作用

一句话简单来说,索引的出现其实就是为了提高数据查询的效率,就像书的目录一样。

2.索引的常见模型

2.1 哈希表

哈希的思路很简单,把值放在数组里,用一个哈希函数把 key 换算成一个确定的位置,然后把 value 放在数组的这个位置。不可避免地,多个 key 值经过哈希函数的换算,会出现同一个值的情况(冲突)。处理这种情况的一种方法是,拉出一个链表,对重复的结果进行遍历匹配

 

哈希表这种结构适用于只有等值查询的场景,比如 Memcached 及其他一些 NoSQL 引擎。

2.2 有序数组

有序数组在等值查询和范围查询场景中的性能就都非常优秀,用二分法就可以快速得到,这个时间复杂度是 O(log(N))。但是有序数组在插入时成本太高

 

所以,有序数组索引只适用于静态存储引擎

 

2.3 二叉搜索树

 

数据库表已有主键还需要建立索引吗 mysql主键还需要建索引吗_数据库表已有主键还需要建立索引吗

 

 

二叉搜索树的特点是:

每个节点的左儿子小于父节点,父节点又小于右儿子。这样如果你要查 ID_card_n2 的话,按照图中的搜索顺序就是按照 UserA -> UserC -> UserF -> User2 这个路径得到。这个时间复杂度是 O(log(N))。当然为了维持 O(log(N)) 的查询复杂度,你就需要保持这棵树是平衡二叉树。为了做这个保证,更新的时间复杂度也是 O(log(N))。

 

树可以有二叉,也可以有多叉。多叉树就是每个节点有多个儿子,儿子之间的大小保证从左到右递增。二叉树是搜索效率最高的,但是实际上大多数的数据库存储却并不使用二叉树。其原因是,索引不止存在内存中,还要写到磁盘上。

(上面的每个索引都是一个数据块)

为了让一个查询尽量少地读磁盘,就必须让查询过程访问尽量少的数据块。那么,我们就不应该使用二叉树,而是要使用“N 叉”树。这里,“N 叉”树中的“N”取决于数据块的大小。

 

以 InnoDB 的一个整数字段索引为例,这个 N 差不多是 1200。这棵树高是 4 的时候,就可以存 1200 的 3 次方个值,这已经 17 亿了。考虑到树根的数据块总是在内存中的,一个 10 亿行的表上一个整数字段的索引,查找一个值最多只需要访问 3 次磁盘。其实,树的第二层也有很大概率在内存中,那么访问磁盘的平均次数就更少了

 

 

3.InnoDB 的索引模型

数据库表已有主键还需要建立索引吗 mysql主键还需要建索引吗_数据库表已有主键还需要建立索引吗_02

 

根据叶子节点的内容,索引类型分为主键索引和非主键索引。主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引(clustered index)。非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引(secondary index)。

 

回表:通过普通索引查询到主键值时,还需要再从主键索引树里面再查一遍的对应的行数据,其实实际是这个主键索引不见得存在,当没有主键的时候,会自动生成一个 6 字节的rowID来建立主键索引树,因此当有主键时,rowID是主键。

 

也就是说,基于非主键索引的查询需要多扫描一棵索引树。因此,我们在应用中应该尽量使用主键查询。

 

显然,主键长度越小,普通索引的叶子节点就越小,普通索引占用的空间也就越小。

 

自增主键的插入数据模式,正符合了我们前面提到的递增插入的场景。每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。而有业务逻辑的字段做主键,则往往不容易保证有序插入,这样写数据成本相对较高。

但是有些情况下,但有几种极端情况用业务字段做主键也是可以的:

1.只有一个索引

2.该索引必须是唯一索引

 

4.覆盖索引

覆盖索引是select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖。一般来说这个说的就是主键索引字段(主键索引可以是多个字段),因为这个时候在普通索引树就可以查询到主键字段,因此不用回表。

但是需要注意的是,扫描行数实际上是MySQL估计找到需要的记录而需要遍历的行数,这是一个估计值,并不完全准确

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

 

5.最左前缀原则

简单来说,假设有联合索引(a,b,c),那么 select * from t where a=1 and b=1 能走索引,select * from t where a=1 and c=2 则不走索引

第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的

第二原则就是空间,如果既有联合查询,又有基于 a、b 各自的查询呢?查询条件里面只有 b 的语句,是无法使用 (a,b) 这个联合索引的,这时候你不得不维护另外一个索引,也就是说你需要同时维护 (a,b)、(b) 这两个索引。

此时如果 b 字段比 a 字段要大,那么最好是建立 (b,a) (a) 索引

 

6.索引下推

在 MySQL 5.6 之前,只能从获取到的主键id 开始一个个回表。到主键索引上找出数据行,再对比字段值。而 MySQL 5.6 引入的索引下推优化(index condition pushdown), 可以在索引遍历过程中,对索引中已经包含的字段先做条件的判断,直接过滤掉不满足条件的记录,减少回表次数。

例如:

mysql> select * from tuser where name like '张%' and age=10 and ismale=1;

无索引下推

 

数据库表已有主键还需要建立索引吗 mysql主键还需要建索引吗_sql_03

 

有索引下推

数据库表已有主键还需要建立索引吗 mysql主键还需要建索引吗_主键_04

7.唯一索引和普通索引的区别

查询过程:

对于普通索引来说,查找到满足条件的第一个记录 (5,500) 后,需要查找下一个记录,直到碰到第一个不满足 k=5 条件的记录。对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。

 

InnoDB 的数据是按数据页为单位来读写的。也就是说,当需要读一条记录的时候,并不是将这个记录本身从磁盘读出来,而是以页为单位,将其整体读入内存。在 InnoDB 中,每个数据页的大小默认是 16KB。因为引擎是按页读写的,所以说,当找到 k=5 的记录的时候,它所在的数据页就都在内存里了。那么,对于普通索引来说,要多做的那一次“查找和判断下一条记录”的操作,就只需要一次指针寻找和一次计算。当然,如果 k=5 这个记录刚好是这个数据页的最后一个记录,那么要取下一个记录,必须读取下一个数据页,这个操作会稍微复杂一些。但是,我们之前计算过,对于整型字段,一个数据页可以放近千个 key,因此出现这种情况的概率会很低。所以,我们计算平均性能差异时,仍可以认为这个操作成本对于现在的 CPU 来说可以忽略不计。

 

更新过程:

唯一索引的更新就不能使用 change buffer,实际上也只有普通索引可以使用。这是因为唯一索引为了保证数据的唯一性,每次更新操作都必须读磁盘,来判断是否有唯一性冲突,无法直接将操作记录在change buffer 中

第一种情况是,这个记录要更新的目标页在内存中。这时,InnoDB 的处理流程如下:对于唯一索引来说,找到 3 和 5 之间的位置,判断到没有冲突,插入这个值,语句执行结束;对于普通索引来说,找到 3 和 5 之间的位置,插入这个值,语句执行结束。这样看来,普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小的 CPU 时间。但,这不是我们关注的重点。

 

第二种情况是,这个记录要更新的目标页不在内存中。这时,InnoDB 的处理流程如下:对于唯一索引来说,需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;对于普通索引来说,则是将更新记录在 change buffer,语句执行就结束了。将数据从磁盘读入内存涉及随机 IO 的访问,是数据库里面成本最高的操作之一。change buffer 因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。之前我就碰到过一件事儿,有个 DBA 的同学跟我反馈说,他负责的某个业务的库内存命中率突然从 99% 降低到了 75%,整个系统处于阻塞状态,更新语句全部堵住。而探究其原因后,我发现这个业务有大量插入数据的操作,而他在前一天把其中的某个普通索引改成了唯一索引。