MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
数据库查询是数据库的最主要功能之一,咱们都但愿查询数据的速度能尽量的快,所以数据库系统的设计者会从查询算法的角度进行优化,这篇文章对索引作一个系统的梳理,但愿对你们有帮助。html
索引的分类能够从多个角度进行,下面分别从数据结构,物理存储和业务逻辑三个维度进行划分。node
关于B+树索引,后面会深刻解析mysql
如今MyISAM和InnoDB引擎都支持了算法
用于对GIS数据类型建立SPATIAL索引sql
索引是经过二叉树的数据结构来描述的,咱们能够这么理解聚簇索引:索引的叶节点就是数据节点。而非聚簇索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。数据库
非汇集索引索引项顺序存储,但索引项对应的内容倒是随机存储的;服务器
举个例子说明下:数据结构
create table student ( `id` INT UNSIGNED AUTO_INCREMENT, `name` VARCHAR(255), PRIMARY KEY(`id`), KEY(`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
该表中主键id是该表的汇集索引、name为非汇集索引;表中的每行数据都是按照汇集索引id排序存储的;好比要查找name='Arla'和name='Arle'的两个同窗,他们在name索引表中位置多是相邻的,可是实际存储位置可能差的很远。name索引表节点按照name排序,检索的是每一行数据的主键。汇集索引表按照主键id排序,检索的是每一行数据的真实内容。数据结构和算法
主键索引是一种特殊的惟一索引,不容许有空值性能
复合索引指多个字段上建立的索引,只有在查询条件中使用了建立索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合
空间索引是对空间数据类型的字段创建的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。
MYSQL使用SPATIAL关键字进行扩展,使得可以用于建立正规索引类型的语法建立空间索引。建立空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中建立.
CREATE TABLE table_name[col_name data type] [unique|fulltext|spatial][index|key][index_name](col_name[length])[asc|desc]
create table table_name( id int(11), name varchar(20), sex boolean, INDEX(id) );
查看表结构
show create table table_name;
可使 EXPLAIN 语句查看索引是否被使用
explain select * from table_name where id = 1\G
create table index2( id int unique, name varchar(20), unique INDEX index_2(id asc) );
全文索引只能在char,varchar或者text 类型的字段上。并且,只有MyISAM 储存引擎支持全文索引。
create table idnex3( id int, info varchar(20), FULLTEXT INDEX index3_info(info) )ENGINE=MyISAM;
create table index4( id int, subject varchar(255), index index4_st(subject(10)) );
这里须要注意的,subject 的长度为255,可是index4_st索引只有10。这样作的目的仍是为了提升查询速度。对于字符型的数据,能够不用查询所有信息,只查询其前面的若干字符信息。
create table index5( id int, name varchar(20), sex char(4), index index5_ns(name.sex) );
这是咱们能够看到,name 和sex字段上已经建立了index_ns索引。
在example0() 表中的id 建立名为index7_id 的索引。
create index index7_id on example0(id);
create UNIQUE index index_name on table_name(name);
create FULLTEXT index index_name on table_name(info);
create INDEX index_name ON table_name(name(10));
create INDEX index_name ON table_name(name,sex);
在name字段上建立名为indx_name 的索引
alter table table_name ADD INDEX index_name(name(20));
alter table table_name ADD UNIQUE INDEX index_name(id);
alter table table_name ADD FULLTEXT INDEX index_name(info);
alter table table_name ADD INDEX index_name(name(4));
alter tabel table_name ADD INDEX index_name(name.sex);
DROP INDEX index_name ON table_name;
目前大部分数据库系统及文件系统都采用B-Tree或其变种B+Tree做为索引结构,那么索引树是如何维护的?
查找是数据结构和算法中一个很是重要的概念。
B-Tree是一种多路搜索树(并非二叉的):
B-树的搜索,从根结点开始,对结点内的关键字(有序)序列进行二分查找,若是命中则结束,不然进入查询关键字所属范围的儿子结点;重复,直到所对应的儿子指针为空,或已是叶子结点;B-Tree上查找算法的伪代码以下:
BTree_Search(node, key) { if(node == null) return null; foreach(node.key) { if(node.key[i] == key) return node.data[i]; if(node.key[i] > key) return BTree_Search(point[i]->node); } return BTree_Search(point[i+1]->node); } data = BTree_Search(root, my_key);
B树中每个内部节点会包含必定数量的键值。一般,键值的数量被选定在d和2d之间。在实际中,键值占用了节点中大部分的空间。因数2将保证节点能够被拆分或组合。若是一个内部节点有2d个键值,那么添加一个键值给此节点的过程,将会拆分2d键值为2个d键值的节点,并把此键值添加给父节点。每个拆分的节点须要最小数目的键值。类似地,若是一个内部节点和他的邻居二者都有d个键值,那么将经过它与邻居的合并来删除一个键值。删除此键值将致使此节点拥有d-1个键值;与邻居的合并则加上d个键值,再加上从邻居节点的父节点移来的一个键值。结果为彻底填充的2d个键值。
下面是往B树中依次插入
6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4
B-Tree有许多变种,其中最多见的是B+Tree,MySQL就广泛使用B+Tree实现其索引结构。
与B-Tree相比,B+Tree有如下不一样点:
B+的搜索与B-树也基本相同,区别是B+树只有达到叶子结点才命中(B-树能够在非叶子结点命中),其性能也等价于在关键字全集作一次二分查找;
下面是往B+树中依次插入
6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4
通常来讲,索引自己也很大,不可能所有存储在内存中,所以索引每每以索引文件的形式存储的磁盘上。
这样的话,索引查找过程当中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,因此评价一个数据结构做为索引的优劣最重要的指标就是在查找过程当中磁盘I/O操做次数的渐进复杂度。换句话说,索引的结构组织要尽可能减小查找过程当中磁盘I/O的存取次数。
假如每一个盘块能够正好存放一个B树的结点(正好存放2个文件名)。那么一个BTNODE结点就表明一个盘块,而子树指针就是存放另一个盘块的地址。
下面,我们来模拟下查找文件29的过程:
B+-tree的内部结点并无指向关键字具体信息的指针。所以其内部结点相对B 树更小。若是把全部同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的须要查找的关键字也就越多。相对来讲IO读写次数也就下降了。
举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶B-tree(一个结点最多8个关键字)的内部结点须要2个盘快。而B+
树内部结点只须要1个盘快。当须要把内部结点读入内存中的时候,B 树就比B+树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。
因为非终结点并非最终指向文件内容的结点,而只是叶子结点中关键字的索引。因此任何关键字的查找必须走一条从根结点到叶子结点的路。全部关键字查询的路径长度相同,致使每个数据的查询效率至关。
索引查询是数据库中重要的记录查询方法,要不要进入索引以及在那些字段上创建索引都要和实际数据库系统的查询要求结合来考虑,下面给出实际中的一些通用的原则:
一个单独的索引扫描只能用于这样的条件子句:使用被索引字段和索引操做符类中的操做符, 而且这些条件以AND链接。
假设在(a, b)上有一个索引, 那么相似WHERE a = 5 AND b = 6的条件可使用索引,可是像WHERE a = 5 OR b = 6的条件就不能直接使用索引。
一个相似WHERE x =42 OR x = 47 OR x = 53 OR x = 99 这样的查询能够分解成四个在x上的独立扫描,每一个扫描使用一个条件, 最后将这些扫描的结果OR 在一块儿,生成最终结果。
另一个例子是,若是咱们在x 和y上有独立的索引,一个相似WHERE x = 5 AND y = 6 这样的查询能够分解为几个使用独立索引的子句,而后把这几个结果AND 在一块儿,生成最终结果。
联合索引又叫复合索引。两个或更多个列上的索引被称做复合索引。
对于复合索引:Mysql从左到右的使用索引中的字段,一个查询能够只使用索引中的一部份,但只能是最左侧部分。例如索引是key index (a,b,c)。 能够支持a | a,b| a,b,c 3种组合进行查找,但不支持 b,c进行查找 .当最左侧字段是常量引用时,索引就十分有效。
因此说建立复合索引时,应该仔细考虑列的顺序。对索引中的全部列执行搜索或仅对前几列执行搜索时,复合索引很是有用;仅对后面的任意列执行搜索时,复合索引则没有用处。
这里记录两种方式,分别是
show status like ‘Handler_read%';
你们能够注意:
+-----------------------+--------------+ | Variable_name | Value | +-----------------------+--------------+ | Handler_read_first | 153 | | Handler_read_key | 364 | | Handler_read_next | 425 | | Handler_read_prev | 598 | | Handler_read_rnd | 605 | | Handler_read_rnd_next | 860571 | +-----------------------+--------------+ 6 rows in set (0.00 sec) ————————————————
分析这几个值,咱们能够查看当前索引的使用状况:
查询 schema_unused_indexes库。
root@localhost [sys]>select * from schema_unused_indexes; +-------------------+-------------+------------+ | object_schema | object_name | index_name | +-------------------+-------------+------------+ | sysbench_testdata | sbtest1 | k_1 | | sysbench_testdata | sbtest10 | k_10 | | sysbench_testdata | sbtest3 | k_3 | | sysbench_testdata | sbtest4 | k_4 | | sysbench_testdata | sbtest5 | k_5 | | sysbench_testdata | sbtest6 | k_6 | | sysbench_testdata | sbtest7 | k_7 | | sysbench_testdata | sbtest8 | k_8 | | sysbench_testdata | sbtest9 | k_9 | +-------------------+-------------+------------+ 9 rows in set (0.00 sec)
explain显示了mysql如何使用索引来处理select语句以及链接表。能够帮助选择更好的索引和写出更优化的查询语句。
新建一张表,
CREATE TABLE IF NOT EXISTS `article` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `author_id` int(10) unsigned NOT NULL, `category_id` int(10) unsigned NOT NULL, `views` int(10) unsigned NOT NULL, `comments` int(10) unsigned NOT NULL, `title` varbinary(255) NOT NULL, `content` text NOT NULL, PRIMARY KEY (`id`) );
执行查询,
EXPLAIN SELECT author_id FROM `article` WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1
响应数据以下,
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: article type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 3 Extra: Using where; Using filesort 1 row in set (0.00 sec)
type 是 ALL,即最坏的状况。Extra 里还出现了 Using filesort,也是最坏的状况。
MySQL 在表里找到所需行的方式。包括(由左至右,由最差到最好):
| All | index | range | ref | eq_ref | const,system | null |