1.单表优化案例
1)建表语句 students表,并插入3条语句
CREATE TABLE `students` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) DEFAULT NULL, `sex` varchar(64) DEFAULT NULL, `age` int(11) DEFAULT NULL, `city` varchar(64) DEFAULT NULL, `birthday` datetime DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `students_id_uindex` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
INSERT INTO students (name, sex, age, city, birthday) VALUES ('tom', 'm', 12, '北京', '2007-10-19 08:46:47');
INSERT INTO students (name, sex, age, city, birthday) VALUES ('alice', 'w', 13, '上海', '2006-10-19 08:48:06');
INSERT INTO students (name, sex, age, city, birthday) VALUES ('join', 'm', 15, '上海', '2004-10-19 08:48:06');
2)在未建索引时执行下面的sql语句,extra中出现了Using filesort,会影响性能,需要优化的
3)
优化1:name、age、birthdy建复合索引 ,然后再执行sql语句分析
mysql> create INDEX idx_students_na_ag_bir ON students(name,age,birthday); Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0
理论上用到索引,实际也用到了索引,但是ref为null且extra中仍然有Using filesort,所以索引失效了;
原因:因为mysql的索引是BTree索引的工作原理,此sql会先排序name,如果遇到相同的name,则再排序age,age相同则再排序birthdy; 当age处于中间位置时,且age是用的“>”即范围查询时,mysql无法利用索引再对后面的birthdy进行检索(即range类型查询字段后面的索引无效),从而导致索引失效
优化2:根据失效原因重新建索引(索引中去掉age)
mysql> drop INDEX idx_students_na_ag_bir ON students; Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> create INDEX idx_students_na_bir ON students(name,birthday); Query OK, 0 rows affected (0.03 sec) Records: 0 Duplicates: 0 Warnings: 0
重新建索引后,ref为const用到了索引,且extra没有了Using filesort
2.两表优化案例
students表和city表
CREATE TABLE `city` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `city_id_uindex` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; INSERT INTO city (name) VALUES ('北京'); INSERT INTO city (name) VALUES ('上海'); INSERT INTO city (name) VALUES ('南京');
1)未建索引的情况下
EXPLAIN SELECT * FROM students LEFT JOIN city ON students.city=city.name;
2)在students表建索引
create INDEX in_city ON students(city);
EXPLAIN SELECT * FROM students LEFT JOIN city ON students.city=city.name; //加上索引后,并未起作用
3)在city表加索引
drop INDEX in_city ON students;
create INDEX in_city ON city(name);
EXPLAIN SELECT * FROM students LEFT JOIN city ON students.city=city.name; //city的type为ref,ref也有值,说明加索引已生效
为什么在left join的左表加索引不起作用呢?
这是由于左连接持续性导致的,左边表的数据都有,left join 用于有右边搜索行,所以索引应建在left join 右边的表;同理,right join 应建在左边的表
join语句的优化:
1. 尽可能减少Join 语句中的Nested Loop 的循环总次数;
如何减少Nested Loop 的循环总次数?最有效的办法只有一个,那就是让驱动表的结果集尽可能的小。
为什么?因为驱动结果集越大,意味着需要循环的次数越多,也就是说在被驱动结果集上面所需要执行的查询检索次数会越多。比如,当两个表(表A 和表B) Join 的时候,如果表A 通过WHERE 条件过滤后有10 条记录,而表B 有20 条记录。如果我们选择表A 作为驱动表,也就是被驱动表的结果集为20,那么我们通过Join 条件对被驱动表(表B)的比较过滤就会有10 次。反之,如果我们选择表B 作为驱动表,则需要有20 次对表A 的比较过滤。当然,此优化的前提条件是通过Join 条件对各个表的每次访问的资源消耗差别不是太大。如果访问存在较大的差别的时候(一般都是因为索引的区别),我们就不能简单的通过结果集的大小来判断需要Join 语句的驱动顺序,而是要通过比较循环次数和每次循环所需要的消耗的乘积的大小来得到如何驱动更优化。
2. 优先优化Nested Loop 的内层循环;
不仅仅是在数据库的Join 中应该做的,实际上在我们优化程序语言的时候也有类似的优化原则。内层循环是循环中执行次数最多的,每次循环节约很小的资源,在整个循环中就能节约很大的资源。
3. 保证Join 语句中被驱动表上Join 条件字段已经被索引;
保证被驱动表上Join 条件字段已经被索引的目的,正是针对上面两点的考虑,只有让被驱动表的Join 条件字段被索引了,才能保证循环中每次查询都能够消耗较少的资源,这也正是优化内层循环的实际优化方法。
4. 当无法保证被驱动表的Join 条件字段被索引且内存资源充足的前提下,不要太吝惜Join
Buffer 的设置:
当在某些特殊的环境中,我们的Join 必须是All,Index,range 或者是index_merge 类型的时候,Join Buffer 就会派上用场了。在这种情况下,Join Buffer 的大小将对整个Join 语句的消耗起到非常关键的作用。
3.索引优化口诀
1.全值匹配我最爱
2.最佳左前缀法则 (如果索引了多列,要遵守最左前缀法则,指的是查询从索引的最前列开始,并且不跳过索引中的列)
3.不在索引上做任何操作(计算、函数、类型转换 会导致索引失效,从而导致全表扫描)
4.存储引擎不能使用索引中范围右边的列
5.尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),尽量不使用select *
6.mysql在使用不等于(!=或<>)时无法使用索引,从而导致全表扫描
7. is not null 也无法使用索引
8. like以通配符开头(%abc...),索引会失效,从而导致全表扫描
9.字符串不加单引号索引失效
10.少用or,用它做连接时索引会失效
-1)全值匹配我最爱
create INDEX index_name_sex_age ON students(name, sex, age) ;
EXPLAIN SELECT * FROM students WHERE name='tom' AND sex='m' AND age=12; //建的索引和用到的索引,全部匹配,且顺序一致
-2)最佳左前缀法则 (如果索引了多列,要遵守最左前缀法则,指的是查询从索引的最前列开始,并且不跳过索引中的列)
1.EXPLAIN SELECT * FROM students WHERE name='tom'; //匹配到了复合索引的第一个字段name 所以用到了索引,ref为const
2.EXPLAIN SELECT * FROM students WHERE sex='m' AND age=12; //因为从复合索引的第二个字段开始,跳过了第一个导致索引失效,引起全表扫描
3.EXPLAIN SELECT * FROM students WHERE name='tom' AND age=12; //虽然用到了索引,但只用到了name ,由于跳过了sex导致age未用到索引,故ref中只有一个const
-3)不在索引上做任何操作(计算、函数、类型转换 会导致索引失效,从而导致全表扫描)
1. EXPLAIN SELECT * FROM students WHERE name='tom';
2. EXPLAIN SELECT * FROM students WHERE left(name,4)='tom'; //使用了left函数后,索引失效
-4)存储引擎不能使用索引中范围右边的列
EXPLAIN SELECT * FROM students WHERE name='tom' AND sex='m' AND age=12;
EXPLAIN SELECT * FROM students WHERE name='tom' AND sex='m' AND age>12; //使用范围时,索引失效
-5) 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),尽量不使用select *
EXPLAIN SELECT name ,sex,age FROM students WHERE name='tom' AND sex='m' AND age>12;
由4的例子可以看到使用“>”时,ref为null,在5中select * 换位索引列后 SELECT name ,sex,age,ref为const,extra中也用上“using index” 所以要尽量查询索引列
-6).mysql在使用不等于(!=或<>)时无法使用索引,从而导致全表扫描
EXPLAIN SELECT * FROM students WHERE name!='tom' ; //!= 导致索引失效
-7). is not null 也无法使用索引
EXPLAIN SELECT * FROM students WHERE name is NOT NULL ;
-8). like以通配符开头(%abc...),索引会失效,从而导致全表扫描
由下图可知,当select * 时,通配符%在右边的话,导致索引失效
但是因为业务需要非得用“%...%”匹配的话,可以在select时使用覆盖索引,以避免索引失效
由下图测试可知,当查询的列不在索引中,也会导致索引失效
-9).字符串不加单引号索引失效
如下图所示,未使用单引号的索引失效
- 10).少用or,用它做连接时索引会失效
使用or时 type为All索引失效
mysql> EXPLAIN SELECT * FROM students WHERE name ='tom' OR name='join';
+----+-------------+----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | students | NULL | ALL | index_name_sex_age | NULL | NULL | NULL | 1 | 100.00 | Using where |
+----+-------------+----------+------------+------+--------------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)