mysql 索引分析

虾米哥 阅读:249 2020-02-17 20:53:20 评论:0

对于任何DBMS,索引都是进行优化的最主要的因素。对于少量的数据,没有合适的索引影响不是很大,但是,当随着数据量的增加,性能会急剧下降。
如果对多列进行索引(组合索引),列的顺序非常重要,MySQL仅能对索引最左边的前缀进行有效的查找。

例如:
假设存在组合索引idx(c1,c2),查询语句select * from t1 where c1=1 and c2=2能够使用该索引。查询语句select * from t1 where c1=1也能够使用该索引。但是,查询语句select * from t1 where c2=2不能够使用该索引,因为没有组合索引的引导列,即要想使用c2列进行查找,必需出现c1等于某值

索引是快速搜索的关键。MySQL索引的建立对于MySQL的高效运行是很重要的。
下面介绍几种常见的MySQL索引类型:
在数据库表中,对字段建立索引可以大大提高查询速度。假如我们创建了一个 mytable表:

CREATE TABLE mytable( 
ID INT NOT NULL,  
username VARCHAR(16) NOT NULL  
); 

我们随机向里面插入了10000条记录,其中有一条:5555, admin。
在查找username="admin"的记录时(SELECT * FROM mytable WHERE username='admin'),如果在username上已经建立了索引,MySQL无须任何扫描,即准确可找到该记录。相反,MySQL会扫描所有记录,即要查询10000条记录。

一、MySQL索引类型包括:
1.普通索引
这是最基本的索引,它没有任何限制。它有以下几种创建方式:
a.创建索引
CREATE INDEX indexName ON mytable(username(length));
如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length,下同。

b.修改表结构
ALTER mytable ADD INDEX [indexName] ON (username(length))

c.创建表的时候直接指定

CREATE TABLE mytable(  
   ID INT NOT NULL,  
   username VARCHAR(16) NOT NULL,  
   INDEX [indexName] (username(length))  
); 

删除索引的语法:
DROP INDEX [indexName] ON mytable;

2.唯一索引
它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:

 a.创建索引
CREATE UNIQUE INDEX indexName ON mytable(username(length))

b.修改表结构
ALTER mytable ADD UNIQUE [indexName] ON (username(length))

 c.创建表的时候直接指定

CREATE TABLE mytable(  
   ID INT NOT NULL,  
   username VARCHAR(16) NOT NULL,  
   UNIQUE [indexName] (username(length))  
); 

3.主键索引
它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引:

CREATE TABLE mytable(  
  ID INT NOT NULL,  
  username VARCHAR(16) NOT NULL,  
  PRIMARY KEY(ID)  
); 

 4.组合索引
为了形象地对比单列索引和组合索引,为表添加多个字段:

CREATE TABLE mytable(  
   ID INT NOT NULL,  
   username VARCHAR(16) NOT NULL,  
   city VARCHAR(50) NOT NULL,  
   age INT NOT NULL  
); 

 为了进一步榨取MySQL的效率,就要考虑建立组合索引。就是将 username, city, age建到一个索引里:

ALTER TABLE mytable ADD INDEX name_city_age (username(10),city,age); 

建表时,usernname长度为16,这里用10。这是因为一般情况下名字的长度不会超过10,这样会加速索引查询速度,还会减少索引文件的大小,提高INSERT的更新速度。

如果分别在 usernname,city,age上建立单列索引,让该表有3个单列索引,查询时和上述的组合索引效率也会大不一样,远远低于我们的组合索引。虽然此时有了三个索引,但MySQL只能用到其中的那个它认为似乎是最有效率的单列索引。

建立这样的组合索引,其实是相当于分别建立了下面三组组合索引:
usernname,city,age
usernname,city
usernname

为什么没有city,age这样的组合索引呢?这是因为MySQL组合索引“最左前缀”的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这三列的查询都会用到该组合索引,下面的几个SQL就会用到这个组合索引:

SELECT * FROM mytable WHREE username="admin" AND city="郑州" 
SELECT * FROM mytable WHREE username="admin"

 而下面几个则不会用到:

SELECT * FROM mytable WHREE age=20 AND city="郑州" 
SELECT * FROM mytable WHREE city="郑州"

二、使用索引的注意事项
使用索引时,有以下一些技巧和注意事项:

1.索引不会包含有NULL值的列
只要列中包含有NULL值都将不会被包含在MySQL索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

2.使用短索引
对串列进行索引,如果可能应该指定一个前缀长度。例如,如果有一个CHAR(255)的列,如果在前10个或20个字符内,多数值是惟一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。

3.索引列排序
MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

4.like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用MySQL索引而like “aaa%”可以使用索引。

5.不要在列上进行运算

select * from users where YEAR(adddate)<2007; 

 将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成

select * from users where adddate<2007-01-01’;  

6.不使用NOT IN和<>操作
NOT IN和<>操作都不会使用索引将进行全表扫描。NOT IN可以使用NOT EXISTS代替,id<>3则可以使用id>3 or id<3来代替。

三、建立索引的时机
到这里我们已经学会了建立索引,那么我们需要在什么情况下建立索引呢?一般来说,在WHERE和JOIN中出现的列需要建立索引,但也不完全如此,因为MySQL只对<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE才会使用索引。例如:

SELECT t.Name 
FROM mytable t LEFT JOIN mytable m  
ON t.Name=m.username WHERE m.age=20 AND m.city='郑州'

此时就需要对city和age建立索引,由于mytable表的userame也出现在了JOIN子句中,也有对它建立索引的必要。
刚才提到只有某些时候的LIKE才需建立索引。因为在以通配符%和_开头作查询时,MySQL不会使用索引。例如下句会使用索引:

SELECT * FROM mytable WHERE username like'admin%'

而下句就不会使用:

SELECT * FROM mytable WHEREt Name like'%admin'

因此,在使用LIKE时应注意以上的区别。

四、索引的不足之处
上面都在说使用索引的好处,但过多的使用索引将会造成滥用。因此索引也会有它的缺点:
(1)虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
(2)建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。
索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。

五、实例

比如有这样一张表

create table staffs( 
        id int primary key auto_increment, 
        name varchar(24) not null default '' comment '姓名', 
        age int not null default 0 comment '年龄', 
        pos varchar(20) not null default '' comment '职位', 
        add_time timestamp not null default current_timestamp comment '入职时间' 
    ) charset utf8 comment '员工记录表';

 添加三列的复合索引:

alter table staffs add index idx_nap(name, age, pos);

在BTREE索引的使用上,以下几种情况可以用到该索引或索引的一部分(使用explain简单查看使用情况):
1.全值匹配
如select * from staffs where name = 'July' and age = '23' and pos = 'dev' ,key字段显示使用了idx_nap索引。

2.匹配最左列,对于复合索引来说,不总是匹配所有字段列,但是可以匹配索引中靠左的列,
如select * from staffs where name = 'July' and age = '23',key字段显示用到了索引,注意,key_len字段(表示本次语句使用的索引长度)数值比上一条小了,意思是它并未使用全部索引列(通常这个长度可估摸着用了哪些索引列),事实上只用到了name和age列。

再试试select * from staffs where name = 'July',它也用了索引,key_len值更小,实际只用到了索引中的name列。

3.匹配列前缀,即一个索引中列的前一部分,主要用在模糊匹配,如select * from staffs where name like 'J%',explain信息的key字段表示使用了索引。

4.匹配范围,如select * from staffs where name > 'Mary'。

5.精确匹配一列并范围匹配右侧相邻列,即前一列是固定值,后一列是范围值,它用了name与age两个列的索引(key_len推测)
如select * from staffs where name = 'July' and age > 25

6.只访问索引的查询,比如staffs表的情况,索引建立在(name,age,pos)上面,前面一直是读取的全部列,如果我们用到了哪些列的索引,查询时也只查这些列的数据,就是只访问索引的查询,如

select name,age,pos from staffs where name = 'July' and age = 25 and pos = 'dev' 
select name,age from staffs where name = July and age > 25

第一句用到了全部索引列,第二句只用了索引前两列,select的字段就最多只能是这两列,这种查询情况的索引,mysql称为覆盖索引,就是索引包含(覆盖)了查询的全部字段。是不是用到了索引查询,在explain中需要看最后一个Extra列的信息,Using index表明使用了覆盖索引,同时Using where表明也使用了where过滤。

7.前缀索引

区别于列前缀(类似like 'J%'形式的模糊匹配)和最左列索引(顺序取索引中靠左的列的查询),它只取某列的一部分作为索引。通常在说InnoDB跟MyISAM的区别时,一个明显的区别是:MyISAM支持全文索引,而InnoDB不行,甚至对于text、blob这种超长的字符串或二进制数据时,MyISAM会取前多少个字符作为索引,InnoDb的前缀索引跟这个类似,某些列,一般是字符串类型,很长,全部作为索引大大增加存储空间,索引也需要维护,对于长字符串,又想作为索引列,一个可取的办法就是取前一部分(前缀),代表一整列作为索引串,问题是:如何确保这个前缀能代表或大致代表这一列?所以mysql中有个概念是索引的选择性,是指索引中不重复的值的数目(也称基数)与整个表该列记录总数(#T)的比值,比如一个列表(1,2,2,3),总数是4,不重复值数目为3,选择性为3/4,因此选择性范围是[1/#T, 1],这个值越大,表示列中不重复值越多,越适合作为前缀索引,唯一索引(UNIQUE KEY)的选择性是1。

比如有一列a varchar(255),以它作前缀索引,比如以7个测试,逐个增加看看选择性值增长到那个数基本不变,就表示可以代表整列了,再结合这个长度的索引列是否存储数据太多,做个权衡,基本就行了。但如果这个选择性本来就小的可怜还是算了

select count(distinct left(a, 7))/count(*) as non_repeat from tab;

定好一个前缀数目,如9,添加索引时可以这样

 alter table tab add index idx_pn(name(9))  --单独前缀索引 
 alter table tab add index idx_cpn(count, name(9))  --复合前缀索引

以上为常见的使用索引的方式,有这么些情况不能用或不能全用,有的就是上面情况的反例,以key(a, b, c)为例
(1).跳过列,where a = 1 and c = 3,最多用到索引列a;where b = 2 and c = 3,一个也用不到,必须从最左列开始
(2).前面是范围查询,where a = 1 and b > 2 and c = 3,最多用到 a, b两个索引列;
(3).顺序颠倒,where c = 3 and b = 2 and a = 1,一个也用不到;
(4).索引列上使用了表达式,如where substr(a, 1, 3) = 'hhh',where a = a + 1,表达式是一大忌讳,再简单mysql也不认。有时数据量不是大到严重影响速度时,一般可以先查出来,比如先查所有有订单记录的数据,再在程序中去筛选以'cp1001'开头的订单,而不是写sql过滤它;
(5).模糊匹配时,尽量写 where a like 'J%',字符串放在左边,这样才可能用得到a列索引,甚至可能还用不到,当然这得看数据类型,最好测试一下。

排序对索引的影响
order by是经常用的语句,排序也遵循最左前缀列的原则,比如key(a, b),下面语句可以用到(测试为妙)

select * from tab where a > 1 order by b 
select * from tab where a > 1 and b > '2015-12-01 00:00:00' order by b 
select * from tab order by a, b

以下情况用不到
(1).非最左列,select * from tab order by b;
(2).不按索引列顺序来的,select * from tab where b > '2015-12-01 00:00:00' order by a;
(3).多列排序,但列的顺序方向不一致,select * from tab a asc, b desc。
初步了解以上内容后,就知道了索引冗余了,比如有了(a,b)索引,(a)就是冗余的,需要创建(a)索引时,直接创建(a)就行,而不是(id,a),id指主键,主键primary key已经是UNIQUE KEY了,不用加唯一限制。

 

声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

发表评论
搜索
关注我们

扫一扫关注我们,了解最新精彩内容