校园招聘之Mysql索引

目录

1. 是什么

2. 存储位置

3. 优缺点

4. Mysql索引结构

4.1 Mysql两种引擎的索引实现

1MyISAM索引实现

2InnoDB索引实现

3) 总结

5. 索引分类

主键索引

单值索引

唯一索引

复合索引

基本语法

6. 索引建立时机

6.1 哪些情况需要创建索引

6.2 哪些情况不要创建索引

7. 查询优化

7.1 使用索引

5.1.1建表SQL

案例(索引失效)

热身case

一般性建议

7.2 单表查询优化

建表SQL

案例

7.3关联查询优化

建表SQL

案例 1

案例 2

建议

7.4 子查询优化

in 还是 exists

7.5 order by关键字优化

1ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序

2)尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀

3)如果不在索引列上,filesort有两种算法:

7.6 分页查询的优化---limit

7.7 GROUP BY关键字优化

7.8去重优化

 

1. 是什么

    首先,索引是数据结构,一种排好序的快速查找数据结构。

2. 存储位置

索引往往以索引文件的形式存储的磁盘上

3. 优缺点

优点:大大提高了查询速度

缺点:降低更新表的速度,占空间

4. Mysql索引结构

4.1 Mysql两种引擎的索引实现

(1)MyISAM索引实现

MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。下图是MyISAM索引的原理图:

 

8

这里设表一共有三列,假设我们以Col1为主键,则图8是一个MyISAM表的主索引(Primary key)示意。可以看出MyISAM的索引文件仅仅保存数据记录的地址。在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。如果我们在Col2上建立一个辅助索引,则此索引的结构如下图所示:

 

9

 

同样也是一颗B+Treedata域保存数据记录的地址。因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录。

MyISAM的索引方式也叫做非聚集的,之所以这么称呼是为了与InnoDB的聚集索引区分。

 

(2)InnoDB索引实现

虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。

第一个重大区别是InnoDB的数据文件本身就是索引文件。从上文知道,MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。

 

 

10

 

10InnoDB主索引(同时也是数据文件)的示意图,可以看到叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的(not null)列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

第二个与MyISAM索引的不同是InnoDB的辅助索引data域存储相应记录主键的值而不是地址。换句话说,InnoDB的所有辅助索引都引用主键作为data域。例如,图11为定义在Col3上的一个辅助索引:

 

这里以英文字符的ASCII码作为比较准则。聚集索引这种实现方式使得按主键的搜索十分高效,但是辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。

了解不同存储引擎的索引实现方式对于正确使用和优化索引都非常有帮助,例如知道了InnoDB的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在InnoDB中不是个好主意,因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。

最左前缀原理 索引选择性与前缀索引

http://blog.codinglabs.org/articles/theory-of-mysql-index.html 

(3)总结

--数据放在叶子节点,每个叶子节点组成链表

--MyISAM 主索引 和 辅助索引,存的都是物理地址,找到索引列后,可以根据地址找改行信息。

--InnoDB 主索引 是聚簇索引,存一整行,辅助索引,存的是索引列和主键,使用辅助索引查找时,需要找到主键,再去主索引查找。

 

5. 索引分类  

主键索引

设定为主键后数据库会自动建立索引,innodb为聚簇索引

语法

随表一起建索引:

CREATE TABLE customer (

id INT(10) UNSIGNED AUTO_INCREMENT ,

customer_no VARCHAR(200),

customer_name VARCHAR(200),

PRIMARY KEY(id) 

);

unsigned (无符号的)

使用  AUTO_INCREMENT 关键字的列必须有索引(只要有索引就行)。

CREATE TABLE customer2 (

id INT(10) UNSIGNED ,

customer_no VARCHAR(200),

customer_name VARCHAR(200),

PRIMARY KEY(id) 

);

单独建主键索引:

ALTER TABLE customer 

add PRIMARY KEY customer(customer_no);  

删除建主键索引:

ALTER TABLE customer 

drop PRIMARY KEY ;  

修改建主键索引:

必须先删除掉(drop)原索引,再新建(add)索引

单值索引

即一个索引只包含单个列,一个表可以有多个单列索引

语法

随表一起建索引:

CREATE TABLE customer (id INT(10) UNSIGNED  AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),

  PRIMARY KEY(id),

  KEY (customer_name)  

);

 随表一起建立的索引 索引名同 列名(customer_name)

单独建单值索引:

CREATE  INDEX idx_customer_name ON customer(customer_name); 

删除索引:

DROP INDEX idx_customer_name ;

唯一索引

索引列的值必须唯一,但允许有空值

语法

随表一起建索引:

CREATE TABLE customer (id INT(10) UNSIGNED  AUTO_INCREMENT ,customer_no VARCHAR(200),customer_name VARCHAR(200),

  PRIMARY KEY(id),

  KEY (customer_name),

  UNIQUE (customer_no)

);

建立 唯一索引时必须保证所有的值是唯一的(除了null),若有重复数据,会报错。  

单独建唯一索引:

CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no); 

删除索引:

DROP INDEX idx_customer_no on customer ;

复合索引

即一个索引包含多个列  

在数据库操作期间,复合索引比单值索引所需要的开销更小(对于相同的多个列建索引)

当表的行数远大于索引列的数目时可以使用复合索引

语法

随表一起建索引:

CREATE TABLE customer (

id INT(10) UNSIGNED  AUTO_INCREMENT ,

customer_no VARCHAR(200),

customer_name VARCHAR(200),

  PRIMARY KEY(id),

  KEY (customer_name),

  UNIQUE (customer_name),

  KEY (customer_no,customer_name)

);

单独建索引:

CREATE  INDEX idx_no_name ON customer(customer_no,customer_name); 

删除索引:

DROP INDEX idx_no_name  on customer ;

基本语法

创建

ALTER mytable ADD  [UNIQUE ]  INDEX [indexName] ON (columnname(length))

删除

DROP INDEX [indexName] ON mytable;

查看

SHOW INDEX FROM table_name\G

使用ALTER命令

有四种方式来添加数据表的索引:

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 ,用于全文索引。

6. 索引建立时机

6.1 哪些情况需要创建索引

1)主键自动建立唯一索引

2)频繁作为查询条件的字段应该创建索引(where 后面的语句)

(4)查询中与其它表关联的字段,外键关系建立索引

 

A 表关联 B 表:A join B  。  on 后面的连接条件 既 A 表查询 B 表的条件。所以 B 表被关联的字段建立索引能大大提高查询效率

因为在 join 中,join 左边的表会用每一个字段去遍历 B 表的所有的关联数据,相当于一个查询操作

4)单键/组合索引的选择问题,who(在高并发下倾向创建组合索引)

5)查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

6)查询中统计或者分组字段

group by 和 order by 后面的字段有索引大大提高效率

6.2 哪些情况不要创建索引

1)表记录太少

2)经常增删改的表

Why:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERTUPDATEDELETE

因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件

3Where条件里用不到的字段不创建索引

(4)数据重复且分布平均的表字段,因此应该只为最经常查询和最经常排序的数据列建立索引。注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。

 

7. 查询优化

7.1 使用索引

建表SQL

CREATE TABLE staffs (

  id INT PRIMARY KEY AUTO_INCREMENT,

  NAME VARCHAR (24)  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 '员工记录表' ;

INSERT INTO staffs(NAME,age,pos,add_time) VALUES('z3',22,'manager',NOW());

INSERT INTO staffs(NAME,age,pos,add_time) VALUES('July',23,'dev',NOW());

INSERT INTO staffs(NAME,age,pos,add_time) VALUES('2000',23,'dev',NOW());

INSERT INTO staffs(NAME,age,pos,add_time) VALUES(null,23,'dev',NOW());

SELECT * FROM staffs;

ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name, age, pos);

案例(索引失效)

  

索引失效针对的组合索引?

若一个字段上有多种索引呢?某一索引失效,可以继续使用其他索引不影响。

(1)全值匹配我最爱

索引  idx_staffs_nameAgePos 建立索引时 以 name , age ,pos 的顺序建立的。全值匹配表示 按顺序匹配的

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25;

EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = 'dev';

 

跳过列时

 

(2)最佳左前缀法则

如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列

 and 忽略左右关系。既即使没有没有按顺序 由于优化器的存在,会自动优化。

经过试验结论  建立了 idx_nameAge 索引  id 为主键

    1.当使用覆盖索引的方式时,(select name/age/id from staffs where age=10 (后面没有其他没有索引的字段条件)),即使不是以 name 开头,也会使用 idx_nameAge 索引。

    select 后的字段 有索引,where 后的字段也有索引,则无关执行顺序。

    2.除开上述条件 才满足最左前缀法则。

EXPLAIN SELECT * FROM staffs WHERE age = 25 AND pos = 'dev';

EXPLAIN SELECT * FROM staffs WHERE pos = 'dev';

(3)不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),做的话会导致索引失效而转向全表扫描

EXPLAIN SELECT * FROM staffs WHERE left(NAME,4) = 'July';

(4)存储引擎不能使用索引中范围条件右边的列

 范围 若有索引则能使用到索引,范围条件右边的索引会失效(范围条件右边与范围条件使用的同一个组合索引,右边的才会失效。若是不同索引则不会失效

 

(5)尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *

 

(6)mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描

索引  idx_nameAgeJob

    idx_name

使用 != 和 <> 的字段索引失效( != 针对数值类型。 <> 针对字符类型

前提 where and 后的字段在混合索引中的位置比比当前字段靠后  where age != 10 and name='xxx'  ,这种情况下,mysql自动优化,将 name='xxx' 放在 age !=10 之前,name 依然能使用索引。只是 age 的索引失效)

(7)is not null 也无法使用索引,但是is null是可以使用索引的

 

(8)like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作

like ‘%abc%’  type 类型会变成 all

like ‘abc%’ type 类型为 range ,算是范围,可以使用索引

 

like ‘k%kk%’ 

 

like +常量即可用索引

问题:解决like '%字符串%'时索引不被使用的方法??

CREATE TABLE `tbl_user` (

 `id` INT(11) NOT NULL AUTO_INCREMENT,

 `NAME` VARCHAR(20) DEFAULT NULL,

 `age` INT(11) DEFAULT NULL,

 email VARCHAR(20) DEFAULT NULL,

 PRIMARY KEY (`id`)

) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

#drop table tbl_user

INSERT INTO tbl_user(NAME,age,email) VALUES('1aa1',21,'[email protected]');

INSERT INTO tbl_user(NAME,age,email) VALUES('2aa2',222,'[email protected]');

INSERT INTO tbl_user(NAME,age,email) VALUES('3aa3',265,'[email protected]');

INSERT INTO tbl_user(NAME,age,email) VALUES('4aa4',21,'[email protected]');

INSERT INTO tbl_user(NAME,age,email) VALUES('aa',121,'[email protected]');

 

  

#before index

EXPLAIN SELECT NAME,age    FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT id FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT NAME     FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT age   FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT id,NAME    FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT id,NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT NAME,age FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT * FROM tbl_user WHERE NAME LIKE '%aa%';

EXPLAIN SELECT id,NAME,age,email  FROM tbl_user WHERE NAME LIKE '%aa%';

 

#create index

CREATE INDEX idx_user_nameAge ON tbl_user(NAME,age);

#DROP INDEX idx_user_nameAge ON tbl_user

#after index

EXPLAIN SELECT * FROM tbl_user WHERE NAME =800 AND age = 33;

给要查的字段建索引,即使用覆盖索引。同时,查主键索引,用like %xxx% 也可以用到索引

 

 

 

 

覆盖索引 :查询的字段都是索引列,不能有其他非索引列。

(9) 字符串不加单引号索引失效

          底层进行转换使索引失效,使用了函数造成索引失效

 

(10)少用or,用它来连接时会索引失效

 

口诀 :

 

(11)小总结

 假设index(a,b,c)           

where a = 3                 

where a = 3 and b = 5  

where a = 3 and b = 5 and c = 4  

where b = 3 或者 where b = 3 and c = 4  或者 where c = 4

where a = 3 and c = 5

where a = 3 and b > 4 and c = 5

where a = 3 and b like 'kk%' and c = 4

where a = 3 and b like '%kk' and c = 4

where a = 3 and b like '%kk%' and c = 4

where a = 3 and b like 'k%kk%' and c = 4

Where语句索引是否被使用

Y,使用到a

Y,使用到a,b

Y,使用到a,b,c

N

使用到a, 但是c不可以,b中间断了

使用到a和b, c不能用在范围之后,b后断了

Y,使用到a,b,c

Y,只用到a

Y,只用到a

Y,使用到a,b,c

热身case

题目SQL

面试题:

【建表语句】

create table test03(

 id int primary key not null auto_increment,

 c1 char(10),

 c2 char(10),

 c3 char(10),

 c4 char(10),

 c5 char(10)

);

insert into test03(c1,c2,c3,c4,c5) values('a1','a2','a3','a4','a5');

insert into test03(c1,c2,c3,c4,c5) values('b1','b2','b3','b4','b5');

insert into test03(c1,c2,c3,c4,c5) values('c1','c2','c3','c4','c5');

insert into test03(c1,c2,c3,c4,c5) values('d1','d2','d3','d4','d5');

insert into test03(c1,c2,c3,c4,c5) values('e1','e2','e3','e4','e5');

select * from test03;

【建索引】

create index idx_test03_c1234 on test03(c1,c2,c3,c4);

show index from test03;

问题:我们创建了复合索引idx_test03_c1234 ,根据以下SQL分析下索引使用情况?

explain select * from test03 where c1='a1';

explain select * from test03 where c1='a1' and c2='a2';

explain select * from test03 where c1='a1' and c2='a2' and c3='a3';

explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4';

1)

 explain select * from test03 where c1='a1' and c2='a2' and c3='a3' and c4='a4'; 

2) 

 explain select * from test03 where c1='a1' and c2='a2' and c4='a4' and c3='a3'; 

 

and 忽略左右关系。既即使没有没有按顺序 由于优化器的存在,会自动优化。

3) 

 explain select * from test03 where c1='a1' and c2='a2' and c3>'a3' and c4='a4';

 

4) 

 explain select * from test03 where c1='a1' and c2='a2' and c4>'a4' and c3='a3';

 

5) 

 explain select * from test03 where c1='a1' and c2='a2' and c4='a4' order by c3;

 c3作用在排序而不是查找

 

6) 

 explain select * from test03 where c1='a1' and c2='a2' order by c3;

 

7) 

 explain select * from test03 where c1='a1' and c2='a2' order by c4; 

出现了filesort

 

8) 

8.1

 explain select * from test03 where c1='a1' and c5='a5' order by c2,c3; 

 

 只用c1一个字段索引,但是c2、c3用于排序,无filesort

8.2

 explain select * from test03 where c1='a1' and c5='a5' order by c3,c2;

 

 出现了filesort,我们建的索引是1234,它没有按照顺序来,3 2 颠倒了

9) 

 explain select * from test03 where c1='a1' and c2='a2' order by c2,c3;

 

10)

 explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c2,c3; 

 

 用c1、c2两个字段索引,但是c2、c3用于排序,无filesort

 explain select * from test03 where c1='a1' and c2='a2' and c5='a5' order by c3,c2;             

 

 本例有常量c2的情况,和8.2对比

 explain select * from test03 where c1='a1' and c5='a5' order by  c3,c2;       

                                filesort

 

由于c2确定为’a2’,order by 后只剩下 c3 不会产生filesort

EXPLAIN SELECT * FROM test03 WHERE c1='a1' AND c3='a4' ORDER BY c3,c2;

 

11)

 explain select * from test03 where c1='a1' and c4='a4' group by c2,c3;

12)(每个索引31)

 explain select * from test03 where c1='a1' and c4='a4' group by c3,c2;

 Using where; Using temporary; Using filesort 

 

一般性建议

对于单键索引,尽量选择针对当前query过滤性更好的索引

在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。(避免索引过滤性好的索引失效)

在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引

尽可能通过分析统计信息和调整query的写法来达到选择合适索引的目的

7.2 单表查询优化

建表SQL

CREATE TABLE IF NOT EXISTS `article` (

`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY 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

);

INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES

(1, 1, 1, 1, '1', '1'),

(2, 2, 2, 2, '2', '2'),

(1, 1, 3, 3, '3', '3');

SELECT * FROM article;

案例

#查询 category_id 为1 且  comments 大于 1 的情况下,views 最多的 article_id。 

 

EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;

#结论:很显然,type 是 ALL,即最坏的情况。Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的。

#开始优化:

# 1.1 新建索引+删除索引

#ALTER TABLE `article` ADD INDEX idx_article_ccv ( `category_id` , `comments`, `views` );

create index idx_article_ccv on article(category_id,comments,views);

DROP INDEX idx_article_ccv ON article

 

# 1.2 第2次EXPLAIN

EXPLAIN SELECT id,author_id FROM `article` WHERE category_id = 1 AND comments >1 ORDER BY views DESC LIMIT 1;

#结论:

#type 变成了 range,这是可以忍受的。但是 extra 里使用 Using filesort 仍是无法接受的。

#但是我们已经建立了索引,为啥没用呢?

#这是因为按照 BTree 索引的工作原理,

# 先排序 category_id,

# 如果遇到相同的 category_id 则再排序 comments,如果遇到相同的 comments 则再排序 views。

#当 comments 字段在联合索引里处于中间位置时,

#因comments > 1 条件是一个范围值(所谓 range),

#MySQL 无法利用索引再对后面的 views 部分进行检索,即 range 类型查询字段后面的索引无效。

 

# 1.3 删除第一次建立的索引

DROP INDEX idx_article_ccv ON article;

# 1.4 第2次新建索引

#ALTER TABLE `article` ADD INDEX idx_article_cv ( `category_id` , `views` ) ;

create index idx_article_cv on article(category_id,views);

# 1.5 第3次EXPLAIN

EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;

#结论:可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。

DROP INDEX idx_article_cv ON article;

7.3关联查询优化

建表SQL

CREATE TABLE IF NOT EXISTS `class` (

`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

`card` INT(10) UNSIGNED NOT NULL,

PRIMARY KEY (`id`)

);

CREATE TABLE IF NOT EXISTS `book` (

`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

`card` INT(10) UNSIGNED NOT NULL,

PRIMARY KEY (`bookid`)

);

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));

案例 1

 

# 下面开始explain分析

EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

#结论:type 有All

 

# 添加索引优化

ALTER TABLE `book` ADD INDEX Y ( `card`);

# 第2次explain

EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

#可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。

#这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,

#所以右边是我们的关键点,一定需要建立索引。

 

# 删除旧索引 + 新建 + 第3次explain

DROP INDEX Y ON book;

ALTER TABLE class ADD INDEX X (card);

EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

 左连接索引加在右表

案例 2

在上面的基础上

CREATE TABLE IF NOT EXISTS `phone` (

`phoneid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,

`card` INT(10) UNSIGNED NOT NULL,

PRIMARY KEY (`phoneid`)

);

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));

EXPLAIN SELECT * FROM  class LEFT JOIN book ON class.`card` =book.`card` LEFT JOIN phone ON book.`card` = phone.`card`;

 

ALTER TABLE book ADD INDEX Y(card)

ALTER TABLE phone ADD INDEX z(card)

 

建议

永远小表驱动大表

1、保证被驱动表的join字段已经被索引

2left join 时,选择小表作为驱动表,大表作为被驱动表。小表是all

3inner join 时,mysql会自己帮你把小结果集的表选为驱动表。

4、子查询尽量不要放在被驱动表,有可能使用不到索引。

7.4 子查询优化

in 还是 exists

实验

有索引 大表驱动小表

select sql_no_cache sum(sal) from emp where deptno in (select deptno from dept);

select sql_no_cache sum(sal) from emp where exists (select 1 from dept where emp.deptno=dept.deptno);  ##用 exists 是否存在,存在返回一条记录,exists 是作为一个查询判断用,所以 select 后返回什么不重要。

select sql_no_cache sum(sal) from emp inner  join dept on  emp.deptno=dept.deptno;

有索引 小表驱动大表

select sql_no_cache sum(e.sal) from (select * from emp where id<10000) e  where  exists (select 1 from  emp where e.deptno=emp.deptno);

select sql_no_cache sum(e.sal) from (select * from emp where id<10000) e inner join (select distinct deptno from  emp) m on m.deptno=e.deptno;

select sql_no_cache sum(sal) from emp where deptno in (select deptno from dept);

有索引小驱动大表 性能优于 大表驱动小表

无索引 小表驱动大表

select sql_no_cache sum(e.sal) from (select * from emp where id<10000) e  where  exists (select 1 from  emp where e.deptno=emp.deptno);

select sql_no_cache sum(e.sal) from (select * from emp where id<10000) e inner join (select distinct deptno from  emp) m on m.deptno=e.deptno;

 select sql_no_cache sum(sal) from emp where deptno in (select deptno from dept);

无索引大表驱动小表

select sql_no_cache sum(sal) from emp where deptno in (select deptno from dept);

select sql_no_cache sum(sal) from emp where exists (select 1 from dept where emp.deptno=dept.deptno);

select sql_no_cache sum(sal) from emp inner  join dept on  emp.deptno=dept.deptno;

结论

有索引的情况下 用  inner join 是最好的  其次是 in  ,exists最糟糕

 

无索引的情况下用 小表驱动大表

因为join 方式需要distinct ,没有索引distinct消耗性能较大 

所以  exists性能最佳 in其次  join性能最差?

无索引的情况下大表驱动小表

in 和 exists 的性能应该是接近的  都比较糟糕  exists稍微好一点 超不过5%     但是inner join 优于使用了 join buffer 所以快很多

如果left join 则最慢

7.5 order by关键字优化

1ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序

建表SQL

CREATE TABLE tblA(

  id int primary key not null auto_increment,

  age INT,

  birth TIMESTAMP NOT NULL,

  name varchar(200)

);

INSERT INTO tblA(age,birth,name) VALUES(22,NOW(),'abc');

INSERT INTO tblA(age,birth,name) VALUES(23,NOW(),'bcd');

INSERT INTO tblA(age,birth,name) VALUES(24,NOW(),'def');

CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);

SELECT * FROM tblA; 

Case

1.

 

2.

 

效率对比

MySQL支持二种方式的排序,FileSortIndexIndex效率高.

它指MySQL扫描索引本身完成排序。FileSort方式效率较低。

Index方式排序条件

ORDER BY满足两情况,会使用Index方式排序:

(1)ORDER BY 语句使用索引最左前列

(2)使用Where子句与Order BY子句条件列组合满足索引最左前列

where子句中如果出现索引的范围查询(explain中出现range)会导致order by 索引失效。

2)尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀

3)如果不在索引列上,filesort有两种算法:

mysql就要启动双路排序和单路排序

双路排序

MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,

读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出

从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。

取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。

单路排序

从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。

结论及引申出的问题

1由于单路是后出的,总体而言好过双路

2但是用单路有问题

在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。

本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。

优化策略

(1)增大sort_buffer_size参数的设置    用于单路排序的内存大小

(2)增大max_length_for_sort_data参数的设置   单次排序字段大小。(单次排序请求)

         

(3)去掉select 后面不需要的字段

 

(4)Why

提高Order By的速度

1. Order byselect * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是:

  1.1 Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。

  1.2 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size

2. 尝试提高 sort_buffer_size

不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的

3. 尝试提高 max_length_for_sort_data

提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率

小总结

 

7.6 分页查询的优化---limit

EXPLAIN    SELECT  SQL_NO_CACHE * FROM emp  ORDER  BY  deptno   LIMIT 10000,40

  

那我们就给deptno这个字段加上索引吧。

然并卵。

优化:  先利用覆盖索引把要取的数据行的主键取到,然后再用这个主键列与数据表做关联:(查询的数据量小了后)

EXPLAIN  SELECT  SQL_NO_CACHE * FROM emp INNER JOIN (SELECT id FROM emp e ORDER BY deptno LIMIT 10000,40) a ON a.id=emp.id

最后比较一下查询速度:

优化前:

优化后:

 实践证明: ①、order by 后的字段(XXX)有索引 ②、sql 中有 limit 时,

    当 select id 或 XXX字段索引包含字段时 ,显示 using index

    当 select 后的字段含有 order by 字段索引不包含的字段时,将显示 using filesort

7.7 GROUP BY关键字优化

1group by实质是先排序后进行分组,遵照索引建的最佳左前缀

2)当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置

3where高于having,能写在where限定的条件就不要去having限定了。

7.8去重优化

尽量不要使用 distinct 关键字去重:优化

t_mall_sku 表

  id  shp_id      kcdz                

------  ------ --------------------

     3       1    北京市昌平区  

     4       1    北京市昌平区  

     5       5    北京市昌平区  

     6       3       重庆              

     8       8     天津              

例子:select kcdz form t_mall_sku where id in( 3,4,5,6,8 )  将产生重复数据,

          select distinct kcdz form t_mall_sku where id in( 3,4,5,6,8 )   使用 distinct 关键字去重消耗性能

优化: select  kcdz form t_mall_sku where id in( 3,4,5,6,8 )  group by kcdz 能够利用到索引

猜你喜欢

转载自www.cnblogs.com/cfl-yin/p/10513708.html
今日推荐