MySQL 索引优化
MySQL 索引优化实战
索引可以包含一列或多列,如果是多列,列的顺序也十分重要;创建一个包含两列的索引,和创建两个一列的索引是大不相同的。
目录
- 索引分类
- 索引命名规范
- 测试表SQL
- 索引设计原则
- 索引优化实战
1、索引分类
- 主键索引(Primary Key)
- 唯一索引(Unique Key)
- 普通索引(Normal Key)
2、索引命名规范
- 主键索引 pk_字段名
- 唯一索引 uk_字段名
- 普通索引 idx_字段名
3、测试表SQL
其中包含三个索引:(1)
ID
主键索引;(2)USERNAME
列唯一索引;(3)ID
,USERNAME
,NAME
组合普通索引
CREATE TABLE `org_user` (
`ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`USERNAME` varchar(64) NOT NULL COMMENT '用户名',
`NAME` varchar(128) DEFAULT NULL COMMENT '姓名',
`AGE` tinyint(4) DEFAULT NULL COMMENT '年龄',
PRIMARY KEY (`ID`),
UNIQUE KEY `uk_username` (`USERNAME`) USING BTREE,
KEY `idx_id_username_name` (`ID`,`USERNAME`,`NAME`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1016 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
INSERT INTO `org_user` VALUES ('1000', 'muwq', '木婉清', '25');
INSERT INTO `org_user` VALUES ('1001', 'wang', '王语嫣', '23');
INSERT INTO `org_user` VALUES ('1002', 'ren', '任盈盈', '17');
INSERT INTO `org_user` VALUES ('1003', 'cheng', '程灵素', '14');
INSERT INTO `org_user` VALUES ('1004', 'zhou', '周芷若', '16');
INSERT INTO `org_user` VALUES ('1005', 'yulz', '俞莲舟', '19');
INSERT INTO `org_user` VALUES ('1006', 'song', '宋远桥', '20');
INSERT INTO `org_user` VALUES ('1007', 'yudy', '俞岱岩', '18');
INSERT INTO `org_user` VALUES ('1008', 'zhangsx', '张松溪', '26');
INSERT INTO `org_user` VALUES ('1009', 'zhangcs', '张翠山', '15');
INSERT INTO `org_user` VALUES ('1010', 'yin', '殷梨亭', '12');
INSERT INTO `org_user` VALUES ('1011', 'mo', '莫声谷', '21');
INSERT INTO `org_user` VALUES ('1012', 'qiao', '乔峰', '18');
INSERT INTO `org_user` VALUES ('1013', 'xu', '虚竹', '19');
INSERT INTO `org_user` VALUES ('1014', 'duan', '段誉', '20');
INSERT INTO `org_user` VALUES ('1015', 'murf', '慕容复', '21');
4、索引设计原则
- 适合加索引的列是where子句中的列,或者连接子句中指定的列
- 基数较小的类和区分度小的字段,索引效果较差,不宜建立索引
- 使用短索引,如果对长字符串增加索引,应指定一个前缀,能节省索引空间
- 更新频繁的字段不宜建立索引
- 多表关联时,要保证关联字段上一定有索引
- 不要过度索引
5、索引优化实战
有时候,表虽然有索引,但是并没被优化器选择使用。
- 查看索引使用情况:
show status like 'Handler_read%'
- Handler_read_key 如果索引正在工作,Handler_read_key的值将很高。越高越好
- Handler_read_rnd_next 数据文件中读取下一行的请求数。越低越好
返回数据占比越低,越容易命中索引。
- 如果查询的数据与全部数据的占比,大于 30%,那么索引的效果可能不好。
前导模糊查询,不会命中索引
- 反例:
EXPLAIN SELECT * FROM org_user WHERE USERNAME LIKE '%wang';
- 正例:
EXPLAIN SELECT * FROM org_user WHERE USERNAME LIKE 'wang%';
- 反例:
数据类型出现隐式转换,不会命中索引。尤其是字符串,要 ‘’ 引起来
- 反例:
EXPLAIN SELECT * FROM org_user WHERE USERNAME = 1;
- 正例:
EXPLAIN SELECT * FROM org_user WHERE USERNAME = '1';
- 反例:
复合索引,查询条件不满足索引最左原则,不会命中索引
ALTER TABLE user ADD INDEX index_name (name,age,status);
- 注意下边两句,最左原则不是查询条件的顺序,而是索引字段顺序
EXPLAIN SELECT * FROM `org_user` WHERE `NAME` = '宋远桥' AND USERNAME = 'song';
EXPLAIN SELECT * FROM `org_user` WHERE USERNAME = 'song' AND `NAME` = '宋远桥';
- 反例:
EXPLAIN SELECT * FROM `org_user` WHERE `NAME` = '宋远桥';
union、in、or都能够命中索引,建议使用in
- union:
EXPLAIN SELECT * FROM org_user WHERE USERNAME = 'wang' UNION ALL SELECT * FROM org_user WHERE USERNAME = 'song';
- in:
EXPLAIN SELECT * FROM org_user WHERE USERNAME IN ('wang', 'song');
- or:
EXPLAIN SELECT * FROM org_user WHERE USERNAME = 'wang' OR USERNAME = 'song';
- union:
用or分割开的条件
如果or前的条件中列有索引,而后面的列中没有索引,那么涉及到的索引都不会被用到。
因为or后面的条件列中没有索引,那么后面的查询肯定要走全表扫描,在存在全表扫描的情况下,就没有必要多一次索引扫描增加IO访问。EXPLAIN SELECT * FROM org_user WHERE USERNAME = 'wang' OR AGE = 18;
负向条件查询不能使用索引,可以优化为in查询
负向条件有:!=、<>、not in、not exists、not like等。测试环境:MySQL 8.0
- 经测试
!=、<>、not in
可以命中索引:EXPLAIN SELECT * FROM org_user WHERE `USERNAME` != 'wang';
EXPLAIN SELECT * FROM org_user WHERE `USERNAME` <> 'wang';
EXPLAIN SELECT * FROM org_user WHERE `USERNAME` not in ('wang', 'admin');
- 负向条件
not like、not exists
不能命中缓存:EXPLAIN SELECT * FROM org_user WHERE NOT EXISTS (SELECT * FROM org_user WHERE USERNAME = 'wang');
EXISTS 语法:如上,如果子句返回 true,则查询全部;如果返回 false,则查询结果为空
- 经测试
范围条件查询可以命中索引
范围条件有:<、<=、>、>=、between等。
ALTER TABLE user ADD INDEX idx_age (AGE);
- 单条件查询
EXPLAIN SELECT * FROM org_user WHERE AGE > 18;
- 会命中:idx_age
- 多条件查询
EXPLAIN SELECT * FROM org_user WHERE AGE > 18 AND ID > 1000;
- 会命中:主键ID索引 或者 idx_age
- 如果有两个范围列,则不能同时使用索引
- 优先原则
EXPLAIN SELECT * FROM org_user WHERE ID > 1000 AND AGE = 18;
- 会命中:idx_age(优先匹配等值查询列的索引)
数据库执行计算不会命中索引
计算逻辑应该尽量放到业务层处理,节省数据库的CPU的同时最大限度的命中索引。
EXPLAIN SELECT * FROM org_user WHERE AGE > 18;
EXPLAIN SELECT * FROM org_user WHERE AGE+1 > 18;
利用覆盖索引进行查询,避免回表
EXPLAIN SELECT USERNAME, `NAME` FROM org_user WHERE USERNAME = 'wang';
- Extra 为 Using Index 代表从索引中查询。
建立索引的列,不允许为 NULL
IS NULL
可以命中索引IS NOT NULL
不能命中索引