0%

MySQL 索引

欢迎指正。

索引的一些基本认知:

  • 索引可以加快数据库的检索速度
  • 表经常进行INSERT/UPDATE/DELETE操作就不要建立索引了,换言之:索引会降低插入、删除、修改等维护任务的速度。
  • 索引需要占物理和数据空间。
  • 了解过索引的最左匹配原则
  • 知道索引的分类:聚集索引和非聚集索引
  • MySQL 支持Hash索引和B+树索引两种

索引的基础知识

为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间 (因为索引也要随之变动)。
创建索引可以大大提高系统的性能。

  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
  • 可以大大加快数据的检索速度,这也是创建索引的最主要的原因。
  • 可以加速表和表之间的连接,
  • 使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

创建索引的原则

创建索引的一些原则:

  • 较频繁的作为查询条件的字段应该创建索引
  • 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
  • 更新非常频繁的字段不适合创建索引(数据库会将索引数据根据算法排序,数据量大之后重新排序会占用过多资源)
  • 用于索引的最好的备选数据列是那些出现在WHERE子句、join子句、ORDER BY或GROUP BY子句中的列。

MySQL 存储结构

MySQL的基本存储结构是,一页的大小一般是16KB

  • 各个数据页可以组成一个双向链表
  • 每个数据页中的记录可以组成单向链表

InnoDB 表

在InnoDB存储引擎中,表都是根据主键顺序组织存放的。在InnoDB存储引擎表中,每张表都有个主键,如果在创建表的时没有显式地定义主键,InnoDB存储引擎会按照下面选择或者创建主键:

  • 首先判断表中是否有非空的唯一索引,如果有,则该列即为主键(如果有多个的时候,选择第一个定义的唯一索引);
  • 如果不符合上述条件,InnoDB存储引擎自动创建一个6字节大小的指针。

注意: 在建表的时候,最好还是指定主键,而且最好使用自增,而不是UUID这种作为主键,因为UUID一般都是无序的,在插入新的记录的时候,可能增加额外的开销。B+树为了维护索引的有序性,在插入新值得实惠需要做必要的维护。比如在某页数据中间插入某条数据,这时候就需要移动后面的数据,空出位置。如果碰到这一页数据满了,还需要申请新的数据页,挪动部分数据过去,这种过程称为页的分裂,这种情况就很影响性能。

索引提高检索速度

索引用于快速查找具有特定列值的行。如果没有索引,MySQL必须从第一行开始,然后读取整个表以查找相关行。表越大,成本越高。如果表中有相关​​列的索引,MySQL可以快速确定要在数据文件中间寻找的位置,而无需查看所有数据。这比按顺序读取每一行要快得多。

如果没有索引

在一个页里查找

如果表中数据现在比较少,都存放在一个页里,在查找的时候按照不同省市条件查找:

  • 主键:在 页目录 中使用二分法快速定位到相应的槽,然后在遍历该槽对应分组即可查找到数据
  • 其他列:只能从最小记录开始依次遍历单链表中的每条记录,然后对比每条记录是不是符合搜索条件

多页中查找

  • 先定位到数据所在的页;
  • 在从页中查找相应的数据。

使用索引

将无序的数据变成有序(相对)。

索引数据结构

B-Tree 索引

B-Tree 是一棵多叉树,在非叶子节点中存储了数据。

B+ Tree索引

该索引使用 B+Tree 数据结构来存储数据,B+Tree 是InnoDB存储引擎默认的索引结构。

B+ Tree 是 B-Tree 的变种,通常意味着所有数据值都是按照顺序存储的,并且每一个叶子页到根的距离相同。存储引擎不需要进行全表扫描来获取需要的数据,只需要从索引节点进行搜索。索引的叶子节点之间通过索引互相连接。

哈希索引

基于哈希表实现,只有精确匹配索引所有列的查询才有效。

1
mysql> select * from table where name = 'Peter'

MySQL先会计算 ‘Peter’ 的哈希值,并使用该值寻找对应的记录指针。

哈希索引的限制:

  • 不能使用索引中的值来避免读取行
  • 不能按照索引顺序存储,所以无法用于排序
  • 不支持部分索引列匹配查询
  • 只支持等值查询,包括 =、in()、<=>

InnoDB 为什么选择 B+Tree 作为默认索引结构

  • 哈希虽然能够提供 O(1) 的单数据行操作性能,但是对于范围查询和排序却无法很好地支持,最终导致全表扫描;
  • B-Tree 能够在非叶节点中存储数据,但是这也导致在查询连续数据时可能会带来更多的随机 I/O,而 B+Tree 的所有叶节点可以通过指针相互连接,能够减少顺序遍历时产生的额外随机 I/O。

常见索引类型

聚簇索引

聚簇索引指主索引文件和数据文件为同一份文件,聚簇索引主要用在 InnoDB 存储引擎中。在该索引实现方式中,B+ Tree 的叶子节点上的data就是数据本身,key为主键,如果是一般索引的话,data便会指向对应的主索引。一张表也不可能有两个地方存储数据,所以一张表只能有一个聚簇索引。

InnoDB 表中聚簇索引的索引列就是主键,所以聚簇索引也叫主键索引。

非聚簇索引

非聚簇索引指的是 B+Tree 的叶节点上的data,并不是数据本身,而是数据存放的地址。主索引和辅助索引没啥区别,只是主索引的key一定是唯一的。主要用于MyIsAM存储引擎中。

非聚簇索引比聚簇索引多了一次读取数据的IO操作,索引查找性能上会差。

二级索引

对于 InnoDB 表,在非主键的其他列上建的索引就是二级索引。

按照字段列分类

MySQL的索引分为单列索引(主键索引,唯一索引,普通索引)和组合索引(或叫联合索引)

  • 单列索引:一个索引只包含一个列,一个表可以有多个单列索引
  • 组合索引:一个组合索引包含两个或两个以上的列。从左到右

索引使用的基本常识

注意点

使用索引时的一些注意点:

  • 符合最左原则
  • 如果是or,不走索引
  • 数据区分度不大的字段不宜使用索引
  • MySQL从左到右的使用索引中的字段,一个查询可以使用索引中的一部分,但只能是最左侧部分。例如索引是key index(a,b,c),可以支持a|a,b|a,b,c 3种组合进行查询,但不支持b,c进行查找。当最左侧字段是常量引用时,索引就十分有效。

索引的优点

  • 索引大大减少了服务器需要扫描的数据量
  • 索引可以帮助服务器避免排序和临时表
  • 索引可以将随机 I/O 变为顺序 I/O

最左匹配原则

  • 索引可以简单如一个列 (a),也可以复杂如多个列 (a,b,c,d),即联合索引。
  • 如果是联合索引,那么key也由多个列组成,同时,索引只能用于查找key是否存在(相等),遇到范围查询 (>、<、between、like左匹配)等就不能进一步匹配了,后续退化为线性查找。
  • 因此,列的排列顺序决定了可命中索引的列数。

比如,现在有索引 (a,b,c,d),查询条件 a=1 and b=2 and c>3 and d=4,则会在每个节点依次命中a、b、c,无法命中d。(c已经是范围查询了,d肯定是排不了序了)。

回表和覆盖索引

回表

在InnoDB中,对于普通索引的查询,是无法直接定位行记录的。普通索引需要先定位到对象的主键,然后通过主键去查询数据。这种查询相对于聚簇索引来说,多了一次查询。

覆盖索引

覆盖索引是一种避免回表查询的优化策略。具体的做法是将查询的数据作为索引列建立普通索引,这样就可以直接返回索引中的数据,而不需要通过聚簇索引去定位行记录,避免回表的发生。

索引下推

最左前缀可以用于在索引中定位记录。现在我们有一个联合索引(name, age),现在需要查询下面这个SQL:

1
select * from table where name like "张%" and age = 10

使用前缀索引规则是,只能用“张”,找到第一个满足条件的记录,然后在一个个回表查询数据行。MySQL 引入索引的下推优化(index condition pushdown),可以在索引遍历中,对索引包含的字段现做判断,直接过滤掉不满足的记录,减少回表次数。如下图所示,第一个图是吴索引下推的流程,第二张图,则是进行了索引下推优化,减少了回表次数。

=、in自动优化顺序

对于注意点最后一点,在MySQL中,对于索引会自动优化条件的顺序,方便匹配尽可能对的索引列。可以看下面一个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CCREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`password` varchar(64) NOT NULL COMMENT '密码',
`card` varchar(64) DEFAULT NULL COMMENT '身份证',
`phone` varchar(64) DEFAULT NULL COMMENT '手机',
`province_id` bigint(12) DEFAULT NULL COMMENT '省份城市',
`city_id` bigint(12) DEFAULT NULL COMMENT '城市ID',
`status` tinyint(3) DEFAULT '1' COMMENT '状态, 1:启用,0:删除',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_user` (`name`,`password`,`card`,`phone`,`age`) USING BTREE,
KEY `idx_age` (`age`) USING BTREE,
FULLTEXT KEY `ft_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户表';

如上面创建一张表,并创建索引 idx_user
1、从上面的一个例子,我们可以看出,虽然我们查询条件并没有按照索引里面的顺序,但是查询还是走的是索引。

2、这个例子看出,下面这个查询没有走索引。可以注意点最后一点对照着看。

关于IN是否走索引

1
EXPLAIN SELECT * FROM `user` WHERE age IN (5,6,18,19)

Reference

客官,赏一杯coffee嘛~~~~