红魔咖啡馆

头发越掉越多,头发越掉越少

0%

【MySQL】索引

索引是帮助MySQL高效获取数据的数据结构

优缺点:

索引结构

索引实现于存储引擎层,不同存储引擎有不同结构:

  • B+树索引:大部分支持
  • Hash索引:底层为哈希表,只有精确匹配索引列的查询才有效
  • R-tree(空间索引):MyISAM的索引类型,用于地理空间数据类型
  • Full-text(全文索引):通过建立倒排索引快速匹配文档

B+树优化

MySQL中对B+树进行了优化,增加了一个指向相邻叶子节点的链表指针,形成了带有顺序指针的B+树,提高了访问区间的性能

索引分类

InnoDB中,根据索引存储形式,又可以分为下面两种:

聚集索引的选取规则:

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

语法

  • 创建索引:CREATE [UNIQUE/FULLTEXT] INDEX 索引名 ON 表名 (字段名,...);
    • 不加参数是普通索引
    • 字段名为索引关联的字段,若只有一列字段称为单列索引,多列字段称为联合索引
  • 查看索引:SHOW INDEX FROM 表名;
  • 删除索引:DROP INDEX 索引名 ON 表名

性能分析

SQL执行频次

通过SHOW [SESSION|GLOBAL] STATUS;命令可以提供服务器状态信息

如下指令可以查看增删查改等命令的访问频次:

SHOW GLOBAL STATUS LIKE 'Com_______'

慢查询日志

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

MySQL的慢查询日志默认没有开启,需要在配置文件(/etc/my.cnf)中配置:

1
2
show_query_log = 1
long_query_time = 2 // 时间超过的参数

生成的日志存放放在/var/lib/mysql,名为localhost-slow.log

show_profile

可以帮助我们了解时间的耗费情况

  • 通过have_profiling参数可以查看是否支持profile操作:SELECT @@have_profiling;

  • 通过SET语句开启:SET [SESSION|GLOBAL] profiling = 1;

  • 查看SQL语句耗时情况:SHOW profiles;

  • 指定看第几个操作的各个阶段的耗时:SHOW profiles FOR query xx;

explain

执行计划,通过该指令可以获取MySQL是如何执行某SELECT语句的信息,包括再执行过程中表如何连接以及连接顺序等

直接在SELECT前面添加DESC或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:返回结果的行数占需读取行数的百分比

  • extra:额外信息

索引使用

最左前缀法则

对于联合索引,要遵循最左前缀法则:查询从索引的最左列开始,且不跳过索引中的列

如果跳过某一列,这一列后的字段索引会失效

且最左前缀法则要求的是最左前缀一定存在,和顺序无关

如,已经建立了一个以profession, age和status为列的联合索引

以下查询可以走索引:

1
2
3
4

SELECT * FROM user WHERE profession = '软件工程' and age = '18';
SELECT * FROM user WHERE profession = '软件工程';
SELECT * FROM user WHERE age = '18' and status = '0' and profession = '软件工程';

而以下查询是全表扫描:

1
2
SELECT * FROM user WHERE status = '0';
SELECT * FROM user WHERE age = '18' and status = '0';

而以下查询部分失效:

1
SELECT * FROM user WHERE profession = '软件工程' and status = '0';

这里profession走了索引,后面的status失效

索引失效

范围查询

联合索引中,若出现范围查询(>, <),索引该位置右侧的列会失效

e.g.

1
SELECT * FROM user WHERE profession = '软件工程' and age > '18' and status = '0';

最后的status不走索引

索引列运算

不要在索引列上进行运算操作,否则索引失效

1
SELECT * FROM user WHERE sbstring(phone, 10, 2)= '15';

模糊查询

若仅是尾部模糊匹配,索引不会失效,如果是从头部模糊匹配,索引会失效

e.g.以下走索引:

1
SELECT * FROM user WHERE profession '软件%'

以下不走索引:

1
SELECT * FROM user WHERE profession '%工程'

or连接的条件

用or连接的条件,如果or前的条件中的列有索引,但后面的没有,则涉及的索引都不会被用到,即or两侧的条件必须都要有索引

e.g. 以下不走索引:

1
SELECT * FROM user WHERE id = 10 or age = 21;

因为这里age不在索引内,需要手动添加

数据分布影响

如果MySQL评估使用索引比全表扫描慢,则不用索引

SQL提示

优化数据库的手段,在SQL语句中添加一些人为提示来达到优化操作的目的

  • USE INDEX:(建议MySQL)使用某个索引

SELECT * FROM user USE INDEX(idx_user_pro) WHERE profession = '软工';

  • IGNORE INDEX:不用某个索引

SELECT * FROM user IGNORE INDEX(idx_user_pro) WHERE profession = '软工'

  • FORCE INDEX:强制使用某个索引

SELECT * FROM user FORCE INDEX(idx_user_pro) WHERE profession = '软工'

覆盖索引

当我们执行SELECT操作时,对其EXPLAIN,注意EXTRA中的内容:

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

后者性能比前者高,因此在实际操作中对于查询操作多的数据要建立合适的索引

前缀索引

字段类型是字符串时,有时索引的字符串很长,索引很大,会影响效率,此时可以取字符串的一部分前缀,可以节约空间,提升效率

语法:CREATE INDEX idx_xxx ON table_name(column(n));

n为存储前n个字符

前缀长度:根据索引选择性来决定(不重复的索引值和数据表的记录总数的比值),选择性越高查询效率越高。唯一索引的选择性是1,是最好的索引选择性

前缀索引必然回表,因为它要根据前缀在辅助索引中找到id,用id回到聚集索引获取完整内容

单列/联合索引

  • 单列索引:索引包含单个列
  • 联合索引:索引包含多个列

业务场景下,若存在多个查询条件,考虑针对查询字段建立索引时,建议建立联合索引

B+树中,联合索引的每一列值组合作为节点使用,按照顺序排序

索引设计原则

  1. 针对数据量较大,查询比较频繁的表建立索引:一般对于百万量级的表建立,没有查询需求的表不需要建立索引
  2. 要针对常作为查询条件、排序、分组操作的字段建立索引
  3. 尽量选择区分度高的列作为索引,尽量用唯一索引:区分度高效率高
  4. 如果是字符串类型的字段,字段长度较长,可以建立前缀索引
  5. 尽量使用联合索引,减少单列索引:联合索引可以覆盖索引,避免回表,提升效率
  6. 控制数量:索引越多,维护的代价越带,影响增删改的效率
  7. 若索引列不能存储NULL值,建表时使用NOT NULL约束:优化器知道每列是否包含NULL值时,才能更好的确定哪个索引最有效地用于查询