MySQL调优(五)索引优化


1 认识MySQL索引

  索引是数据库中对一列值或者多列值排序的存储结构,合理地创建索引可以提高查询效率,减少排序和分组的时间。具体内容可参考官方文档

1.1 索引的优点

  索引有以下优点:

  1. 减少服务器查询数据时需要扫描的数据量。
  2. 帮助服务器避免排序和临时表。
  3. 将随机I/O变成顺序I/O。

1.2 索引的用处

  MySQL使用索引可以做以下操作:
4. 快速查找匹配WHERE子句的数据行。
5. 在待选数据中消除行。比如有多个索引可供选择,MySQL通常会使用匹配到最少行的索引。
6. 如果表具有组合索引,优化器可以使用索引的任何最左前缀来查找行。
7. 查找特定索引列的min或max值。
8. 如果排序或分组时在可用索引的最左前缀上完成的,则对表进行排序和分组。
9. 在某些情况下,可以优化查询以检索值而无需查询数据行

2 索引的分类

  索引有以下几种分类:
10. 唯一索引:在唯一列上创建的索引,它通过确保表中没有两个数据行具有完全相同的键值来帮助维护数据完整性。
11. 主键索引:是一种特殊的唯一索引,不允许有空值。
12. 普通索引:又称为非唯一索引,不约束列的唯一性,仅仅是为了加快数据查询速度,应当为经常出现在查询条件where或排序条件order by的列建立普通索引。
13. 全文索引,基于文本列创建的索引,可以加快在这些列上查询和DML操作的速度。
14. 组合索引,使用多个列创建的索引。

3 索引的数据结构

3.1 B+Tree索引

  大部分的MySQL索引都是使用B+树来组织数据的,比如InnoDB和MyISAM存储引擎。

3.2 哈希索引

  除了B+树以外,还有哈希索引,MEMORY存储引擎即使用了哈希索引。详细信息参考《MySQL之哈希索引》

4 组合索引

  包含多个列作为索引,需要注意的如何设置组合索引中字段的顺序。例如:一个表中有name VARCHCAR和age INT两个字段,业务中可能会根据name和age查找记录,也能只根据name或age查找记录。这时有两种建立索引的方法,一个方法是创建(age, name)和name两个索引,另一个方法是创建(name, age)和age两个索引。显然,第二种方法更好,因为索引(age, name)和(name, age)占用的空间是一样的,而age占用空间比name少。

5 覆盖索引

  如果一个索引包含所有需要查询的字段的值,我们称之为覆盖索引。不同的存储引擎实现覆盖索引的方式不同,不是所有的引擎都支持覆盖索引,MEMORY不支持覆盖索引。由于索引条目通常远小于数据行大小,如果只需要读取索引,那么MySQL就会极大地减少数据访问量。当在查询中使用到覆盖索引时,执行计划的Extra字段会出现Using index:

mysql> explain select s_name, age, address from student where s_name = 'July' and age = 25 and address = 'Street Bell';
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key     | key_len | ref               | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ref  | idx_naa       | idx_naa | 140     | const,const,const |    1 |   100.00 | Using index |
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

6 索引匹配方式

  创建表并添加索引:

CREATE TABLE student(
	s_id INT PRIMARY KEY AUTO_INCREMENT,
	s_name VARCHAR(24) NOT NULL DEFAULT "",
	age INT NOT NULL DEFAULT 0,
	address VARCHAR(20) NOT NULL DEFAULT "",
	in_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) CHARSET UTF8;
ALTER TABLE student ADD INDEX idx_naa(s_name, age, address);
  • 全值匹配:和索引中的所有列进行匹配。
mysql> explain select * from student where s_name = 'Mike' and age = 23 and address = 'Street Bell';
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------+
| id | select_type | table   | partitions | type | possible_keys | key     | key_len | ref               | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------+
|  1 | SIMPLE      | student | NULL       | ref  | idx_naa       | idx_naa | 140     | const,const,const |    1 |   100.00 | NULL  |
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
  • 匹配最左前缀:只匹配组合索引中前面几列。
mysql> explain select * from student where s_name = 'Mike' and age = '23';
+----+-------------+---------+------------+------+---------------+---------+---------+-------------+------+----------+-------+
| id | select_type | table   | partitions | type | possible_keys | key     | key_len | ref         | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+---------+---------+-------------+------+----------+-------+
|  1 | SIMPLE      | student | NULL       | ref  | idx_naa       | idx_naa | 78      | const,const |    1 |   100.00 | NULL  |
+----+-------------+---------+------------+------+---------------+---------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
  • 匹配列前缀:匹配某一列的值的开头部分。
mysql> explain select * from student where s_name like 'M%';
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | student | NULL       | range | idx_naa       | idx_naa | 74      | NULL |    1 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
  • 匹配范围值:可以查找某一个范围的数据。
mysql> explain select * from student where s_name > 'Mike';
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | student | NULL       | range | idx_naa       | idx_naa | 74      | NULL |    1 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
  • 精确匹配某一列并范围匹配另外一列。
mysql> explain select * from student where s_name = 'July' and age > 25;
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | student | NULL       | range | idx_naa       | idx_naa | 78      | NULL |    1 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+---------------+---------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)
  • 只访问索引的查询,即覆盖索引。
mysql> explain select s_name, age, address from student where s_name = 'July' and age = 25 and address = 'Street Bell';
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key     | key_len | ref               | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------------+
|  1 | SIMPLE      | student | NULL       | ref  | idx_naa       | idx_naa | 140     | const,const,const |    1 |   100.00 | Using index |
+----+-------------+---------+------------+------+---------------+---------+---------+-------------------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

7 聚簇索引和非聚簇索引

7.1 聚簇索引

  不是单独的索引类型,而是一种数据存储方式,指的是数据行跟相邻的键值紧凑的存储在一起。在InnoDB存储引擎中,索引和数据都保存在同一个.ibd数据文件中。聚簇索引有以下优点:

  • 可以把相关数据保存在一起。
  • 数据访问更快,因为索引和数据保存在同一个树中。
  • 使用覆盖索引扫描的查询可以直接使用页节点中的主键值。

聚簇索引同时也存在一些缺点:

  • 插入速度严重依赖于插入顺序(页分裂),按照主键的顺序插入是最快的方式。
  • 更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置。
  • 基于聚簇索引的表在插入新行,或者主键被更新导致需要移动行的时候,可能面临页分裂的问题。
  • 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续的时候。

7.2 非聚簇索引

  数据文件跟索引文件分开存放,在MyISAM存储引擎中,索引存放在.myi文件中,而数据存放在.myd文件中。

8 细节优化

  • 当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层。
  • 尽量使用主键查询,而不是其他索引,因为主键查询不会触发回表查询。
  • 使用前缀索引。
  • 使用索引扫描来排序。
  • union all,in,or都能够使用索引,但是推荐使用in。
  • 范围列可以用到索引。
  • 强制类型转换会全表扫描。
  • 更新十分频繁,数据区分度不高的字段上不宜建立索引。
  • 创建索引的列,不允许为null,可能会得到不符合预期的结果。
  • 当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致。
  • 能使用limit的时候尽量使用limit。
  • 单表索引建议控制在5个以内。
  • 组合索引字段数不允许超过5个。

9 索引监控

  MySQL中可以通过show status like 'Handler_read%'语句查看索引的使用情况,一般需要重点关注Handler_read_key和Handler_read_rnd_next,当值很小的时候,可能意味着索引创建有问题。

mysql> show status like 'Handler_read%';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| Handler_read_first    | 2     |
| Handler_read_key      | 3     |
| Handler_read_last     | 0     |
| Handler_read_next     | 0     |
| Handler_read_prev     | 0     |
| Handler_read_rnd      | 0     |
| Handler_read_rnd_next | 104   |
+-----------------------+-------+
7 rows in set (0.01 sec)
  • Handler_read_first:读取索引第一个条目的次数。
  • Handler_read_key:通过index获取数据的次数。
  • Handler_read_last:读取索引最后一个条目的次数。
  • Handler_read_next:通过索引读取下一条数据的次数。
  • Handler_read_prev:通过索引读取上一条数据的次数。
  • Handler_read_rnd:从固定位置读取数据的次数。
  • Handler_read_rnd_next:从数据节点读取下一条数据的次数。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值