MySql索引优化

索引优化

-- 创建两个新表
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 |
+----+-------------+--------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+


  1. 通过观察key_len,可以看出第一条语句中只用到了复合索引中的name和age,因为单用name的key_len是74,name+age的key_len是78;没有用到pos,因为范围后的索引会失效。
  2. 当我只查询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 = 3Y,使用到a
where a = 3 and b = 5Y,使用到a,b
where a = 3 and b = 5 and c = 4Y,使用到a,b,c
where b = 3 或 where b = 3 and c = 4 或 where c = 4N
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值