用来加快查询的技术很多,其中最重要的是索引。通常索引能够快速提高查询速度。如果不适用索引,MYSQL必须从第一条记录开始然后读完整个表直到找出相关的行。表越大,花费的时间越多。但也不全是这样。本文讨论索引是什么以及如何使用索引来改善性能,以及索引可能降低性能的情况。
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。
数据库查询是数据库的最主要功能之一。我们都希望查询数据的速度能尽可能的快,因此数据库系统的设计者会从查询算法的角度进行优化。最基本的查询算法当然是顺序查找(linear search),这种复杂度为O(n)的算法在数据量很大时显然是糟糕的,好在计算机科学的发展提供了很多更优秀的查找算法,例如二分查找(binary search)、二叉树查找(binary tree search)等。如果稍微分析一下会发现,每种查找算法都只能应用于特定的数据结构之上,例如二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织),所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
索引是在MYSQL的存储引擎层中实现的,而不是在服务层实现的。所以每种存储引擎的索引都不一定完全相同,也不是所有的存储引擎都支持所有的索引类型。MYSQL目前提供了一下4种索引。
B-Tree 索引:最常见的索引类型,大部分引擎都支持B树索引。HASH 索引:只有Memory引擎支持,使用场景简单。R-Tree 索引(空间索引):空间索引是MyISAM的一种特殊索引类型,主要用于地理空间数据类型。Full-text (全文索引):全文索引也是MyISAM的一种特殊索引类型,主要用于全文索引,InnoDB从MYSQL5.6版本提供对全文索引的支持。Mysql目前不支持函数索引,但是能对列的前面某一部分进行索引,例如标题title字段,可以只取title的前10个字符进行索引,这个特性可以大大缩小索引文件的大小,但前缀索引也有缺点,在排序Order By和分组Group By 操作的时候无法使用。用户在设计表结构的时候也可以对文本列根据此特性进行灵活设计。语法:create index idx_title on film (title(10))
MyISAM、InnoDB引擎、Memory三个常用引擎类型比较
索引MyISAM引擎InnoDB引擎Memory引擎B-Tree 索引支持支持支持HASH 索引不支持不支持支持R-Tree 索引支持不支持不支持Full-text 索引不支持暂不支持不支持在执行CREATE TABLE语句时可以创建索引,也可以单独用CREATE INDEX或ALTER TABLE来为表增加索引。
1.ALTER TABLE - ALTER TABLE用来创建普通索引、UNIQUE索引或PRIMARY KEY索引。
ALTER TABLE table_name ADD INDEX index_name (column_list)ALTER TABLE table_name ADD UNIQUE (column_list)ALTER TABLE table_name ADD PRIMARY KEY (column_list)2.CREATE INDEX - CREATE INDEX可对表增加普通索引或UNIQUE索引。
CREATE INDEX index_name ON table_name (column_list)CREATE UNIQUE INDEX index_name ON table_name (column_list)可利用ALTER TABLE或DROP INDEX语句来删除索引。类似于CREATE INDEX语句,DROP INDEX可以在ALTER TABLE内部作为一条语句处理,语法如下。
DROP INDEX index_name ON talbe_nameALTER TABLE table_name DROP INDEX index_nameALTER TABLE table_name DROP PRIMARY KEY其中,前两条语句是等价的,删除掉table_name中的索引index_name。 第3条语句只在删除PRIMARY KEY索引时使用,因为一个表只可能有一个PRIMARY KEY索引,因此不需要指定索引名。如果没有创建PRIMARY KEY索引,但表具有一个或多个UNIQUE索引,则MySQL将删除第一个UNIQUE索引。
如果从表中删除了某列,则索引会受到影响。对于多列组合的索引,如果删除其中的某列,则该列也会从索引中删除。如果删除组成索引的所有列,则整个索引将被删除。
1. 较频繁的作为查询条件的字段应该创建索引2. 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件3. 更新非常频繁的字段不适合创建索引
当然,并不是存在更新的字段就适合创建索引,从判定策略的用语上也可以看出,是"非常频繁"的字段。到底什么样的更新频率应该算是"非常频繁"呢?每秒?每分钟?还是每小时呢?说实话,还真难定义。很多时候是通过比较同一时间段内被更新的次数和利用该字段作为条件的查询次数来判断的,如果通过该字段的查询并不是很多,可能几个小时或是更长才会执行一次,更新反而比查询更频繁,那这样的字段肯定不适合创建索引。反之,如果我们通过该字段的查询比较频繁,但更新并不是特别多,比如查询几十次或更多才可能会产生一次更新,那我个人觉得更新所带来的附加成本也是可以接受的。
4. 不会出现在 WHERE 子句中的字段不该创建索引
使用短索引,如果对字符串列进行索引,应该指定一个前缀长度,可节省大量索引空间,提升查询速度;
例如,有一个CHAR(200)列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。对前10个或者20个字符进行索引能够节省大量索引空间,也可能会使查询更快。较小的索引涉及的磁盘IO较少,较短的值比较起来更快。更为重要的是,对于较短的键值,所以高速缓存中的快能容纳更多的键值,因此,MYSQL也可以在内存中容纳更多的值。这样就增加了找到行而不用读取索引中较多快的可能性。
利用最左前缀
既然索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,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),所以实在没有什么必要为其单独建索引。
MySQL只对一下操作符才使用索引:<,<=,=,>,>=,between,in, 以及某些时候的like(不以通配符%或_开头的情形)。
不要过度索引,只保持所需的索引。每个额外的索引都要占用额外的磁盘空间,并降低写操作的性能。 在修改表的内容时,索引必须进行更新,有时可能需要重构,因此,索引越多,所花的时间越长。
索引的益处已经清楚了,但是我们不能只看到这些益处,并认为索引是解决查询优化的圣经,只要发现 查询运行不够快就将 WHERE 子句中的条件全部放在索引中。
确实,索引能够极大地提高数据检索效率,也能够改善排序分组操作的性能,但有不能忽略的一个问题就是索引是完全独立于基础数据之外的一部分数据。假设在Table ta 中的Column ca 创建了索引 idx_ta_ca,那么任何更新 Column ca 的操作,MySQL在更新表中 Column ca的同时,都须要更新Column ca 的索引数据,调整因为更新带来键值变化的索引信息。而如果没有对 Column ca 进行索引,MySQL要做的仅仅是更新表中 Column ca 的信息。这样,最明显的资源消耗就是增加了更新所带来的 IO 量和调整索引所致的计算量。此外,Column ca 的索引idx_ta_ca须要占用存储空间,而且随着 Table ta 数据量的增加,idx_ta_ca 所占用的空间也会不断增加,所以索引还会带来存储空间资源消耗的增加。
结果:
[SQL]SELECT * FROM `demo1` WHERE `name` = 'A'; 受影响的行: 0 时间: 0.003s这是最基本的索引,它没有任何限制。它有以下几种创建方式:
CREATE INDEX index_name ON demo1(`name`) 查看表结构 CREATE TABLE `demo1` ( `ID` int(11) NOT NULL, `name` varchar(16) NOT NULL, KEY `index_name` (`name`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; 查询 SELECT * FROM demo1 WHERE name = 'A' 结果: [SQL]SELECT * FROM demo1 WHERE name = 'A' 受影响的行: 0 时间: 0.023s会看到比上一次查询快了 注意 在创建索引的时候,如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length。
添加索引的方式: 修改表结构(添加索引) ALTER table demo1 ADD INDEX index_name(name) 删除索引 DROP INDEX [indexName] ON mytable;注意:索引列的值必须唯一,但允许有空值。
创建索引 CREATE UNIQUE INDEX nickname ON demo1(nickname) 有四种方式来添加数据表的索引: ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。 ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。 ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。 ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。 查看表结构 CREATE TABLE `demo1` ( `ID` int(11) NOT NULL, `name` varchar(16) NOT NULL, `nickname` varchar(255) DEFAULT NULL, UNIQUE KEY `nickname` (`nickname`), KEY `index_name` (`name`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1; 查询: -- 未加索引之前 SELECT * FROM demo1 WHERE nickname = 'd' -- 查询结果: [SQL]SELECT * FROM demo1 WHERE nickname = 'd' --受影响的行: 0 时间: 0.002s -- 加索引之后查询结果: [SQL]SELECT * FROM demo1 WHERE nickname = 'd' --受影响的行: 0 时间: 0.001s组合索引就是在多个字段上创建一个索引
添加索引 ALTER TABLE `demo1` ADD INDEX MultiIdx1(`ID`, `age`) 查询: -- 未加索引之前 SELECT * FROM demo1 WHERE id <9 AND age <10 -- 查询结果: [SQL]SELECT * FROM demo1 WHERE id <9 AND age <10 --受影响的行: 0 --时间: 0.002s -- 加索引以后查询结果: [SQL]SELECT * FROM demo1 WHERE id <9 AND age <10 --受影响的行: 0 --时间: 0.001s注意:
组合索引的索引文件以B-Tree格式保存,在创建组合索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。组合索引的第一个字段必须出现在查询组句中,这个索引才会被用到。 组合索引就是遵从了最左前缀,利用索引中最左边的列集来匹配行,这样的列集称为最左前缀,不明白没关系,举几个例子就明白了,例如,这里由id、age 2个字段构成的索引,索引行中就按id/age的顺序存放,索引可以索引下面字段组合(id,age)、或者(id)。如果要查询的字段不构成索引最左面的前缀,那么就不会是用索引,比如,age 就不会使用索引查询例如
-- 如果有一个组合索引(col_a,col_b,col_c),下面的情况都会用到这个索引: col_a = "some value"; col_a = "some value" and col_b = "some value"; col_a = "some value" and col_b = "some value" and col_c = "some value"; col_b = "some value" and col_a = "some value" and col_c = "some value"; -- 不会用到索引 col_b = "aaaaaa"; col_b = "aaaa" and col_c = "cccccc";注意
旧版的MySQL的全文索引只能用在MyISAM表格的char、varchar和text的字段上。 不过新版的MySQL5.6.24上InnoDB引擎也加入了全文索引,所以具体信息要随时关注官网. 字段类型:char、varchar和text 添加索引: ALTER TABLE `demo1` ADD FULLTEXT (`name`); 查询: -- 通常未加全文索引前,检索某个名字 我们一般都是模糊查询 SELECT * FROM demo1 WHERE `name` like 'A%' -- 运行结果: [SQL]SELECT * FROM demo1 WHERE `name` like 'A' --受影响的行: 0 --时间: 0.003s -- 添加全文索引以后 SELECT * FROM `demo1` WHERE MATCH(`name`) AGAINST('thisis') -- 运行结果 [SQL]SELECT * FROM `demo1` WHERE MATCH(`name`) AGAINST('thiss') --受影响的行: 0 --时间: 0.001s注意
mysql指定了最小字符长度,默认是4,必须要匹配大于4的才会有返回结果,也就是 AGAINST() 里面的字符比如是大于4的 不然就gameover了 可以用SHOW VARIABLES LIKE ‘ft_min_word_len’ 来查看指定的字符长度,也可以在mysql配置文件my.ini 更改最小字符长度,方法是在my.ini 增加一行 比如:ft_min_word_len = 2,改完后重启mysql即可。mysql在集和查询中的对每个合适的词都会先计算它们的权重,一个出现在多个文档中的词将有较低的权重(可能甚至有一个零权重),因为在这个特定的集中,它有较低的语义值。否则,如果词是较少的,它将得到一个较高的权重,mysql默认的阀值是50%,上面‘you’在每个文档都出现,因此是100%,只有低于50%的才会出现在结果集中。
上面通过IN BOOLEAN MODE指定全文检索模式为布尔全文检索。MySQL还提供了一些类似我们平时使用搜索引擎时用到的的语法:逻辑与、逻辑或、逻辑非等。具体通过几个SQL语句例子来说明:
SELECT * FROM demo1 WHERE MATCH (name) AGAINST ('+apple -banana' IN BOOLEAN MODE); -- + 表示AND,即必须包含。- 表示NOT,即不包含。 SELECT * FROM demo1 WHERE MATCH (name) AGAINST ('apple banana' IN BOOLEAN MODE); -- apple和banana之间是空格,空格表示OR,即至少包含apple、banana中的一个。 SELECT * FROM demo1 WHERE MATCH (name) AGAINST ('+apple banana' IN BOOLEAN MODE); -- 必须包含apple,但是如果同时也包含banana则会获得更高的权重。 SELECT * FROM demo1 WHERE MATCH (name) AGAINST ('+apple ~banana' IN BOOLEAN MODE); -- ~ 是我们熟悉的异或运算符。返回的记录必须包含apple,但是如果同时也包含banana会降低权重。但是它没有 +apple -banana 严格,因为后者如果包含banana压根就不返回。 SELECT * FROM demo1 WHERE MATCH (name) AGAINST ('+apple +(>banana <orange)' IN BOOLEAN MODE); -- 返回同时包含apple和banana或者同时包含apple和orange的记录。但是同时包含apple和banana的记录的权重高于同时包含apple和orange的记录。注意
A. MySQL全文检索默认不支持中文,且对英文检索时忽略大小写B. MySQL全文检索时,默认检索长度为4,即关键词的长度必须大于5才能被捕获C. MySQL全文检索时,所有FULLTEXT索引列必须使用相同的字符集D. MySQL全文检索返回结果集时还会考虑权重E. MySQL全文检索还支持灵活的布尔全文检索模式MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。
打个比方,如果合理的设计且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车。
拿汉语字典的目录页(索引)打比方,我们可以按拼音、笔画、偏旁部首等排序的目录(索引)快速查找到需要的字。
索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索引包含多个列。
创建索引时,你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。
实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。
上面都在说使用索引的好处,但过多的使用索引将会造成滥用。因此索引也会有它的缺点:虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
建立索引会占用磁盘空间的索引文件。
这是最基本的索引,它没有任何限制。它有以下几种创建方式:
CREATE INDEX indexName ON mytable(username(length));如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length。
它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
有四种方式来添加数据表的索引:
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。
以下实例为在表中添加索引。
mysql> ALTER TABLE testalter_tbl ADD INDEX (c);你还可以在 ALTER 命令中使用 DROP 子句来删除索引。尝试以下实例删除索引:
mysql> ALTER TABLE testalter_tbl DROP INDEX c;主键只能作用于一个列上,添加主键索引时,你需要确保该主键默认不为空(NOT NULL)。实例如下:
mysql> ALTER TABLE testalter_tbl MODIFY i INT NOT NULL; mysql> ALTER TABLE testalter_tbl ADD PRIMARY KEY (i);你也可以使用 ALTER 命令删除主键:
mysql> ALTER TABLE testalter_tbl DROP PRIMARY KEY;删除主键时只需指定PRIMARY KEY,但在删除索引时,你必须知道索引名。
你可以使用 SHOW INDEX 命令来列出表中的相关的索引信息。可以通过添加 \G 来格式化输出信息。
尝试以下实例:
mysql> SHOW INDEX FROM table_name; \G ........