- 优点:加速对表的查询。通过使用快速路径访问方法来快速定位数据,减少磁盘I/O。
- 缺点:动态维护索引耗费时间(表中记录增删改时),随数据量的增加而增加;占用一定的磁盘空间。
- 何时不适合索引:
- 自动:当在表上定义主键约束、唯一约束、外键约束时,系统会为该数据列自动创建对应的索引。
- 手动:create index……
- 语法格式:CREATE INDEX index_name ON table_name(column,column……);
CREATE INDEX index_name ON student (age); #提高基于age字段的查询速度
CREATE INDEX index_name ON student (age,grade); #为age、grade两列同时建立名为index_name的索引
CREATE INDEX idx_example ON table1 (col1 ASC, col2 DESC);
- 自动:数据表删除时,表上索引自动删除。
- 手动:drop index……
- 语法格式:DROP INDEX index_name ON table_name;
- Note:Oracle要求每个索引都有唯一的名字,so删除索引无需指定表名,MySQL只要求每个表内索引不重名,so必须指定表名。
CREATE TABLE student1 (
id INT auto_increment NOT NULL PRIMARY KEY,
uname VARCHAR(200),
mark VARCHAR(200),
age INT,
FULLTEXT (mark, uname, age)
);
SELECT *
FROM table_name
WHERE MATCH(column1, column2) AGAINST (
‘word1′,
‘word2′,
‘word3′
);
MySQL索引实现
在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,本文主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式。
MyISAM索引实现(非聚集)
MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图:
图8
这里设表一共有三列,假设我们以Col1为主键,则图8是一个MyISAM表的主索引(Primary key)示意。
- MyISAM的索引文件仅仅保存数据记录的地址。
- MyISAM的主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引,则此索引的结构如下图所示:
图9
同样也是一颗B+Tree,data域保存数据记录的地址。因此,MyISAM中索引检索的算法为:
- 首先按照B+Tree搜索算法搜索索引;
- 如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。
MyISAM的索引方式也叫做“非聚集”的,之所以这么称呼是为了与InnoDB的聚集索引区分。
虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。区别:
- InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
图10
图10是InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
- 与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。例如,图11为定义在Col3上的一个辅助索引:
图11
这里以英文字符的ASCII码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助。
- InnoDB索引,不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。
- InnoDB中不建议用非单调的字段作为主键,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而建议用自增字段作为主键。
索引使用策略及优化
MySQL的优化主要分为结构优化(Scheme optimization)和查询优化(Query optimization)。本章讨论的高性能索引策略主要属于结构优化范畴。本章的内容完全基于上文的理论基础,实际上一旦理解了索引背后的机制,那么选择高性能的策略就变成了纯粹的推理,并且可以理解这些策略背后的逻辑。
- 表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了。至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。
- 索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:
- SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
- +-------------+
- |Selectivity|
- +-------------+
- |0.0000|
- +-------------+
title的选择性不足0.0001(精确值为0.00001579),所以实在没有什么必要为其单独建索引。
有一种与索引选择性有关的索引优化策略叫做前缀索引,就是用列的前缀代替整个列作为索引key,当前缀长度合适时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销。下面以employees.employees表为例介绍前缀索引的选择和使用。
从图12可以看到employees表只有一个索引<emp_no>,那么如果我们想按名字搜索一个人,就只能全表扫描了:
- EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';
- +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
- | id | select_type | table | type | possible_keys | key | key_len |ref| rows |Extra|
- +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
- |1| SIMPLE | employees | ALL | NULL | NULL | NULL | NULL |300024|Usingwhere|
- +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
如果频繁按名字搜索员工,这样显然效率很低,因此我们可以考虑建索引。有两种选择,建<first_name>或<first_name, last_name>,看下两个索引的选择性:
- SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees;
- +-------------+
- |Selectivity|
- +-------------+
- |0.0042|
- +-------------+
- SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees;
- +-------------+
- |Selectivity|
- +-------------+
- |0.9313|
- +-------------+
<first_name>显然选择性太低,<first_name, last_name>选择性很好,但是first_name和last_name加起来长度为30,有没有兼顾长度和选择性的办法?可以考虑用first_name和last_name的前几个字符建立索引,例如<first_name, left(last_name, 3)>,看看其选择性:
- SELECT count(DISTINCT(concat(first_name, left(last_name,3))))/count(*) AS Selectivity FROM employees.employees;
- +-------------+
- |Selectivity|
- +-------------+
- |0.7879|
- +-------------+
选择性还不错,但离0.9313还是有点距离,那么把last_name前缀加到4:
- SELECT count(DISTINCT(concat(first_name, left(last_name,4))))/count(*) AS Selectivity FROM employees.employees;
- +-------------+
- |Selectivity|
- +-------------+
- |0.9007|
- +-------------+
这时选择性已经很理想了,而这个索引的长度只有18,比<first_name, last_name>短了接近一半,我们把这个前缀索引 建上:
- ALTER TABLE employees.employees
- ADD INDEX `first_name_last_name4`(first_name, last_name(4));
此时再执行一遍按名字查询,比较分析一下与建索引前的结果:
- SHOW PROFILES;
- +----------+------------+---------------------------------------------------------------------------------+
- |Query_ID|Duration|Query|
- +----------+------------+---------------------------------------------------------------------------------+
- |87|0.11941700| SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'|
- |90|0.00092400| SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'|
- +----------+------------+---------------------------------------------------------------------------------+
性能的提升是显著的,查询速度提高了120多倍。
前缀索引兼顾索引大小和查询速度,但是其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于Covering index(即当索引本身包含查询所需全部数据时,不再访问数据文件本身)。
InnoDB的主键选择与插入优化
在使用InnoDB存储引擎时,如果没有特别的需要,请永远使用一个与业务无关的【自增字段】作为主键。
经常看到有帖子或博客讨论主键选择问题,有人建议使用业务无关的自增主键,有人觉得没有必要,完全可以使用如学号或身份证号这种唯一字段作为主键。不论支持哪种论点,大多数论据都是业务层面的。如果从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对是一个糟糕的主意。
上文讨论过InnoDB的索引实现,InnoDB使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。
如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。如下图所示:
图13
这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很多开销在维护索引上。
如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:
图14
此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。
因此,只要可以,请尽量在InnoDB上采用自增字段做主键。
另外,MySQL索引及其优化涵盖范围非常广,本文只是涉及到其中一部分。如与排序(ORDER BY)相关的索引优化及覆盖索引(Covering index)的话题本文并未涉及,同时除B-Tree索引外MySQL还根据不同引擎支持的哈希索引、全文索引等等本文也并未涉及。如果有机会,希望再对本文未涉及的部分进行补充吧。
欢迎个人转载,但须在文章页面明显位置给出原文连接;
未经作者同意必须保留此段声明、不得随意修改原文、不得用于商业用途,否则保留追究法律责任的权利。
【 CSDN 】:csdn.zxiaofan.com
【GitHub】:github.zxiaofan.com
如有任何问题,欢迎留言。祝君好运!
Life is all about choices!
将来的你一定会感激现在拼命的自己!