索引失效原则与查询优化
数据库调优的维度:
- 索引建立
- SQL优化(本文重点)
- my.cnf的调整(线程数,缓存等)
- 分库分表
SQL查询优化的技术从大方向上可以分为 物理查询优化,逻辑查询优化
- 物理查询优化:即通过建立索引,表连接的方式来进行优化
- 逻辑查询优化:SQL等价变换提升效率
1. 数据准备
学员表 插 50万 条, 班级表 插 1万 条。
CREATE TABLE `class` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`className` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
`monitor` INT NULL ,
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;CREATE TABLE `student` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`stuno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`classId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_class_id` FOREIGN KEY (`classId`) REFERENCES `t_class` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;命令开启:允许创建函数设置:
set global log_bin_trust_function_creators=1; # 不加global只是当前窗口有效。创建函数
保证每条数据都不同。随机产生字符串
CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
BEGIN
DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
DECLARE return_str VARCHAR(255) DEFAULT '';
DECLARE i INT DEFAULT 0;
WHILE i < n DO
SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
SET i = i + 1;
END WHILE;
RETURN return_str;
END 随机产生班级编号
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
RETURN i;
END创建存储过程
#创建往stu表中插入数据的存储过程
CREATE PROCEDURE insert_stu( START INT , max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i = i + 1; #赋值
INSERT INTO student (stuno, name ,age ,classId ) VALUES
((START+i),rand_string(6),rand_num(1,50),rand_num(1,1000));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END创建往class表中插入数据的存储过程
CREATE PROCEDURE `insert_class`( max_num INT )
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i = i + 1;
INSERT INTO class ( classname,address,monitor ) VALUES
(rand_string(8),rand_string(10),rand_num(1,100000));
UNTIL i = max_num
END REPEAT;
commit;
END#执行存储过程,往class表添加1万条数据
CALL insert_class(10000);执行存储过程,往stu表添加50万条数据
CALL insert_stu(100000,500000);# 删除索引存储过程
CREATE PROCEDURE `proc_drop_index`(dbname VARCHAR(200),tablename VARCHAR(200))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE ct INT DEFAULT 0;
DECLARE _index VARCHAR(200) DEFAULT '';
DECLARE _cur CURSOR FOR SELECT index_name FROM
information_schema.STATISTICS WHERE table_schema=dbname AND table_name=tablename AND
seq_in_index=1 AND index_name <>'PRIMARY' ;
#每个游标必须使用不同的declare continue handler for not found set done=1来控制游标的结束
DECLARE CONTINUE HANDLER FOR NOT FOUND set done=2 ;
#若没有数据返回,程序继续,并将变量done设为2
OPEN _cur;
FETCH _cur INTO _index;
WHILE _index<>'' DO
SET @str = CONCAT("drop index " , _index , " on " , tablename );
PREPARE sql_str FROM @str ;
EXECUTE sql_str;
DEALLOCATE PREPARE sql_str;
SET _index='';
FETCH _cur INTO _index;
END WHILE;
CLOSE _cur;
END
2. 索引失效的11种情况
2.1 全值匹配我最爱
explain select * from student where age = 20
explain select * from student where age = 20 and classId = 4
explain select * from student where age = 30 and classId = 4 and name = 'abcd'
上面三条sql语句的type全部为ALL
性能由好到最坏依次是: system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。(阿里巴巴开发手册要求)
加上索引
CREATE INDEX idx_age on student(age)
CREATE INDEX idx_age_classid on student(age,classId)
CREATE INDEX idx_age_classid_name on student)age,classId,name)
再次进行explain
就会使用上索引
2.2 最佳左前缀法则
要遵守最佳左前缀法则
CREATE INDEX idx_age_classid_name on student)age,classId,name)
explain select * from student where age = 30 and classId = 4 and name = 'abcd'
上述sql是可以使用到联合索引的,因为查询条件的顺序和个数都是完全匹配上索引的。
explain select * from student where classId = 4 and name = 'abcd' and age = 30
那这里为什么顺序和索引的顺序不一致了,还能使用上索引呢?因为顺序不一致,但是字段是能够匹配的上索引的字段的,所以能够使用上索引(满足总结的3)
explain select * from student where name = 'abcd' and classId = 4
此时,就用不到索引了(因为不满足下述总结的1)
explain select * from student where name = 'abcd' and age = 30
为什么这个sql又能用到索引呢?(总结的2)
总结:对于联合索引,是否能用到索引的条件
- 查询条件的字段要能够从最左边开始覆盖到索引的字段
- 如果能从最左边开始覆盖到索引,那怕中间断开了,也能使用索引,只不过使用的索引不是索引的全部
- 顺序如果与索引定义的顺序不一致也没关系,只要能满足1,优化器在底层也会给我们自动排序
2.3 主键插入顺序
对于InnoDB存储引擎的表来说,表中的实际数据都是存储在聚簇索引的叶子节点的,记录是存在数据页中,数据页和记录是按照主键值从小到大进行排序的,如果我们插入的记录的主键值是依次增大的话,那么插入的记录会依次往后排,但是如果主键值忽大忽小,那么就会存在页分裂的情况。
例如现在这个数据页已经满了,此时再插入id为9的数据
可这个数据页已经满了,再插进来咋办呢?我们需要把当前 页面分裂 成两个页面,把本页中的一些记录移动到新创建的这个页中。页面分裂和记录移位意味着什么?意味着: 性能损耗 !所以如果我们想尽量避免这样无谓的性能损耗,最好让插入的记录的 主键值依次递增 ,这样就不会发生这样的性能损耗了。
所以我们建议:让主键具有 AUTO_INCREMENT ,让存储引擎自己为表生成主键,而不是我们手动插入
但是在分布式系统中,主键一般都是代码里生成的,所以…
2.4 计算、函数、类型转换(自动或手动)导致索引失效
CREATE INDEX idx_name ON student(NAME);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
上述两个sql哪个性能更好?
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE LEFT(student.name,3) = 'abc';
由此可见是第一个更好!使用函数后,已经不能使用上索引了。上述like可以使用上索引
接下来看看这三条sql语句
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE 'abc%';
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE '%abc';
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE student.name LIKE '%abc%';
其对应的结果如下
由此可见,只有第一条可以使用上索引
CREATE INDEX idx_sno ON student(stuno);
EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno+1 = 900001;EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno = 900000;
上述两个sql,一个对字段进行了计算,一个没有,答案显而易见 不做运算的能够使用上索引
类型自动转换不能使用索引
INSERT INTO `sql_optimize`.`student`(`id`, `stuno`, `name`, `age`, `classId`) VALUES (817239817, 1111111, '123', 12, 317);
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name=123;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE name='123';
显而易见第二条sql能够使用上索引,因为name是varchar,第一条sql的name为int,第二条sql的name为字符串类型,虽然第一条也能匹配的上记录,但是是由于底层给我们使用了函数进行类型转换。
2.5 范围条件右边的列索引失效
create index idx_age_classId_name on student(age,classId,`name`)
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;
观察上述sql能否用到索引
虽然是用到索引了,但是只用到了age和classId两个字段,name字段没有用到。因为classId是范围条件,范围条件右边的列索引失效
那如果我交换查询条件classId和name的顺序呢?
EXPLAIN SELECT SQL_NO_CACHE * FROM student
WHERE student.age=30 AND student.name = 'abc' AND student.classId>20;
结果也还是一样,因为你的索引的顺序是不变的(age,classId,name
),你的sql查询条件的顺序变化了,优化器底层还是会改变查询条件的顺序来匹配索引列的顺序来使用上索引,除非改变索引列的顺序为(age,name
,classId),这样就能使用完全索引了。
下述都是属于范围查询
< <= > >= between
总结应用开发中范围查询,例如金额,日期等,设计索引时应该将这些字段放到联合索引的最后。
2.6 不等于(!= 或者<>)索引失效
create index idx_name on student(`name`)EXPLAIN SELECT * from student where name = 'abc'EXPLAIN SELECT * from student where name <> 'abc'
观察上述sql,哪个不能使用上索引
由此可见,不等于是不能使用索引的
2.7 is null可以使用索引,is not null无法使用索引
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NULL;
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age IS NOT NULL;
is not null无法使用索引
2.8 like以通配符%开头索引失效
上述最左匹配原则时有提到过
拓展:Alibaba《Java开发手册》【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决
2.9 or 前后存在非索引的列,索引失效
create index idx_age on student(age)
EXPLAIN SELECT SQL_NO_CACHE * FROM student WHERE age = 10 OR classid = 100;
可以看到possible_keys有值,但是key没有,因为age能匹配上索引,但是classId匹配不上,那为什么不用idx_age索引呢?
因为如果走了idx_age索引,后面跟上or classid = 100 ,classid没有索引,就相当于还是得走一遍全表扫描,所以idx_age还不如不走,直接走全表扫描来的更快。
create index idx_age on student(age)
此时在创建classId的索引,就使用上了索引 type为index_merge
2.10 数据库和表的字符集统一使用utf8mb4
统一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。不同的字符集进行比较前需要进行转换会造成索引失效。
3. 关联查询优化
数据准备
CREATE TABLE IF NOT EXISTS `type` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
);
#图书
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`)
);#向分类表中添加20条记录
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO TYPE(card) VALUES(FLOOR(1 + (RAND() * 20)));#向图书表中添加20条记录
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
3.1 左外连接
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` LEFT JOIN book ON type.card = book.card;
上述没有使用上索引
CREATE INDEX Y ON book(card);
给book加上索引
给type加上索引
CREATE INDEX X ON `type`(card);
我删除掉book的索引
DROP INDEX Y ON book;
可以得到book没有使用上索引
3.2 内连接
删除掉上述的book和type的索引
EXPLAIN SELECT SQL_NO_CACHE * FROM `type` INNER JOIN book ON type.card = book.card;
CREATE INDEX Y ON book(card);
CREATE INDEX X ON `type`(card);
对于内连接来说,查询优化器是可以决定谁作为驱动表,谁作为被驱动表。
现在删除book的索引
DROP INDEX Y ON book;
可以看到book的位置跑到了type的上面,即book是驱动表
结论:
1. 如果内连接有索引的话,索引给被驱动表,成本消耗是最低的。
2. 对于内连接来说,如果两个表的连接条件都存在索引的情况下,会选择小表作为驱动表。即小表驱动大表
因为join连接中,例如 A inner join B … 例如是取出A的一条数据来匹配B的所有数据,若此时B有索引,则匹配的时候,就能使用上索引,但是A是必须得全部取出来的,所以这满足了结论1
如果B的数量级很大,那么索引的优势越明显,所以小表驱动大小满足了结论2
3.3 join的底层原理
join方式连接表,本质就是各个表之间数据进行循环匹配,Mysql5.5之前,Mysql只支持一种表间关联方式,就是嵌套循环(Nested Loop Join)。如果关联表的数据量很大,则join关联的执行时间会很长。在Mysql5.5之后的版本中,Mysql通过引入BNLJ算法来优化嵌套执行。
上述我们看到了Mysql优化器会帮我们决定inner join中驱动表与被驱动表。那么对于外连接(left join,right join)Mysql优化器也会帮我们决定驱动表与被驱动表
3.3.1 Simple Nested-Loop Join
开销统计 | SNLJ |
---|---|
外表扫描次数 | 1 |
内表扫描次数 | A |
读取记录数 | A+A*B |
join次数 | B*A |
回表读取次数 | 0 (因为没有索引) |
3.3.3 Index Nested-Loop Join
Index Nested-Loop Join其优化的主要思路就是减少内层的匹配次数,所以要求被驱动表必须有索引。
开销统计 | SNLJ |
---|---|
外表扫描次数 | 1 |
内表扫描次数 | 0 |
读取记录数 | A+B(match) |
join次数 | A*Index(Height) |
回表读取次数 | B(mathc) |
3.3.3 Block Nested-Loop Join
如果存在索引,那么会使用index的方式进行join,如果join的列没有索引,被驱动表扫描的次数太多了,每次访问被驱动表,其表中的记录都会被加载到内存中,然后再从驱动表中进行匹配,匹配完之后取出内存,然后再从驱动表中取出一条数据,加载被驱动表的记录到内存中继续比较,周而复始,这种方式大大的增加了IO次数,为了减少被驱动表的IO次数,就出现了Block Nested-Loop Join。
不再是逐条获取驱动表的数据,而是一块一块的获取,存入join buffer缓冲区中,将驱动表join相关的部分数据列(大小受到join buffer的限制)缓存到join buffer中,然后全表扫描被驱动表,被驱动表的每一条记录一次性和join buffer中的所有驱动表记录进行匹配(内存中操作),将简单的嵌套循环中的多次比较合并为一次,降低了被驱动表的访问频率。
注意:
- 这里缓存的不只是关联表的列,select 后面的列也会进行缓存
- 在一个有N个join关联的SQL中会分配n-1个join buffer,所以查询的时候尽量减少不必要的字段,可以让join buffer中存放更多的列
- 所以尽量别用select * …
3.3.4 相关参数
show variables like '%optimizer_switch%'
查看block_nested_loop
的状态,默认是ON
show variables like '%join_buffer_size%'
查看join_buffer_size
的大小,默认是256K
join_buffer_size
在32位系统上可以申请4G,在64位系统上可以申请大于4G的空间(64位windows系统除外,其最大值会被截断位4G并发出警告)
3.3.5 总结
- 效率上:Index Nested-Loop Join > Block Nested-Loop Join > Simple Nested-Loop Join
- 永远使用小结果集驱动大结果集(本质就是减少外层循环数量)(小的度量单位是指 表的行数*每行大小)
- 被驱动表匹配的条件增加索引列
- 增大join buffer size的大小
- 减少驱动表不必要的字段查询(为什么是驱动表?因为如果是Block Nested-Loop Join,驱动表的查询字段也会加载到join buffer中)
3.3.6 Hash Join
4. 子查询优化
Mysql从4.1开始支持子查询,使用子查询可以进行SELECT语句的嵌套查询,即一个子查询的结果作为另一个SELECT语句的条件。子查询可以一次性完成很多逻辑上需要多个步骤才能完成的SQL操作。但是子查询的效率不高,原因如下:
- 执行子查询时,Mysql需要为内层查询语句的查询结果建立一个临时表,然后外层查询语句从临时表中查询记录,查询完毕后,再撤销这些表,这样会消耗过多的cpu和io资源,产生大量慢查询
- 子查询的结果存储的临时表,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会有影响
- 对于返回结果集比较大的子查询,其对查询性能的影响也越大
建议实际开发中,使用join操作来替代子查询
5. 排序优化
为什么在order by字段上添加索引?
Mysql中支持两种排序方式,分别是FileSort和Index排序。
- Index排序中,索引可以保证数据的有序性,不需要在进行排序,效率更高
- FileSort排序则是在内存中进行排序,占用CPU资源,如果待排序的数据较大,会产生临时文件IO到磁盘进行排序,效率低下
优化建议:
- SQL中,可以在where和order by子句中使用索引,目的是在where子句中避免全表扫描,order by子句中避免使用FileSort排序,但是,某些情况下全表扫描或者FileSort排序不一定比索引排序慢。
- 尽量使用Index排序,如果where和order by是同一个字段,则单列索引就可以满足,如果不一致,则使用联合索引
- 无法使用Index排序,则对FileSort方式进行调优
5.1 fileSort算法
- 双路排序:MySQL 4.1之前是使用双路排序 ,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和order by列 ,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出从磁盘取排序字段,在buffer进行排序,再从 磁盘取其他字段
- 单路排序 (快)从磁盘读取查询需要的 所有列 ,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。
在sort_buffer中,单路比多路要占用很多的空间,因为单路是把所有字段都取出,所以有可能取出的数据的总大小超过了sort_buffer的容量,导致每次只能去sort_buffer容量大小的数据进行排序(然后创建tmp文件,多路合并)排完后取sort_buffer容量大小,再排…导致多次IO
优化策略:
- 提高sort_buffer_size大小 Mysql5.7默认位1M,
show variables like '%sort_buffer_size%'
- 尝试提高
max_length_for_sort_data
提高这个参数会增加用改进算法的概率,但是如果设置的太高,数据总容量容易超过max_buffer_size
,明显症状就是IO增加,如果需要返回列的总长度大于max_length_for_sort_data,则使用双路,否则使用单路,该值建议在1024-8192字节之间进行调整 - order by时候建议不要使用 select * 原因见优化策略1,2条
6. group by 优化
- group by 使用索引的原则几乎跟order by一致 ,group by 即使没有过滤条件用到索引,也可以直接使用索引。
- group by 先排序再分组,遵照索引建的最佳左前缀法则
- 当无法使用索引列,增大 max_length_for_sort_data 和 sort_buffer_size 参数的设置
- where效率高于having,能写在where限定的条件就不要写在having中了
- 减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。Order by、group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。
- 包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。
7. 分页优化
优化思路1:使用order by
在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容。
EXPLAIN SELECT * FROM student t,(SELECT id FROM student ORDER BY id LIMIT 2000000,10)
a WHERE t.id = a.id;
优化思路2:该方案适用于主键自增的表,可以把Limit 查询转换成某个位置的查询 。
EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;
8. 覆盖索引
理解方式一:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了满足查询结果的数据就叫做覆盖索引。
理解方式二:非聚簇复合索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列(即建索引的字段正好是覆盖查询条件中所涉及的字段)。
简单说就是, 索引列+主键 包含 SELECT 到 FROM之间查询的列 。
查询的字段在索引中存在,即不需要回表进行查找
优点:
- 避免Innodb表进行索引的二次查询(回表)
- 可以把随机IO变成顺序IO加快查询效率(我们二级索引是有顺序的,但是如果没有索引覆盖,就得回表,从二级索引获取的主键值,在聚簇索引中不一定是连续的,所以就有可能是随机IO)
缺点:
- 索引字段的维护 总是有代价的。因此,在建立冗余索引来支持覆盖索引时就需要权衡考虑了。
具体的联合索引内容见Innodb索引还不清楚?看这一篇就够啦
9. 索引下推(ICP)
Index Condition Pushdown(ICP)是MySQL 5.6中新特性,是一种在存储引擎层使用索引过滤数据的一种优化方式。ICP可以减少存储引擎访问基表的次数以及MySQL服务器访问存储引擎的次数。
create index idx_name_stuno_age on student(name,stuno,age)
explain select * from student where name = '大帅' and stuno like '%10' and classId = 11
可以看到Extra中有Using index condition;即ICP索引下推
原理过程分析:
- name使用到了索引,在二级索引过滤name的查询条件后,然后就直接回表了吗?
- 此时并没有直接回表,因为使用到的联合索引中还包含了stuno字段,我们可以在过滤name后的数据集中进行stuno的过滤,然后再去回表
减少了回表的随机IO的次数
在不使用ICP索引扫描的过程:
storage层:只将满足index key条件的索引记录对应的整行记录取出,返回给server层
server 层:对返回的数据,使用后面的where条件过滤,直至返回最后一行
使用ICP扫描的过程:
storage层:首先将index key条件满足的索引记录区间确定,然后在索引上使用index filter进行过滤。将满足的indexfilter条件的索引记录才去回表取出整行记录返回server层。不满足index filter条件的索引记录丢弃,不回表、也不会返回server层。
server 层:对返回的数据,使用table filter条件做最后的过滤
使用前后的成本差别:
- 使用前,存储层多返回了需要被index filter过滤掉的整行记录
- 使用ICP后,直接就去掉了不满足index filter条件的记录,省去了他们回表和传递到server层的成本。
- ICP的 加速效果 取决于在存储引擎内通过 ICP筛选 掉的数据的比例
ICP的使用条件:
- 只能用于二级索引(secondary index)
- explain显示的执行计划中type值(join 类型)为 range 、 ref 、 eq_ref 或者 ref_or_null 。
- 并非全部where条件都可以用ICP筛选,如果where条件的字段不在索引列中,还是要读取整表的记录
到server端做where过滤。 - ICP可以用于MyISAM和InnnoDB存储引擎
- MySQL 5.6版本的不支持分区表的ICP功能,5.7版本的开始支持。
- 当SQL使用覆盖索引时,不支持ICP优化方法
- ICP是一定基于有回表操作的情况下的
10 一切基于成本考虑
上述说明了索引失效的很多种情况,但是实际中并不是死板的,所有的一切,是否使用索引,最终还是交由Mysql的优化器来根据成本进行决策。
举个例子:
!= 或者 <> 无法使用索引
上述结论有提到过
create index idx_age_name on student(age,name)
创建一个索引然后执行sql
explain select * from student where age <> 20
可以看到并没有使用上索引,然后我再修改一下sql
explain select age,name from student where age <> 20
可以看到已经使用上了索引,因为此时我改变查询列,完全满足索引覆盖,没必要回表,开销小,所以就使用上了索引。总之一切都是基于开销来做决定。
相关文章:

索引失效原则与查询优化
数据库调优的维度: 索引建立SQL优化(本文重点)my.cnf的调整(线程数,缓存等)分库分表 SQL查询优化的技术从大方向上可以分为 物理查询优化,逻辑查询优化 物理查询优化:即通过建立索…...

读完这篇文章你就彻底了解了什么是AES算法
目录 导论 介绍加密算法的定义和基本概念 解释加密算法在现代通信和存储系统中的重要性...

ArrayDeque类常用方法
数据结构 ArrayDeque类是 双端队列的线性实现类。 具有以下特征: ArrayDeque是采用数组方式实现的双端队列。ArrayDeque的出队入队是通过头尾指针循环,利用数组实现的。ArrayDeque容量不足时是会扩容的,每次扩容容量增加一倍。ArrayDeque可…...

Leetcode.130 被围绕的区域
题目链接 Leetcode.130 被围绕的区域 mid 题目描述 给你一个 m x n的矩阵 board,由若干字符 X和 O,找到所有被 X围绕的区域,并将这些区域里所有的 O用 X填充。 示例 1: 输入:board [[“X”,“X”,“X”,“X”],[“X…...

MySQL-四大类日志
目录 🍁MySQL日志分为4大类 🍁错误日志 🍃修改系统配置 🍁二进制日志 🍃查看二进制日志 🍃删除二进制日志 🍃暂时停止二进制日志的功能 🍁事务日志(或称redo日志) 🍁慢查…...

新加坡量子软件公司Horizon完成1810万美元A轮融资
(图片来源:网络) 近期,Horizon宣布已完成来自印度红杉资本、腾讯、SGInnovate、Pappas Capital和Expeditions Fund的1810万美元A轮投资。 Horizon是一家开发新一代编程工具的公司,总部位于新加坡,它致力…...

Spring学习(四):Scope的介绍及其失效解决方案
目录 一、spring当中有哪些scope 二、scope初始化与销毁演示 2.1 scope的初始化 2.2 scope的销毁 三、scope失效及其解决方案 3.1 scope失效演示 3.2 scope失效解决方案一:Lazy 3.3 scope失效解决方案二:设置proxyMode属性 3.4 scope失效解决…...

【学习集合--Set】
学习内容: Set集合概述Set集合实现—HashSet LinkedHashSet和TreeSet 学习产出: Set集合概述 Set中不存在值相同的节点。将两个对象e1.equals(e2),如果结果为true,或者(e1e2)内存地址相等,就认为两个对象…...

函数的参数
函数的默认实参 函数默认参数:函数的形参可以有默认值,如果我们自己传入参数,就用自己的数据,如果没有,那么用默认值 特别注意*: 如果某个位置有了默认参数,那么从这个位置往后,必…...

数组(八)-- LC[53][152] 最大子数组之和与乘积最大子数组
1 最大子数组之和 1.1 题目描述 题目链接:https://leetcode.cn/problems/maximum-subarray/ 1.2 求解思路 1. 暴力法 class Solution:def maxSubArray(self, nums: List[int]) -> int:length len(nums)max_sum float(-inf)for i in range(length):sum_sub_…...

docker2-zabbix
安装最新版docker yum remove docker docker-common docker-selinux docker-engine yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum makecache fast yum list docker-ce --showduplicates | sort -r yum install docke…...

ctcdecode安装
1.下载 https://pan.baidu.com/s/1sZMbzzYtBoT35zHtDifVqQ ,提取码:a05y。然后解压到ctcdecode文件夹中。 感谢 ctcdecode安装_huangneng0219的博客-CSDN博客 提供。 然后build.py文件中的compile_args [-O3, -DKENLM_MAX_ORDER6, -stdc11, -fPIC] …...

虚树学习小记
虚树是什么 虚树指在原树上选择需要的点和它们的LCALCALCA组成的一棵树。这样可以使在树DP时顶点数更少,从而减少时间复杂度。一般用于有多组数据且能保证所有数据访问的点的和不超过规定范围。 情景代入:SDOI2011消耗战 SDOI2011消耗战 题目大意 给…...

【C++】特殊类设计(单例模式)
文章目录一、设计模式概念二、设计一个不能被拷贝的类三、设计一个只能在堆上创建对象的类3.1 私有构造3.2 私有析构四、设计一个只能在栈上创建对象的类五、设计不能被继承的类六、单例模式❗️❗️6.1 饿汉模式6.2 懒汉模式6.2.1 线程安全问题6.2.2 新写法一、设计模式概念 …...

基于YOLOv5的水下海洋目标检测
摘要:水下海洋目标检测技术具有广泛的应用前景,可以用于海洋环境监测、海洋资源开发、海洋生物学研究等领域。本文提出了一种基于 YOLOv5 的水下海洋目标检测方法,使用数据增强方法进行了大量实验,并与其他方法进行了对比…...

磁盘这列(Raid)
RAID介绍 RAID技术通过把多个硬盘设备组合成一个容量更大的、安全性更好的磁盘阵列。把数据切割成许多区段后分别放在不同的物理磁盘上,然后利用分散读写技术来提升磁盘阵列整体的性能,同时把多个重要数据的副本同步到不同的物理设备上,从而…...

Oracle之PL/SQL存储过程与函数练习题(七)
1.创建一个存储过程,以员工号为参数,输出该员工的工资2.创建一个存储过程,以员工号为参数,修改该员工的工资。若该员工属于10号部门,则工资增加150;若属于20号部门,则工资增加200;若…...

C++入门教程||C++ 基本的输入输出||C++ 数据结构
C 基本的输入输出 C 基本的输入输出 C 标准库提供了一组丰富的输入/输出功能,我们将在后续的章节进行介绍。本章将讨论 C 编程中最基本和最常见的 I/O 操作。 C 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、…...

线性表——顺序表
文章目录一:线性表二:顺序表1:概念与结构1:静态顺序表2:动态顺序表2:动态顺序表的代码实现1:结构2:接口实现1:初始化2:释放内存3:检查容量4&#…...

第六章 Vite4+Vue3+Vtkjs 模型颜色切换、漫反射曲面颜色
一、介绍 💥 💥 Vtk里面工具非常的齐全,但是相关的文档又少之又少,只能花大量时间去阅读源码。漫反射曲面颜色是什么意思呢,Vtk可以使用漫反射曲面颜色来模拟光线在表面反射时的颜色。漫反射是一种光线与表面发生碰撞后,被散射到各个方向的现象,这种现象可以用来解释物…...

【QT学习七】QTreeWidget
目录 一、QTreeWidget 概述 二、QTreeWidget 的基本使用 2.1、创建 QTreeWidget 控件 2.2、设置 QTreeWidget 的大小和位置 2.3、设置 QTreeWidget 的列数和列标题 2.4、添加节点 2.5、读取节点 2.6、设置节点数据 2.7、自定义节点样式 三、注意事项 四、完整示例 一…...

【Linux】组管理和权限管理
目录1 Linux组的基本介绍2 文件/目录所有者2.1 查看文件的所有者2.2 修改文件所有者3 组的创建3.1 基本指令3.2 应用实例4 文件/目录 所在组4.1 查看文件/目录所在组4.2修改文件/目录所在的组5 其他组6 改变用户所在组6.1 改变用户所在的组6.2 应用实例7 权限介绍8 rwx权限详解…...

从零到一发布 NPM 包
如果你负责前端的基础能力建设,发布各种功能/插件包犹如家常便饭,所以熟悉对 npm 包的发布与管理是非常有必要的,故此有了本篇总结文章。本篇文章一方面总结,一方面向社区贡献开箱即用的 npm 开发、编译、发布、调试模板ÿ…...

uniapp国际化配置
1、创建资源文件 创建一个locale文件夹,新增index.js,en.json,zh-hans.json 2.配置locale文件夹中的index.js文件 import Vue from vue import VueI18n from vue-i18n// v8.x import en from ./en.json import zhHans from ./zh-Hans.json import zhHant from .…...

前端中 try-catch 捕获不到哪些异常和常见错误
在开发过程中,我们的目标是 0error,0warning。 但有很多因素并不是我们可控的,为了避免某块代码的错误,影响到其他模块或者整体代码的运行,我们经常会使用try-catch模块来主动捕获一些异常或者错误。 比如我们在获取…...

javaEE 初阶 — 如何构造一个 HTTP 请求
文章目录使用 form 表单标签构造1 构造 GET 请求2 构造 POST 请求使用 ajax 构造1 什么是异步2 代码中如何使用 ajax使用第三方工具构造1 postman 工具的安装2 postman 工具的使用使用 form 表单标签构造 1 构造 GET 请求 使用 form 表单构造 HTTP 请求,需要用到两…...

CentOS 7下安装PostgreSQL 15版本数据库(图文详细)
文章目录CentOS 7下安装PostgreSQL 15版本数据库(图文详细)1 简介1.1 概述1.2 官网2 PostgreSQL安装2.1 选定版本2.2 安装依赖2.3 执行安装2.4 初始化2.5 配置环境变量2.6 创建数据库2.6.1 进入命令行2.6.2 创建DB2.6.3 设置密码2.7 配置远程2.8 测试链接3 pgAdmin4工具安装3.1…...

代码随想录算法训练营第五十一天 | 309. 最佳买卖股票时机含冷冻期、714. 买卖股票的最佳时机含手续费
309. 最佳买卖股票时机含冷冻期 动规五部曲 1、确定dp数组以及下标的含义 dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。 具体可以区分出如下四个状态: 状态一:持有股票状态(今天买入股票,或者是…...

中英文拼写检测纠正开源项目使用入门 word-checker 1.1.0
项目简介 word-checker 本项目用于单词拼写检查。支持英文单词拼写检测,和中文拼写检测。 特性说明 可以迅速判断当前单词是否拼写错误 可以返回最佳匹配结果 可以返回纠正匹配列表,支持指定返回列表的大小 错误提示支持 i18n 支持大小写、全角半角…...

面试如果还不会Netty,看这篇文章就够了
我们去面试的时候,经常被问到netty的题目。我整理了netty的32连问。小伙伴们,收藏起来慢慢看吧。 1. Netty是什么,它的主要特点是什么? Netty是一个高性能、异步事件驱动的网络编程框架,它基于NIO技术实现࿰…...