索引优化
-- 创建两个新表
create table staffs(
id int primary key auto_increment,
name varchar(24) not null default "",
age int not null default 0,
pos varchar(20) not null default "",
add_time timestamp not null default CURRENT_TIMESTAMP
)charset utf8;
-- 插入数据
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());
-- 建立索引
create index idx_staffs_nameAgePos on staffs(name,age,pos);
mysql 中CURRENT_TIMESTAMP用于创建时间字段的时候,表示插入数据的时候,该字段默认值为当前时间。ON UPDATE CURRENT_TIMESTAMP。
timestamp是时间戳
如何优化?是改变索引,还是改变查询语句?先做一下测试
-- 查询姓名为july,年龄大于22,pos为dev的信息(我们先不要管语句是否合理)
explain select * from staffs where name='july' and age>22 and pos='dev';
+----+-------------+--------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | staffs | NULL | range | idx_staffs_nap | idx_staffs_nap | 78 | NULL | 1 | 33.33 | Using index condition |
+----+-------------+--------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
explain select * from staffs where name='july';
+----+-------------+--------+------------+------+----------------+----------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+----------------+----------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | staffs | NULL | ref | idx_staffs_nap | idx_staffs_nap | 74 | const | 1 | 100.00 | NULL |
+----+-------------+--------+------------+------+----------------+----------------+---------+-------+------+----------+-------+
explain select * from staffs where name='july' and age >22;
+----+-------------+--------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | staffs | NULL | range | idx_staffs_nap | idx_staffs_nap | 78 | NULL | 1 | 100.00 | Using index condition |
+----+-------------+--------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
- 通过观察key_len,可以看出第一条语句中只用到了复合索引中的name和age,因为单用name的key_len是74,name+age的key_len是78;没有用到pos,因为范围后的索引会失效。
- 当我只查询name,或name+age时,索引也没有全部用到,既然建立复合索引,最好是与之相匹配,如复合索引(name,age,pos),那查询条件也按照name…age…pos…去写
优化方案
我们应该根据实际需求选择不同的优化方案,下面提供思路。
-- 将范围改为常量
explain select * from staffs where name='july' and age =22 and pos='dev';
+----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | staffs | NULL | ref | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 140 | const,const,const | 1 | 100.00 | NULL |
+----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------------------+------+----------+-------+
-- age使用范围查找
explain select name,age,pos from staffs where name='july' and age between 23 and 30 and pos='dev'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: staffs
partitions: NULL
type: range
possible_keys: idx_staffs_nameAgePos
key: idx_staffs_nameAgePos
key_len: 140
ref: NULL
rows: 1
filtered: 33.33
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)
更改需求
-- 在上面索引的基础上,查询年龄大于23的员工信息
explain select * from staffs where age>23;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
type为all,全表扫描,没有用到索引,之前说过复合索引遵从最左前缀原则,该索引中最左前缀为name,跳过name使用age显然是不行的。
优化方案
-- 不要小菊花,需要什么拿什么
explain select name,age from staffs where age>23;
--或者如下,总之不要小菊花
explain select id from staffs where age>23;
+----+-------------+--------+------------+-------+---------------+-----------------------+---------+------+------+----------+--------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+---------------+-----------------------+---------+------+------+----------+--------------------------+
| 1 | SIMPLE | staffs | NULL | index | NULL | idx_staffs_nameAgePos | 140 | NULL | 3 | 33.33 | Using where; Using index |
+----+-------------+--------+------------+-------+---------------+-----------------------+---------+------+------+----------+--------------------------+
-- 遵照最左原则
mysql> explain select * from staffs where name='july' and age>23;
+----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | staffs | NULL | range | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 78 | NULL | 1 | 100.00 | Using index condition |
+----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
如果我按照name和pos设置条件,根据当前索引跳过了age,索引只会用到name
索引上少计算
explain select * from staffs where name='2000' and age+1=23;
+----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | staffs | NULL | ref | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 74 | const | 1 | 100.00 | Using index condition |
+----+-------------+--------+------------+------+-----------------------+-----------------------+---------+-------+------+----------+-----------------------+
没啥大问题,就是本身的索引和后面的索引会失效,但可以使用聚合函数(需要改一下索引)。
使用不等于(!= or <>)、空值、or后面的索引也会失效
varchar类型一定要带引号
不带引号会怎么样呢?表面上不会怎样,依然可以查询出来,因为mysql会做隐式类型转换,看着好像没啥,这样的语句写多了是要翻车的;隐式转换不仅会影响性能(转换也要耗费资源的,且很难发现这种错误),还会带来安全问题。
模糊查询%写最右
explain select * from staffs where name like '%ju%' and age=23;
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | staffs | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |
+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+
-- 上面的解决方案,把j前的%去掉就可以了
explain select * from staffs where name like 'ju%' and age=23;
+----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | staffs | NULL | range | idx_staffs_nameAgePos | idx_staffs_nameAgePos | 78 | NULL | 1 | 33.33 | Using index condition |
+----+-------------+--------+------------+-------+-----------------------+-----------------------+---------+------+------+----------+-----------------------+
本文中介绍的是思想,上面有很多不合理的地方,有些问题改索引是更好的解决方案。
练习
假设index(a,b,c)
where语句 | 索引是否被使用到 |
---|---|
where a = 3 | Y,使用到a |
where a = 3 and b = 5 | Y,使用到a,b |
where a = 3 and b = 5 and c = 4 | Y,使用到a,b,c |
where b = 3 或 where b = 3 and c = 4 或 where c = 4 | N |
where a = 3 and c = 5 | 使用到a,c没有被使用,b中间断了 |
where a = 3 and b > 4 and c = 5 | 使用到了a,b |
where a = 3 and b like ‘kk%’ and c = 4 | 使用到了a,b,c |
where a = 3 and b like ‘%kk’ and c = 4 | 使用到了a |
where a = 3 and b like ‘%kk%’ and c = 4 | 使用到了a |