Mysql索引原理及SQL优化

一、什么是索引?

索引用来快速地寻找那些具有特定值的记录,所有MySQL索引都以B-tree的形式保存。如果没有索引,执行查询时MySQL必须从第一个记录开始扫描整个表的所有记录,直至找到符合要求的记录。表里面的记录数量越多,这个操作的代价就越高。如果作为搜索条件的列上已经创建了索引,MySQL无需扫描任何记录即可迅速得到目标记录所在的位置。如果表有100万条记录,通过索引查找记录至少要比顺序扫描记录快1000倍。

二、索引的原理

为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。

上图展示了一种可能的索引方式。左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址(注意逻辑上相邻的记录在磁盘上也并不是一定物理相邻的)。为了加快 Col2 的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在 O(log2n)的复杂度内获取到相应数据。

创建索引可以大大提高系统的性能:

第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。

第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。

第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。

第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

也许会有人要问:增加索引有如此多的优点,为什么不对表中的每一个列创建一个索引呢?

因为,增加索引也有许多不利的方面:

第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。

第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。

第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

 

索引是建立在数据库表中的某些列的上面。在创建索引的时候,应该考虑在哪些列上可以创建索引,在哪些列上不能创建索引。一般来说,应该在这些列上创建索引:

1、在经常需要搜索的列上,可以加快搜索的速度;

2、在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;

3、在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;

4、在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;

5、在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;

6、在经常使用在 WHERE 子句中的列上面创建索引,加快条件的判断速度。

根据数据库的功能,可以在数据库设计器中创建三种索引:唯一索引、主键索引和聚集索引

 

 

三、索引的分类:

主键索引

主键是一种唯一性索引,但它必须指定为“PRIMARY KEY”。如果你曾经用过AUTO_INCREMENT类型的列,你可能已经熟悉主键之类的概念了。主键一般在创建表的时候指定,例如“CREATE TABLE tablename ( [...], PRIMARY KEY (列的列表) ); ”。但是,我们也可以通过修改表的方式加入主键,例如“ALTER TABLE tablename ADD PRIMARY KEY (列的列表); ”。每个表只能有一个主键。

当一张表,把某个列设为主键的时候,则该列就是主键索引

create table aaa(

id int unsigned primary key auto_increment ,

name varchar(32) not null default '');

这是id 列就是主键索引.

如果你创建表时,没有指定主键索引,也可以在创建表后,在添加, 指令:

实例:

alter table 表名 add primary key (列名);

删除主键索引

alter table articles drop primary key;

查询索引

desc  表名;   不能显示索引名称

show index from 表名

show keys from 表名

普通索引

普通索引(由关键字KEY或INDEX定义的索引)的唯一任务是加快对数据的访问速度。因此,应该只为那些最经常出现在查询条件(WHERE column= )或排序条件(ORDER BY column)中的数据列创建索引。只要有可能,就应该选择一个数据最整齐、最紧凑的数据列(如一个整数类型的数据列)来创建索引。

create table ccc(
id int unsigned,
name varchar(32)
);

create index 索引名 on 表 (列1,列2);

唯一索引(不能用重复'')

这种索引和前面的“普通索引”基本相同,但有一个区别:索引列的所有值都只能出现一次,即必须唯一。唯一性索引可以用以下几种方式创建: 

创建索引,例如CREATE UNIQUE INDEX <索引的名字> ON tablename (列的列表); 

修改表,例如ALTER TABLE tablename ADD UNIQUE [索引的名字] (列的列表); 

创建表的时候指定索引,例如CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (列的列表) ); 

create table ddd(
id int primary key auto_increment ,
name varchar(32) unique);

注意

unique字段可以为NULL,并可以有多NULL, 但是如果是具体内容,则不能重复,

但是不能存有重复的空字符串’’

组合索引(my_ind)

创建一个组合索引

alter table dept add index my_ind (dname,loc); //dname 左边的列,loc就是右边的列

只使用dname而不使用loc一起作为条件查找,dname会使用组合索引进行查找TYPEmy_ind,但只使用loc不使用dname作为条件查找,不会使用索引TYPEALL(后面会提到mysql的执行计划ALL为全文查找)。

全文索引

FULLTEXT

CREATE TABLE articles (
       id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
       title VARCHAR(200),
       body TEXT,
       FULLTEXT (title,body)
     )engine=myisam charset utf8;

INSERT INTO articles (title,body) VALUES
     ('MySQL Tutorial','DBMS stands for DataBase ...'),
     ('How To Use MySQL Well','After you went through a ...'),
     ('Optimizing MySQL','In this tutorial we will show ...'),
     ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
     ('MySQL vs. YourSQL','In the following database comparison ...'),
     ('MySQL Security','When configured properly, MySQL ...');

 

错误用法:

select * from articles where body like '%mysql%'; 错误用法 索引不会生效

正确用法:

select * from articles where match(title,body) against ( 'database');

说明:

在mysql中fulltext 索引只针对 myisam生效,只有myisam支持全文索引

mysql自己提供的fulltext针对英文生效->sphinx (coreseek) 技术处理中文

使用方法是 match(字段名..) against(‘关键字’)

全文索引:停止词,  因为在一个文本中,创建索引是一个无穷大的数,因此,对一些常用词和字符,就不会创建,这些词,称为停止词.比如(a,b,mysql,the)

mysql> select match(title,body) against ('database') from articles;(输出的是每行和database的匹配度)

四、MySQL explain执行计划解读

当我们在查询前能否预先估计查询究竟要涉及多少行、使用哪些索引、运行时间呢?答案是能的,mysql提供了相应的功能和语法来实现该功能。

MySql提供了EXPLAIN语法用来进行查询分析,在SQL语句前加一个"EXPLAIN"即可。比如我们要分析如下SQL语句:

explain select * from table where table.id = 1;

运行上面的sql语句后你会看到,下面的表头信息:
table | type | possible_keys | key | key_len | ref | rows | Extra

table 
显示这一行的数据是关于哪张表的

type 
这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALL

说明:不同连接类型的解释(按照效率高低的顺序排序)

system:表只有一行:system表。这是const连接类型的特殊情况。

const :表中的一个记录的最大值能够匹配这个查询(索引可以是主键或唯一索引)。因为只有一行,这个值实际就是常数,因为MYSQL先读这个值然后把它当做常数来对待。

eq_ref:在连接中,MYSQL在查询时,从前面的表中,对每一个记录的联合都从表中读取一个记录,它在查询使用了索引为主键或惟一键的全部时使用。

ref:这个连接类型只有在查询使用了不是唯一或主键的键或者是这些类型的部分(比如,利用最左边前缀)时发生。对于之前的表的每一个行联合,全部记录都将从表中读出。这个类型严重依赖于根据索引匹配的记录多少—越少越好。

range:这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西时发生的情况。

index:这个连接类型对前面的表中的每一个记录联合进行完全扫描(比ALL更好,因为索引一般小于表数据)。

ALL:这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免。

my_ind:组合索引

key 
实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引

key_len 
使用的索引的长度。在不损失精确性的情况下,长度越短越好

ref 
显示索引的哪一列被使用了,如果可能的话,是一个常数

rows 
MYSQL认为必须检查的用来返回请求数据的行数

extra列返回的描述的意义

Distinct :一旦mysql找到了与行相联合匹配的行,就不再搜索了。

Not exists :mysql优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了。

Range checked for each Record(index map:#) :没有找到理想的索引,因此对从前面表中来的每一个行组合,mysql检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一。

Using filesort :看到这个的时候,查询就需要优化了。mysql需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。

Using index :列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候。

Using temporary :看到这个的时候,查询需要优化了。这里,mysql需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上。

Where used :使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题。

因此,弄明白了explain语法返回的每一项结果,我们就能知道查询大致的运行时间了,如果查询里没有用到索引、或者需要扫描的行过多,那么可以感到明显的延迟。因此需要改变查询方式或者新建索引。mysql中的explain语法可以帮助我们改写查询,优化表的结构和索引的设置,从而最大地提高查询效率。当然,在大规模数据量时,索引的建立和维护的代价也是很高的,往往需要较长的时间和较大的空间,如果在不同的列组合上建立索引,空间的开销会更大。因此索引最好设置在需要经常查询的字段中。

 

五、总结

优点:提高查询效率

缺点:增删慢,索引文件需要更新,增加内存

什么情况需要用到索引

  • 在where条件经常使用
  • 该字段的内容不是唯一的几个值,如性别男0,女1
  • 字段内容不是频繁变化

关于索引注意事项【重要】:

记住:联合、like、or、null、字符串、in、group  by、

1、联合查询 alter table dept add index my_ind (dname,loc)  使用dname或使用两者作为条件才会使用到索引

2、模糊查询在like前面有%百分号开头会失效,因此在使用索引时候,不要使用%开头,否则全表扫描,应写成like "条件%"

3、如果条件中有or,即使其中有条件带索引也不会使用。换言之,就是要求使用的所有字段,都必须建立索引, 我们建议大家尽量避免使用or关键字。or中,条件必须都为索引,否则只要有一个不为索引,则都不会进行索引

4、如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引。(添加时,字符串必须''), 也就是,如果列是字符串类型,就一定要用 '' 把他包括起来。
 

SQL优化【重要】:

1、使用group by 分组查询是——默认分组后,还会排序,可能会降低速度

在group by 后面增加 order by null 就可以禁止排序.

explain select * from emp  group by deptno order by null;

2、select * from userId >= 101 和select * from userId > 100   哪个效率高?

在工作中尽量不要使用>= 、 <=因为会做两次全表扫描,使用> 、 <  、 !=、<>

3、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num is null

最好不要给数据库留 NULL,尽可能的使用 NOT NULL 填充数据库。不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL 也包含在内),都是占用 100 个字符的空间的,如果是 varchar 这样的变长字段, null 不占用空间。

4、in 和 not in 也要慎用,否则会导致全表扫描,如:

select id from t where num in(1,2,3)

对于连续的数值,能用 between 就不要用 in 了:

select id from t where num between 1 and 3

很多时候用 exists 代替 in 是一个好的选择:

select num from a where num in(select num from b)

用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num)

更多参考:

http://www.cnblogs.com/yunfeifei/p/3850440.html

猜你喜欢

转载自blog.csdn.net/itcats_cn/article/details/82152336