3.MySQL索引
2016-01-23 15:38
525 查看
索引(Index)是帮助MySQL高效获取数据的数据结构。是对数据库表中一列或多列的值进行排序的一种结构。索引是一个单独的、物理的数据库结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。(图书目录)
索引是存放在模式(schema)中的数据库对象,在数据字典中独立存放,必须从属于某个数据表,和数据表一样属于数据库对象。
优点:加速对表的查询。通过使用快速路径访问方法来快速定位数据,减少磁盘I/O。
缺点:动态维护索引耗费时间(表中记录增删改时),随数据量的增加而增加;占用一定的磁盘空间。
何时不适合索引:
1、创建索引
自动:当在表上定义主键约束、唯一约束、外键约束时,系统会为该数据列自动创建对应的索引。
手动: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 语句中指定 DESC(降序) 来将这些列按降序排列。ASC(升序)
CREATE INDEX idx_example ON table1 (col1 ASC, col2 DESC);
2、删除索引
自动:数据表删除时,表上索引自动删除。
手动:drop index……
语法格式:DROP INDEX index_name ON table_name;
Note:Oracle要求每个索引都有唯一的名字,so删除索引无需指定表名,MySQL只要求每个表内索引不重名,so必须指定表名。
3、索引分类
①普通索引
普通索引(关键字KEY、INDEX定义)的唯一任务是加快对数据的访问速度。
适用:最经常出现在查询条件(WHERE column = …)或排序条件(ORDER BY column)中的数据列。尽可能选择一个数据最整齐、最紧凑的数据列(如整数类型的数据列)来创建索引。
tips:允许被索引的数据列包含重复的值。
② 唯一索引
确定某数据列只包含彼此各不相同的值,在为这个数据列创建索引时就应该用关键字UNIQUE定义为唯一索引。
好处:一是简化了MySQL对这个索引的管理工作,这个索引也因此而变得更有效率;二是保证数据记录的唯一性(主要作用)。
③主索引
必须为主键字段创建一个索引(主索引)。主索引与唯一索引的唯一区别是:前者在定义时使用的关键字是 PRIMARY而不是UNIQUE。
④外键索引
如果为某个外键字段定义了一个外键约束条件,MySQL就会定义一个内部索引来帮助自己以最有效率的方式去管理和使用外键约束条件。
⑤ 复合索引
覆盖多个数据列,如像INDEX(columnA, columnB)索引。
优点:是MySQL可以有选择地使用索引。如查询操作只需要用到columnA数据列上的一个索引,就可以使用复合索引INDEX(columnA, columnB)。
缺点:仅适用于在复合索引中排列在前的数据列组合。比如说,INDEX(A, B, C)可以当做A或(A, B)的索引来使用,但不能当做B、C或(B, C)的索引来使用。(如果您知道姓氏,电话簿将非常有用,如果您知道名字和姓氏,电话簿则更为有用,但如果您只知道名字而不知道姓氏,电话簿将没有用处。)
Note:创建复合索引时,应该仔细考虑列的顺序。
⑥全文索引
普通索引只能加快对字段内容开头的字符进行检索操作。如果字段里存放的是由几个、甚至是多个单词构成的较大段文字,普通索引就没什么作用了。这种检索往往以LIKE %word%的形式出现,这对MySQL来说很复杂,如果需要处理的数据量很大,响应时间就会很长。
生成全文索引时,MySQL将把在文本中出现的所有单词创建为一份清单,查询操作将根据这份清单去检索有关的数据记录。
生成:①随数据表一同创建;②ALTER TABLE table_name ADD FULLTEXT (column1, column2)
CREATE TABLE student1 (
id INT auto_increment NOT NULL PRIMARY KEY,
uname VARCHAR(200),
mark VARCHAR(200),
age INT,
FULLTEXT (mark, uname, age)
);
作用:用SELECT检索包含一个或多个给定单词的数据记录:
SELECT *
FROM table_name
WHERE MATCH(column1, column2) AGAINST (
‘word1′,
‘word2′,
‘word3′
);
上面这条命令将把column1和column2字段里有word1、word2和word3的数据记录全部查询出来。
Note:
MySQL5.6.4 之后版本才支持InnoDB数据表实现全文索引。MyISAM一直支持。MySQL自带的全文索引只能对英文进行全文检索,目前无法对中文进行全文检索。如果需要对包含中文在内的文本数据进行全文检索,我们需要采用Sphinx(斯芬克斯)/Coreseek技术来处理中文。
⑦索引长度
在为CHAR和VARCHAR类型的数据列定义索引时,可以把索引的长度限制为一个给定的字符个数(这个数字必须小于该字段所允许的最大字符个数)。
优点:生成一个尺寸比较小、检索速度却较快的索引文件。在绝大多数应用里,数据库中的字符串数据大都以各种各样的名字为主,把索引的长度设置 为10~15个字符已经足以把搜索范围缩小到很少的几条数据记录了。
在为BLOB和TEXT类型的数据列创建索引时,必须对索引的长度做出限制;MySQL所允许的最大索引长度是255个字符。
4、索引实现原理
目前大部分数据库系统及文件系统都采用B-Tree或其变种B+
Tree作为索引结构。实际的数据库系统几乎没有使用二叉查找树或其进化品种红黑树(red-black tree)实现。MySQL(B+树)。
a)为什么使用B+树?
①B+树方便扫库,B树必须用中序遍历的方法按序扫库,而B+树直接从叶子结点挨个扫一遍就完了,B+树支持range-query(范围搜索)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。
比如要查 5-10之间的,B+树一把到5这个标记,再一把到10,然后串起来就行了,B树就非常麻烦。B树的好处,就是成功查询特别有利,因为树的高度总体要比B+树矮。不成功的情况下,B树也比B+树稍稍占一点点便宜。 B树比如你的例子中查,17的话,一把就得到结果了, 有很多基于频率的搜索是选用B树,越频繁query的结点越往根上走,前提是需要对query做统计,而且要对key做一些变化。
另外B树也好B+树也好,根或者上面几层因为被反复query,所以这几块基本都在内存中,不会出现读磁盘IO,一般已启动的时候,就会主动换入内存。(走进搜索引擎的作者梁斌老师)
理解:B+树好处在于要连续访问节点,如从1--10,是连续的,这取决于B+的存储结构,因为B+树的叶子结点都用链接指针连起来了,故而连续访问非常快
。而这是B树的弱点,它没有B+这样的存储特点,故而更适合单个查询,不论成不成功,都很快。故而是B+树用于数据库主要原因,数据库数据大多数在磁盘中(B与B+差不多),也经常涉及连续访问(B+)
②B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题,B+树只要遍历叶子节点就可以实现整棵树的遍历。
③内存中B+树是没有优势的,但是一到磁盘,B+树的威力就出来了。(Bucket Li)
原因:(参考:http://bbs.csdn.net/topics/390128306)
a.B+树在内存中会占据大量的空间,内存相对比较宝贵;
b.机械硬盘最慢的就是寻道,寻道之后读取数据的话,2个字节同4K个字节消耗的时间几乎没有区别。用B树可以减少树高,如果用二叉树,也许需要寻道20次,B数也许2次就够了。不过现在的SSD硬盘,不用寻道,B树就不一定强了。另外在普通机械硬盘上,顺序读写的速度要远远高于随机读写,针对于顺序写,LSM树效率更优。
c.外存的寻址速度和外存的读取速度差距比较大,举个例子:假设在外存读取4K的数据,其中寻址用了4ms,而真正读取的时间连1ms都不到,此时能提升速度的途径就是,尽量减少读写次数,每次多读多写,而B+树正是基于这种思想设计的数据结构。假设用平衡二叉树实现外存索引,在数据规模相同的情况下,在平衡二叉树中操作一个关键字需要对节点I/O的次数比B+树多很多,并且平衡二叉树节点比B+树节点小,违背了上述的途径,因此效率自然不如B+树。在外存索引中,B+树的效率和阶数在一定范围内是成正比的,超过这个范围就是反比了。
在MySQL中,索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的,本文主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式。
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索引实现(聚集索引)
虽然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)。本章讨论的高性能索引策略主要属于结构优化范畴。本章的内容完全基于上文的理论基础,实际上一旦理解了索引背后的机制,那么选择高性能的策略就变成了纯粹的推理,并且可以理解这些策略背后的逻辑。
最左前缀原理与相关优化
略……
索引选择性与前缀索引
既然索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好。
一般两种情况下不建议建索引:
表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了。至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。
索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:
Index Selectivity = Cardinality / #T
显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。例如,上文用到的employees.titles表,如果title字段经常被单独查询,是否需要建索引,我们看一下它的选择性:
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使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上。这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放,因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。
如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。如下图所示:
图13
这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很多开销在维护索引上。
如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:
图14
此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。
因此,只要可以,请尽量在InnoDB上采用自增字段做主键。
另外,MySQL索引及其优化涵盖范围非常广,本文只是涉及到其中一部分。如与排序(ORDER BY)相关的索引优化及覆盖索引(Covering index)的话题本文并未涉及,同时除B-Tree索引外MySQL还根据不同引擎支持的哈希索引、全文索引等等本文也并未涉及。如果有机会,希望再对本文未涉及的部分进行补充吧。
MySQL索引背后的数据结构及算法原理
参考资料:
mysql索引的类型和优缺点
由浅入深探究mysql索引结构原理、性能分析与优化
适合建索引?不适合建索引?分析
【暂未查阅】
索引是存放在模式(schema)中的数据库对象,在数据字典中独立存放,必须从属于某个数据表,和数据表一样属于数据库对象。
优点:加速对表的查询。通过使用快速路径访问方法来快速定位数据,减少磁盘I/O。
缺点:动态维护索引耗费时间(表中记录增删改时),随数据量的增加而增加;占用一定的磁盘空间。
何时不适合索引:
1、创建索引
自动:当在表上定义主键约束、唯一约束、外键约束时,系统会为该数据列自动创建对应的索引。
手动: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 语句中指定 DESC(降序) 来将这些列按降序排列。ASC(升序)
CREATE INDEX idx_example ON table1 (col1 ASC, col2 DESC);
2、删除索引
自动:数据表删除时,表上索引自动删除。
手动:drop index……
语法格式:DROP INDEX index_name ON table_name;
Note:Oracle要求每个索引都有唯一的名字,so删除索引无需指定表名,MySQL只要求每个表内索引不重名,so必须指定表名。
3、索引分类
①普通索引
普通索引(关键字KEY、INDEX定义)的唯一任务是加快对数据的访问速度。
适用:最经常出现在查询条件(WHERE column = …)或排序条件(ORDER BY column)中的数据列。尽可能选择一个数据最整齐、最紧凑的数据列(如整数类型的数据列)来创建索引。
tips:允许被索引的数据列包含重复的值。
② 唯一索引
确定某数据列只包含彼此各不相同的值,在为这个数据列创建索引时就应该用关键字UNIQUE定义为唯一索引。
好处:一是简化了MySQL对这个索引的管理工作,这个索引也因此而变得更有效率;二是保证数据记录的唯一性(主要作用)。
③主索引
必须为主键字段创建一个索引(主索引)。主索引与唯一索引的唯一区别是:前者在定义时使用的关键字是 PRIMARY而不是UNIQUE。
④外键索引
如果为某个外键字段定义了一个外键约束条件,MySQL就会定义一个内部索引来帮助自己以最有效率的方式去管理和使用外键约束条件。
⑤ 复合索引
覆盖多个数据列,如像INDEX(columnA, columnB)索引。
优点:是MySQL可以有选择地使用索引。如查询操作只需要用到columnA数据列上的一个索引,就可以使用复合索引INDEX(columnA, columnB)。
缺点:仅适用于在复合索引中排列在前的数据列组合。比如说,INDEX(A, B, C)可以当做A或(A, B)的索引来使用,但不能当做B、C或(B, C)的索引来使用。(如果您知道姓氏,电话簿将非常有用,如果您知道名字和姓氏,电话簿则更为有用,但如果您只知道名字而不知道姓氏,电话簿将没有用处。)
Note:创建复合索引时,应该仔细考虑列的顺序。
⑥全文索引
普通索引只能加快对字段内容开头的字符进行检索操作。如果字段里存放的是由几个、甚至是多个单词构成的较大段文字,普通索引就没什么作用了。这种检索往往以LIKE %word%的形式出现,这对MySQL来说很复杂,如果需要处理的数据量很大,响应时间就会很长。
生成全文索引时,MySQL将把在文本中出现的所有单词创建为一份清单,查询操作将根据这份清单去检索有关的数据记录。
生成:①随数据表一同创建;②ALTER TABLE table_name ADD FULLTEXT (column1, column2)
CREATE TABLE student1 (
id INT auto_increment NOT NULL PRIMARY KEY,
uname VARCHAR(200),
mark VARCHAR(200),
age INT,
FULLTEXT (mark, uname, age)
);
作用:用SELECT检索包含一个或多个给定单词的数据记录:
SELECT *
FROM table_name
WHERE MATCH(column1, column2) AGAINST (
‘word1′,
‘word2′,
‘word3′
);
上面这条命令将把column1和column2字段里有word1、word2和word3的数据记录全部查询出来。
Note:
MySQL5.6.4 之后版本才支持InnoDB数据表实现全文索引。MyISAM一直支持。MySQL自带的全文索引只能对英文进行全文检索,目前无法对中文进行全文检索。如果需要对包含中文在内的文本数据进行全文检索,我们需要采用Sphinx(斯芬克斯)/Coreseek技术来处理中文。
⑦索引长度
在为CHAR和VARCHAR类型的数据列定义索引时,可以把索引的长度限制为一个给定的字符个数(这个数字必须小于该字段所允许的最大字符个数)。
优点:生成一个尺寸比较小、检索速度却较快的索引文件。在绝大多数应用里,数据库中的字符串数据大都以各种各样的名字为主,把索引的长度设置 为10~15个字符已经足以把搜索范围缩小到很少的几条数据记录了。
在为BLOB和TEXT类型的数据列创建索引时,必须对索引的长度做出限制;MySQL所允许的最大索引长度是255个字符。
4、索引实现原理
目前大部分数据库系统及文件系统都采用B-Tree或其变种B+
Tree作为索引结构。实际的数据库系统几乎没有使用二叉查找树或其进化品种红黑树(red-black tree)实现。MySQL(B+树)。
a)为什么使用B+树?
①B+树方便扫库,B树必须用中序遍历的方法按序扫库,而B+树直接从叶子结点挨个扫一遍就完了,B+树支持range-query(范围搜索)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。
比如要查 5-10之间的,B+树一把到5这个标记,再一把到10,然后串起来就行了,B树就非常麻烦。B树的好处,就是成功查询特别有利,因为树的高度总体要比B+树矮。不成功的情况下,B树也比B+树稍稍占一点点便宜。 B树比如你的例子中查,17的话,一把就得到结果了, 有很多基于频率的搜索是选用B树,越频繁query的结点越往根上走,前提是需要对query做统计,而且要对key做一些变化。
另外B树也好B+树也好,根或者上面几层因为被反复query,所以这几块基本都在内存中,不会出现读磁盘IO,一般已启动的时候,就会主动换入内存。(走进搜索引擎的作者梁斌老师)
理解:B+树好处在于要连续访问节点,如从1--10,是连续的,这取决于B+的存储结构,因为B+树的叶子结点都用链接指针连起来了,故而连续访问非常快
。而这是B树的弱点,它没有B+这样的存储特点,故而更适合单个查询,不论成不成功,都很快。故而是B+树用于数据库主要原因,数据库数据大多数在磁盘中(B与B+差不多),也经常涉及连续访问(B+)
②B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题,B+树只要遍历叶子节点就可以实现整棵树的遍历。
③内存中B+树是没有优势的,但是一到磁盘,B+树的威力就出来了。(Bucket Li)
原因:(参考:http://bbs.csdn.net/topics/390128306)
a.B+树在内存中会占据大量的空间,内存相对比较宝贵;
b.机械硬盘最慢的就是寻道,寻道之后读取数据的话,2个字节同4K个字节消耗的时间几乎没有区别。用B树可以减少树高,如果用二叉树,也许需要寻道20次,B数也许2次就够了。不过现在的SSD硬盘,不用寻道,B树就不一定强了。另外在普通机械硬盘上,顺序读写的速度要远远高于随机读写,针对于顺序写,LSM树效率更优。
c.外存的寻址速度和外存的读取速度差距比较大,举个例子:假设在外存读取4K的数据,其中寻址用了4ms,而真正读取的时间连1ms都不到,此时能提升速度的途径就是,尽量减少读写次数,每次多读多写,而B+树正是基于这种思想设计的数据结构。假设用平衡二叉树实现外存索引,在数据规模相同的情况下,在平衡二叉树中操作一个关键字需要对节点I/O的次数比B+树多很多,并且平衡二叉树节点比B+树节点小,违背了上述的途径,因此效率自然不如B+树。在外存索引中,B+树的效率和阶数在一定范围内是成正比的,超过这个范围就是反比了。
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的数据文件本身就是索引文件。从上文知道,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)。本章讨论的高性能索引策略主要属于结构优化范畴。本章的内容完全基于上文的理论基础,实际上一旦理解了索引背后的机制,那么选择高性能的策略就变成了纯粹的推理,并且可以理解这些策略背后的逻辑。最左前缀原理与相关优化
略……
索引选择性与前缀索引
既然索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好。
一般两种情况下不建议建索引:
表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描就好了。至于多少条记录才算多,这个个人有个人的看法,我个人的经验是以2000作为分界线,记录数不超过 2000可以考虑不建索引,超过2000条可以酌情考虑索引。
索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:
Index Selectivity = Cardinality / #T
显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。例如,上文用到的employees.titles表,如果title字段经常被单独查询,是否需要建索引,我们看一下它的选择性:
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还根据不同引擎支持的哈希索引、全文索引等等本文也并未涉及。如果有机会,希望再对本文未涉及的部分进行补充吧。
MySQL索引背后的数据结构及算法原理
参考资料:
mysql索引的类型和优缺点
由浅入深探究mysql索引结构原理、性能分析与优化
适合建索引?不适合建索引?分析
【暂未查阅】
欢迎个人转载,但须在文章页面明显位置给出原文连接; 未经作者同意必须保留此段声明、不得随意修改原文、不得用于商业用途,否则保留追究法律责任的权利。 【 CSDN 】:csdn.zxiaofan.com 【GitHub】:github.zxiaofan.com 如有任何问题,欢迎留言。祝君好运! Life is all about choices! 将来的你一定会感激现在拼命的自己!
相关文章推荐
- 1.5 MySql建表
- MySQL数据库读写分离
- Mysql存储引擎
- mysql转换引擎的方法
- Mysql读书笔记
- [Mysql for Excel指南] 第三章 配置
- 解决xampp mysql不能启动 问题
- mysql中declare语句用法
- mysql游标使用
- mysql 压缩版安装
- MySQL特殊函数
- windows mysql 解压版 data文件初始化
- 4.Mysql驱动
- mysql下载镜像收集
- URLEncoder Function for MySQL(mysql urlencode 支持中文)
- Mysql基本操作语句
- mysql优化重要参数整理(初级篇)
- Ubuntu 14.04数据库服务器--mysql的安装和配置
- [实战]MVC5+EF6+MySql企业网盘实战(28)——其他列表
- Mysql的游标的定义使用及关闭深入分析