示例表 记录数100000+
CREATE TABLE `employees` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
`age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
`position` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
`hire_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
PRIMARY KEY (`id`),
KEY `idx_name_age_position` (`name`,`age`,`position`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
1.联合索引第一个字段使用范围不会走索引
EXPLAIN SELECT * FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';
2. 联合索引第一个字段使用范围,force index强制走索引
扫描的行数rows变少了,但是需要回表
EXPLAIN SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei' AND age = 22 AND position ='manager';
-- 关闭查询缓存
set global query_cache_size=0;
set global query_cache_type=0;
-- 执行时间0.121s
SELECT * FROM employees WHERE name > 'LiLei';
-- 执行时间0.239s
SELECT * FROM employees force index(idx_name_age_position) WHERE name > 'LiLei';
3.覆盖索引优化
EXPLAIN SELECT name,age,position FROM employees WHERE name > 'LiLei' AND age = 22 AND position ='manager';
4.in和or在数据量大的时候会走索引,数据量小的时候会选择全表扫描(mysql8成本计算做了改动,数据量小的时候也会走索引)
EXPLAIN SELECT * FROM employees WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees WHERE (name = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees_copy WHERE name in ('LiLei','HanMeimei','Lucy') AND age = 22 AND position ='manager';
EXPLAIN SELECT * FROM employees_copy WHERE (name = 'LiLei' or name = 'HanMeimei') AND age = 22 AND position ='manager';
5. 模糊查询kk%一般会走索引
EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
6. 什么是索引下推?
Index Condition Pushdown,简单来说比如如下sql:
EXPLAIN SELECT * FROM employees WHERE name like 'LiLei%' AND age = 22 AND position ='manager';
联合索引(name,age,position)通常只会走name字段索引(name过滤后age和position是无序的),拿到符合name条件的主键id后回表
如果使用索引下推,联合索引(name,age,position)在索引遍历过程中,对索引包含的所有字段先做判断,过滤掉不符合条件的记录再回表,这样可以大大减少回表的次数
如果范围条件过滤后结果集较小会走索引下推,结果集较大不会走索引下推
7. mysql如何选择合适的索引?
EXPLAIN select * from employees where name > 'a' ;
EXPLAIN select * from employees where name > 'zzz' ;
mysql是否选择走索引或者一张表有多个索引mysql最终选择走哪个索引,可以用trace工具来查看详情,trace工具会影响mysql性能,所以只能临时分析sql使用,用完之后立即关闭
set session optimizer_trace="enabled=on",end_markers_in_json="on"; --开启trace
set session optimizer_trace="enabled=off"; --关闭trace
select * from employees where name > 'a' order by position;
SELECT * FROM information_schema.OPTIMIZER_TRACE;可以看出mysql计算出全表扫描的成本低于使用索引的成本,最终选择全表扫描
select * from employees where name > 'zzz' order by position;
SELECT * FROM information_schema.OPTIMIZER_TRACE;查看trace字段可知索引扫描的成本低于全表扫描,所以mysql最终选择索引扫描
8.索引设计的原则总结
1)代码先行,索引后上
主题业务功能开发完毕,把所以涉及到的相关sql都拿出来分析之后再建立索引
2)联合索引尽量覆盖条件
设计几个联合索引,尽量覆盖sql里的where、order by、group by字段,保证联合索引字段顺序满足sql查询的最左前缀原则,尽量少建单值索引
3)不要在小基数字段上建立索引
基数较小的列比如性别字段只有男女,表记录多时无法利用B+树二分查找的效率优势
4)长字符串可以采用前缀索引
尽量在字段类型较小的列建立索引,因为占用空间小,搜索性能也会高
在varchar(255)这样的字段建立索引,可以优化一下建立类似KEY index(name(20),age,position)这样的前缀索引,根据where name字段搜索时先在索引树里根据name的前20个字符搜索,定位到前20个字符匹配的数据之后再回到聚簇索引提取出完整的name字段进行比对。
索引树里name字段只包含前20个字符所以时无序的,order by name和group by name无法用到name索引
5)where和order by冲突是优先where
where条件基于索引先筛选出数据,再对筛选出的数据排序成本会小很多(因为排序数据少)
6)慢查询sql做优化
监控后台的一些慢sql,针对慢sql查询做特定的索引优化,后续我会专门针对慢sql优化做总结。