索引

125 阅读9分钟

概述

索引(index)是帮助MySQL高效获取数据的数据结构(有序) 。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

常见索引结构

索引结构描述
B+Tree索引最常见的索引类型,大部分引擎都支持B+树索引
Hash索引底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询
R-tree(空间索引)空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-text(全文索引)是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene,Solr,ES

mysql中的b+树索引

mysql索引经典b+树做了优化,增加了一个指向相邻叶子节点的指针,使得叶子节点形成了一个双向循环链表,提高了访问区间的性能

索引分类

分类含义特点关键字
主键索引针对于表中主键创建的索引默认自动创建,只能有一个PRIMARY
唯一索引避免同一个表中某数据列中的值重复可以有多个UNIQUE
常规索引快速定位特定数据可以有多个
全文索引全文索引查找的是文本中的关键词,而不是比较索引中的值可以有多个FULLTEXT

在innoDB引擎中,根据索引的存储方式,又能分为以下两种:

分类含义特点
聚集索引(Clustered Index)将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据必须有,而且只有一个
二级索引(Secondary Index)将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键可以存在多个

聚集索引选取规则:

  • 如果存在主键,主键索引就是聚集索引。
  • 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。
  • 如果表没有主键,或没有合适的唯一索引,则InnoDB会自动生成一个rowid作为隐藏的聚集索引。

索引语法

  • 创建索引: CREATE [UNIQUE | FULLTEXT] INDEX 索引名 ON 表名(字段名, ...);

  • 查看索引: SHOW INDEX FROM 表名;

  • 删除索引: DROP INDEX 索引名 ON 表名;

sql性能分析

sql执行频率

要对表进行性能分析,要先分析表中哪种操作的频率是最高的。

在mysql中,通过 show [session|global] status 命令可以提供服务器状态信息。通过如下指令,可以查看当前数据库的 INSERT、UPDATE、DELETE、SELECT 的访问频次: SHOW GLOBAL STATUS LIKE 'Com______';

在查出来的数据中, Com_select就是查询次数,Com_insert是插入次数,其他操作类似。

慢查询日志

慢查询日记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认 10 秒)的所有 SQL 语句的日志。

MySQL 的慢查询日志默认没有开启,查看慢查询日志是否开启: show variables like "slow_query_log";

开启需要在 MySQL 的配置文件(/etc/my.cnf)中配置如下信息:

# 开启MySQL慢日志查询开关
slow_query_log=1
# 设置慢日志的时间为2秒,SQL语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2

配置完成之后,需要重启mysql服务,并在/var/lib/mysql/localhost-slow.log这个目录下可以看到慢查询日志

profile详情

show profiles 能够在做 SQL 优化时帮助我们了解时间都耗费到哪里去了。通过 have_profiling 参数,能够看到当前 MySQL 是否支持 profile 操作: select have_profiling。 通过 profiling 参数可以看到是否开启了profile: select @@profiling

默认 profiling 是关闭的,可以通过 set 语句在 session/global 级别开启 profiling: set [session | global] profiling = 1

开启profiling之后,可以通过以下指令查看sql的执行情况:

# 查看每一条sql的耗时基本情况
show profiles;

# 查看指定query_id的sql语句在各个阶段的耗时情况
show profile for query query_id;

# 查看指定query_id的sql语句cpu的使用情况
show profile cpu for query query_id;

explain执行计划

EXPLAIN 或者 DESC 命令可以获取 MySQL 如何执行 SELECT 语句的信息,包括在 SELECT 语句执行过程中表如何连接和连接的顺序。

# 直接在select语句之前加上关键字explain / desc
EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件;

EXPLAIN 执行计划各字段含义:

  • Id:select 查询的序列号,表示查询中执行 select 子句或者是操作表的顺序 (id 相同,执行顺序从上到下;id 不同,值越大,越先执行)。
  • select_type:表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE 之后包含了子查询)等。
  • type:表示连接类型,性能由好到差的连接类型为 NULL、system、const、eq_ref、ref、range、index、all 。
  • possible_key:显示可能应用在这张表上的索引,一个或多个。
  • Key:实际使用的索引,如果为NULL,则没有使用索引。
  • Key_len:表示索引中使用的字节数, 该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好 。
  • rows:MySQL认为必须要执行查询的行数,在innodb引擎的表中,是一个估计值,可能并不总是准确的。
  • filtered:表示返回结果的行数占需读取行数的百分比,filtered的值越大越好。

索引使用规则

最左前缀法则

如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始,并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。

范围查询

联合索引中,如果某一列出现了范围查询(<, >),该列右侧的索引列失效。在业务允许的情况下,使用<=、>=可规避这种情况。

索引失效情况

  • 索引列运算: 不要在索引列上进行运算操作,否则索引会失效。例如: SELECT * FROM user WHERE substring(phone, 10, 2) === "15";,由于对phone列进行了函数运算,所以phone列的索引将会在查询时失效
  • 字符串不加引号: 字符串类型字段使用时不加引号,索引会失效。例如: SELECT * FROM user WHERE phone = 123456;,由于phone字段是字符类型,但是筛选条件中对比的字段是数字,此时虽然能进行查询,但phone的索引会失效
  • 模糊查询: 如果模糊查询包含头部模糊查询,索引会失效。例如: SELECT * FROM user WHERE name LIKE "%平";,此时由于name字段的模糊匹配包含了头部匹配,所以name字段的索引失效。
  • or连接: 使用or连接的条件语句,如果其中一个子条件语句不包含索引,那么其他条件语句的索引也会失效。
  • 数据分布影响: 如果mysql评估使用索引比全表扫描慢,就不会使用索引。

sql提示

SQL提示就是在 SQL 语句中加入一些人为的提示来达到优化操作的目的。

  • use index: 建议mysql使用某个索引。例如:explain select * from tb_user use index(idx_user_pro) where profession = '软件工程';
  • ignore index: 忽略某个索引。例如: explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程';
  • force index: 强制使用某个索引。例如: explain select * from tb_user force index(idx_user_pro) where profession = '软件工程';

覆盖索引

尽量使用覆盖索引(查询使用了索引,并且需要返回的列,在该索引中已经全部能够找到),减少 select *,避免回表查询。

explain中extra字段中的提示:

  • using index condition:查找使用了索引,但是需要回表查询数据
  • using where; using index:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据

前缀索引

当字段类型为字符串(varchar, text 等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘 IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。

创建前缀索引: create index idx_xxxx on table_name(column(n));

前缀长度

创建前缀索引时选择的长度可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是 1,这是最好的索引选择性,性能也是最好的。

单列索引和联合索引

在业务场景中,如果存在多个查询条件,可以考虑针对需要查询的字段建立联合索引。因为联合索引可以在一次查询中查询到所需要的信息而无需回表查询,如果只是针对每个字段建立了单列索引,mysql只会选择其中一个索引进行查询,由于索引结构中不包含其他字段的信息,所以还必须进行一次回表查询,而联合索引就可以省略回表查询这个步骤。

索引设计原则

  • 针对于数据量较大,且查询比较频繁的表建立索引。
  • 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
  • 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
  • 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
  • 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
  • 要控制索引的数量,索引并非越多越好,索引越多,维护索引结构的代价越大,会影响增删改的效率。
  • 如果要创建索引的列不能存储NULL值,那么在创建表时就使用NOT NULL约束它。当优化器知道该列不包含NULL值时,能更好确定哪个索引最有效用于查询。