一篇文章搞定 MySQL 索引优化

栏目: 数据库 · 发布时间: 5年前

内容简介:本文主要讨论MySQL索引的部分知识。将会从MySQL索引基础、索引优化实战和数据库索引背后的数据结构三部分相关内容,下面一一展开(本文图片可点开放大)。首先,我们将从索引基础开始介绍一下什么是索引,分析索引的几种类型,并探讨一下如何创建索引以及索引设计的基本原则。此部分用于测试索引创建的user表的结构如下:

本文主要讨论 MySQL 索引的部分知识。将会从MySQL索引基础、索引优化实战和数据库索引背后的数据结构三部分相关内容,下面一一展开(本文图片可点开放大)。

一、MySQL索引基础

首先,我们将从索引基础开始介绍一下什么是索引,分析索引的几种类型,并探讨一下如何创建索引以及索引设计的基本原则。

此部分用于测试索引创建的user表的结构如下:

一篇文章搞定 MySQL 索引优化

1. 什么是索引?

“索引(在MySQL中也叫“键key”)是存储引擎快速找到记录的一种数据结构。”
——《高性能MySQL》

我们需要知道索引其实是一种数据结构,其功能是帮助我们快速匹配查找到需要的数据行,是数据库性能优化最常用的 工具 之一。其作用相当于超市里的导购员、书本里的目录。

2. 索引类型

可以使用SHOW INDEX FROM table_name;查看索引详情:

一篇文章搞定 MySQL 索引优化

主键索引 PRIMARY KEY:它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引。注意:一个表只能有一个主键。

一篇文章搞定 MySQL 索引优化

唯一索引 UNIQUE:唯一索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。可以通过ALTER TABLE table_name ADD UNIQUE (column);创建唯一索引:

一篇文章搞定 MySQL 索引优化
一篇文章搞定 MySQL 索引优化

可以通过ALTER TABLE table_name ADD UNIQUE (column1,column2);创建唯一组合索引:

一篇文章搞定 MySQL 索引优化
一篇文章搞定 MySQL 索引优化

普通索引 INDEX:这是最基本的索引,它没有任何限制。可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引:

一篇文章搞定 MySQL 索引优化
一篇文章搞定 MySQL 索引优化

组合索引 INDEX:即一个索引包含多个列,多用于避免回表查询。可以通过ALTER TABLE table_name ADD INDEX index_name(column1,column2, column3);创建组合索引:

一篇文章搞定 MySQL 索引优化
一篇文章搞定 MySQL 索引优化

全文索引 FULLTEXT:也称全文检索,是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引:

一篇文章搞定 MySQL 索引优化
一篇文章搞定 MySQL 索引优化

索引一经创建不能修改,如果要修改索引,只能删除重建。可以使用

DROP INDEX index_name ON table_name;删除索引。

3、索引设计的原则

1)适合索引的列是出现在where子句中的列,或者连接子句中指定的列;

2)基数较小的类,索引效果较差,没有必要在此列建立索引;

3)使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间;

4)不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。

二、MySQL索引优化实战

上面我们介绍了索引的基本内容,这部分我们介绍索引优化实战。在介绍索引优化实战之前,首先要介绍两个与索引相关的重要概念,这两个概念对于索引优化至关重要。

此部分用于测试的user表结构:

一篇文章搞定 MySQL 索引优化

1、索引相关的重要概念

基数:单个列唯一键(distict_keys)的数量叫做基数。

SELECT COUNT(DISTINCT name),COUNT(DISTINCT gender) FROM user;

一篇文章搞定 MySQL 索引优化

user表的总行数是5,gender列的基数是2,说明gender列里面有大量重复值,name列的基数等于总行数,说明name列没有重复值,相当于主键。

返回数据的比例:user表中共有5条数据:

SELECT * FROM user;

一篇文章搞定 MySQL 索引优化

查询满足性别为0(男)的记录数:

一篇文章搞定 MySQL 索引优化

那么返回记录的比例数是:

一篇文章搞定 MySQL 索引优化

同理,查询name为'swj'的记录数:

一篇文章搞定 MySQL 索引优化

返回记录的比例数是:

一篇文章搞定 MySQL 索引优化

现在问题来了,假设name、gender列都有索引,那么SELECT * FROM user WHERE gender = 0; SELECT * FROM user WHERE name = 'swj';都能命中索引吗?

user表的索引详情:

一篇文章搞定 MySQL 索引优化

SELECT * FROM user WHERE gender = 0;没有命中索引,注意filtered的值就是上面我们计算的返回记录的比例数。

一篇文章搞定 MySQL 索引优化

SELECT * FROM user WHERE name = 'swj';命中了索引index_name,因为走索引直接就能找到要查询的记录,所以filtered的值为100。

一篇文章搞定 MySQL 索引优化

因此,返回表中30%内的数据会走索引,返回超过30%数据就使用全表扫描。当然这个结论太绝对了,也并不是绝对的30%,只是一个大概的范围。

回表:当对一个列创建索引之后,索引会包含该列的键值及键值对应行所在的rowid。通过索引中记录的rowid访问表中的数据就叫回表。回表次数太多会严重影响 SQL 性能,如果回表次数太多,就不应该走索引扫描,应该直接走全表扫描。

EXPLAIN命令结果中的Using Index意味着不会回表,通过索引就可以获得主要的数据。Using Where则意味着需要回表取数据。

2. 索引优化实战

有些时候虽然数据库有索引,但是并不被优化器选择使用。我们可以通过SHOW STATUS LIKE 'Handler_read%';查看索引的使用情况:

一篇文章搞定 MySQL 索引优化

Handler_read_key:如果索引正在工作,Handler_read_key的值将很高。

Handler_read_rnd_next:数据文件中读取下一行的请求数,如果正在进行大量的表扫描,值将较高,则说明索引利用不理想。

索引优化规则:

1)如果MySQL估计使用索引比全表扫描还慢,则不会使用索引。

返回数据的比例是重要的指标,比例越低越容易命中索引。记住这个范围值——30%,后面所讲的内容都是建立在返回数据的比例在30%以内的基础上。

2)前导模糊查询不能命中索引。

name列创建普通索引:

一篇文章搞定 MySQL 索引优化

前导模糊查询不能命中索引:

EXPLAIN SELECT * FROM user WHERE name LIKE '%s%';

一篇文章搞定 MySQL 索引优化

非前导模糊查询则可以使用索引,可优化为使用非前导模糊查询:

EXPLAIN SELECT * FROM user WHERE name LIKE 's%';

一篇文章搞定 MySQL 索引优化

3)数据类型出现隐式转换的时候不会命中索引,特别是当列类型是字符串,一定要将字符常量值用引号引起来。

EXPLAIN SELECT * FROM user WHERE name=1;

一篇文章搞定 MySQL 索引优化

EXPLAIN SELECT * FROM user WHERE name='1';

一篇文章搞定 MySQL 索引优化

4)复合索引的情况下,查询条件不包含索引列最左边部分(不满足最左原则),不会命中符合索引。

name,age,status列创建复合索引:

ALTER TABLE user ADD INDEX index_name (name,age,status);

一篇文章搞定 MySQL 索引优化

user表索引详情:

SHOW INDEX FROM user;

一篇文章搞定 MySQL 索引优化

根据最左原则,可以命中复合索引index_name:

EXPLAIN SELECT * FROM user WHERE name='swj' AND status=1;

一篇文章搞定 MySQL 索引优化

注意,最左原则并不是说是查询条件的顺序:

EXPLAIN SELECT * FROM user WHERE status=1 AND name='swj';

一篇文章搞定 MySQL 索引优化

而是查询条件中是否包含索引最左列字段:

EXPLAIN SELECT * FROM user WHERE status=2 ;

一篇文章搞定 MySQL 索引优化

5)union、in、or都能够命中索引,建议使用in。

union:

EXPLAIN SELECT*FROM user WHERE status=1

UNION ALL

SELECT*FROM user WHERE status = 2;

一篇文章搞定 MySQL 索引优化

in:

EXPLAIN SELECT * FROM user WHERE status IN (1,2);

一篇文章搞定 MySQL 索引优化

or:

EXPLAIN SELECT*FROM user WHERE status=1OR status=2;

一篇文章搞定 MySQL 索引优化

查询的CPU消耗:or>in>union。

6)用or分割开的条件,如果or前的条件中列有索引,而后面的列中没有索引,那么涉及到的索引都不会被用到。

EXPLAIN SELECT * FROM payment WHERE customer_id = 203 OR amount = 3.96;

一篇文章搞定 MySQL 索引优化

因为or后面的条件列中没有索引,那么后面的查询肯定要走全表扫描,在存在全表扫描的情况下,就没有必要多一次索引扫描增加IO访问。

7)负向条件查询不能使用索引,可以优化为in查询。

负向条件有:!=、<>、not in、not exists、not like等。

status列创建索引:

ALTER TABLE user ADD INDEX index_status (status);

一篇文章搞定 MySQL 索引优化

user表索引详情:

SHOW INDEX FROM user;

一篇文章搞定 MySQL 索引优化

负向条件不能命中缓存:

EXPLAIN SELECT * FROM user WHERE status !=1 AND status != 2;

一篇文章搞定 MySQL 索引优化

可以优化为in查询,但是前提是区分度要高,返回数据的比例在30%以内:

EXPLAIN SELECT * FROM user WHERE status IN (0,3,4);

一篇文章搞定 MySQL 索引优化

8)范围条件查询可以命中索引。范围条件有:<、<=、>、>=、between等。

status,age列分别创建索引:

ALTER TABLE user ADD INDEX index_status (status);

一篇文章搞定 MySQL 索引优化

ALTER TABLE user ADD INDEX index_age (age);

一篇文章搞定 MySQL 索引优化

user表索引详情:

SHOW INDEX FROM user;

一篇文章搞定 MySQL 索引优化

范围条件查询可以命中索引:

EXPLAIN SELECT * FROM user WHERE status>5;

一篇文章搞定 MySQL 索引优化

范围列可以用到索引(联合索引必须是最左前缀),但是范围列后面的列无法用到索引,索引最多用于一个范围列,如果查询条件中有两个范围列则无法全用到索引:

EXPLAIN SELECT * FROM user WHERE status>5 AND age<24;

如果是范围查询和等值查询同时存在,优先匹配等值查询列的索引:

EXPLAIN SELECT * FROM user WHERE status>5 AND age=24;

一篇文章搞定 MySQL 索引优化

8)数据库执行计算不会命中索引。

EXPLAIN SELECT * FROM user WHERE age>24;

一篇文章搞定 MySQL 索引优化

EXPLAIN SELECT * FROM user WHERE age+1>24;

一篇文章搞定 MySQL 索引优化

计算逻辑应该尽量放到业务层处理,节省数据库的CPU的同时最大限度的命中索引。

9)利用覆盖索引进行查询,避免回表。

被查询的列,数据能从索引中取得,而不用通过行定位符row-locator再到row上获取,即“被查询列要被所建的索引覆盖”,这能够加速查询速度。

user表的索引详情:

一篇文章搞定 MySQL 索引优化

因为status字段是索引列,所以直接从索引中就可以获取值,不必回表查询:

Using Index代表从索引中查询:

EXPLAIN SELECT status FROM user where status=1;

一篇文章搞定 MySQL 索引优化

当查询其他列时,就需要回表查询,这也是为什么要避免SELECT*的原因之一:

EXPLAIN SELECT * FROM user where status=1;

一篇文章搞定 MySQL 索引优化

10)建立索引的列,不允许为null。

单列索引不存null值,复合索引不存全为null的值,如果列允许为null,可能会得到“不符合预期”的结果集,所以,请使用not null约束以及默认值。

remark列建立索引:

ALTER TABLE user ADD INDEX index_remark (remark);

一篇文章搞定 MySQL 索引优化

IS NULL可以命中索引:

EXPLAIN SELECT * FROM user WHERE remark IS NULL;

一篇文章搞定 MySQL 索引优化

IS NOT NULL不能命中索引:

EXPLAIN SELECT * FROM user WHERE remark IS NOT NULL;

一篇文章搞定 MySQL 索引优化

虽然IS NULL可以命中索引,但是NULL本身就不是一种好的数据库设计,应该使用NOT NULL约束以及默认值。

a. 更新十分频繁的字段上不宜建立索引:因为更新操作会变更B+树,重建索引。这个过程是十分消耗数据库性能的。

b. 区分度不大的字段上不宜建立索引:类似于性别这种区分度不大的字段,建立索引的意义不大。因为不能有效过滤数据,性能和全表扫描相当。另外返回数据的比例在30%以外的情况下,优化器不会选择使用索引。

c. 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。虽然唯一索引会影响insert速度,但是对于查询的速度提升是非常明显的。另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,在并发的情况下,依然有脏数据产生。

d. 多表关联时,要保证关联字段上一定有索引。

e. 创建索引时避免以下错误观念:索引越多越好,认为一个查询就需要建一个索引;宁缺勿滥,认为索引会消耗空间、严重拖慢更新和新增速度;抵制唯一索引,认为业务的唯一性一律需要在应用层通过“先查后插”方式解决;过早优化,在不了解系统的情况下就开始优化。

3. 小结

对于自己编写的SQL查询语句,要尽量使用EXPLAIN命令分析一下,做一个对SQL性能有追求的程序员。衡量一个 程序员 是否靠谱,SQL能力是一个重要的指标。作为后端程序员,深以为然。

扫描下方二维码, 及时 获取更多 互联网求职面经javapython爬虫大数据 等技术,和 海量资料分享 :公众号后台回复“ csdn ”即可免费领取【csdn】和【百度文库】下载服务;公众号后台回复“ 资料 ”:即可领取 5T精品学习资料java面试考点java面经总结 ,以及 几十个 java 、大数据项目资料很全,你想找的几乎都有

一篇文章搞定 MySQL 索引优化

以上所述就是小编给大家介绍的《一篇文章搞定 MySQL 索引优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Two Scoops of Django

Two Scoops of Django

Daniel Greenfeld、Audrey M. Roy / CreateSpace Independent Publishing Platform / 2013-4-16 / USD 29.95

Two Scoops of Django: Best Practices For Django 1.5 is chock-full of material that will help you with your Django projects. We'll introduce you to various tips, tricks, patterns, code snippets, and......一起来看看 《Two Scoops of Django》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器