查询优化

1.批量数据脚本

1.往表里插入50W数据

1.建表

  1. CREATE TABLE `dept` (
  2. `id` INT(11) NOT NULL AUTO_INCREMENT,
  3. `deptName` VARCHAR(30) DEFAULT NULL,
  4. `address` VARCHAR(40) DEFAULT NULL,
  5. ceo INT NULL ,
  6. PRIMARY KEY (`id`)
  7. ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
  8. CREATE TABLE `emp` (
  9. `id` INT(11) NOT NULL AUTO_INCREMENT,
  10. `empno` INT NOT NULL ,
  11. `name` VARCHAR(20) DEFAULT NULL,
  12. `age` INT(3) DEFAULT NULL,
  13. `deptId` INT(11) DEFAULT NULL,
  14. PRIMARY KEY (`id`)
  15. #CONSTRAINT `fk_dept_id` FOREIGN KEY (`deptId`) REFERENCES `t_dept` (`id`)
  16. ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

2.设置参数log_bin_trust_function_creators

创建函数,假如报错:This function has none of DETERMINISTIC……
由于开启过慢查询日志,因为我们开启了 bin-log, 我们就必须为我们的function指定一个参数。
Mysql开启log-bin日志

  • 1、检查是否开启
  • 检查是否开启show global variables like ‘%log_bin%’; log_bin 为 on 则已开启,为 off则未开启。
  • 查询所有日志文件show binary logs;
  • 查询最近日志文件show master status;

  • ```plsql show variables like ‘log_bin_trust_function_creators’;

set global log_bin_trust_function_creators=1;

**这样添加了参数以后,如果mysqld重启,上述参数又会消失,永久方法:**
```plsql
windows下my.ini[mysqld]加上log_bin_trust_function_creators=1

linux下    /etc/my.cnf下my.cnf[mysqld]加上log_bin_trust_function_creators=1

3.创建函数,保证每条数据都不同

随机产生字符串

create function rand_string(n int)returns varchar(255)
begin
    declare char_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(char_str,floor(1+rand()*52),1));
    set i=i+1;
    end while;
    return return_str;
end
select rand_string(5);

随机产生部门编号

用于随机产生多少到多少的编号

create function rand_num(form_num int,to_num int)returns int(11)
begin
    declare i int default 0;
    set i=floor(form_num+rand()*(to_num-form_num+1));
    return i;
end
select rand_num(1,5);
drop function rand_num;

4.创建存储过程

show variables like ‘autocommit’; 是否开启自动提交
set autocommit=0;关闭自动提交
select @@autocommit;

1.创建往emp表中插入数据的存储过程
delimiter $$
create procedure insert_emp(start_num int,max_num int)
begin
    declare i int default 0;
    set autocommit =0;
    repeat
    set i=i+1;
    insert into emp(empno,name,age,deptid)values((start_num+i),rand_string(6),rand_num(30,50),rand_num(1,10000));
    until i=max_num
    end repeat;
    commit;
end $$
delimiter ;
call insert_emp(1,5);
select * from emp;
truncate table emp;
drop procedure insert_emp;

2.创建往dept表中插入数据的存储过程

执行存储过程,往dept表添加随机数据

delimiter $$
create procedure insert_dept(in max_num int)
begin
    declare i int default 0;
    set autocommit=0;
    repeat
    set i=i+1;
    insert into dept(deptName,address,ceo)values(rand_string(8),rand_string(10),rand_num(1, 15000));
    until i=max_num
    end repeat;
    commit;
end $$
delimiter;
call insert_dept(5);
select * from dept;
truncate table dept;
drop PROCEDURE insert_dept;

3.调用存储过程
    **dept**
执行存储过程,往dept表添加1万条数据
delimiter;
call insert_dept(10000);
select count(1) from dept;
    ![image.png](https://cdn.nlark.com/yuque/0/2022/png/25797418/1651991938186-9096470d-505d-4ccb-ae1c-80a060dfdc8d.png#clientId=uf77c771b-1fcb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=119&id=u0c7bafca&margin=%5Bobject%20Object%5D&name=image.png&originHeight=134&originWidth=806&originalType=binary&ratio=1&rotation=0&showTitle=false&size=7996&status=done&style=none&taskId=u5767e9b1-75cf-4378-814e-94320f7ea4f&title=&width=716.4444444444445)<br />**emp**
执行存储过程,往emp表添加50万条数据
delimiter;
call insert_emp(100000,500000);
select count(1) from emp;

image.png

2.批量删除某个表上的所有索引

1.存储过程

DELIMITER $$
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=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.执行存储过程

CALL proc_drop_index("dbname","tablename");

2.单表使用索引及常见索引失效

1.案例(索引失效)

1.全值匹配我最爱
 系统中经常出现的sql语句如下:  
 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30  
 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4
 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'  
索引应该如何建立 ?
    **建立索引**
create index  idx_age_deptid_name on emp(age,deptid,name);

建立索引前
image.png
索引后
image.png

2.最佳左前缀法则
如果系统经常出现的sql如下:
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30   AND emp.name = 'abcd'   
或者
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.deptid=1   AND emp.name = 'abcd'   
那原来的idx_age_deptid_name 还能否正常使用?
    **如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列**。

1.中间少了一项,与索引不一致

虽然可以正常使用,但是只有部分被使用到了。
image.png

2.最左的一项缺少

完全没有使用上索引
image.png

3. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
这两条sql哪种写法更好
create index  name_idex on emp(name);
EXPLAIN   SELECT SQL_NO_CACHE * FROM emp WHERE   emp.name  LIKE 'abc%' 
EXPLAIN   SELECT SQL_NO_CACHE * FROM emp WHERE   LEFT(emp.name,3)  = 'abc'

1.第一种:

image.png

2.第二种

image.png

4.存储引擎不能使用索引中范围条件右边的列
如果系统经常出现的sql如下:
 ELECT  SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.deptId>20 AND emp.name = 'abc' ; 
那么索引 idx_age_deptid_name这个索引还能正常使用么?

image.png
如果这种sql 出现较多,应该建立:

create index idx_age_name_deptid on emp(age,name,deptid);
drop index idx_age_name_deptid on emp;

image.png

5.mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
CREATE INDEX idx_name ON emp(name);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name =  'abc';
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <>  'abc';
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name !=  'abc';
drop idx_name on emp;

image.png

6. is not null 也无法使用索引,但是is null是可以使用索引的
UPDATE emp SET age =NULL WHERE id=123456;
#下列哪个sql语句可以用到索引
EXPLAIN SELECT * FROM emp WHERE age IS NULL;
EXPLAIN SELECT * FROM emp WHERE age IS NOT NULL;

image.png

7.like以通配符开头(‘%abc…’)mysql索引失效会变成全表扫描的操作
create index  age_name_idex on emp(age,name);
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name like 'abc%';
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name like '%abc%';
drop index age_name_idex on emp;

image.png

8. 字符串不加单引号索引失效
CREATE INDEX idx_name ON emp(name);
drop idx_name on emp;
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name =  '123';
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name =  123;

image.png

9.小总结

假设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, c不能用在范围之后,b断了
where a is null and b is not null is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,但是 b不可以使用
where a <> 3 不能使用索引
where abs(a) =3 不能使用 索引
where a = 3 and b like ‘kk%’ and c = 4 Y,使用到a,b,c
where a = 3 and b like ‘%kk’ and c = 4 Y,只用到a
where a = 3 and b like ‘%kk%’ and c = 4 Y,只用到a
where a = 3 and b like ‘k%kk%’ and c = 4 Y,使用到a,b,c

2.一般性建议

1.对于单键索引,尽量选择针对当前query过滤性更好的索引

2.在选择组合索引的时候,当前Query中过滤性最好的字段在索引字段顺序中,位置越靠前越好。

3.在选择组合索引的时候,尽量选择可以能够包含当前query中的where字句中更多字段的索引

4.在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面

5.书写sql语句时,尽量避免造成索引失效的情况

3.关联查询优化

1.建表SQL

CREATE TABLE IF NOT EXISTS `class` (
`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`)
);
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
select * from class; 
#####################
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)));
select * from book;

2. 案例

1.下面开始explain分析

EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
结论:type 有All

image.png

2.添加索引优化

ALTER TABLE `book` ADD INDEX Y ( `card`);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显

image.png
这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引

# 删除旧索引 + 新建 + 第3次explain
DROP INDEX Y ON book;
ALTER TABLE class ADD INDEX X (card);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;

image.png

3.建议

1、保证被驱动表的join字段已经被索引

2、left join 时,选择小表作为驱动表,大表作为被驱动表。

3、inner join 时,mysql会自己帮你把小结果集的表选为驱动表。

4、子查询尽量不要放在被驱动表,有可能使用不到索引。

5、能够直接多表关联的尽量直接关联,不用子查询。

4.子查询优化

1.尽量不要使用not in 或者 not exists

取所有不为掌门人的员工,按年龄分组 ,每个年龄段多少人

SELECT SQL_NO_CACHE age,count(*)  FROM emp a WHERE  id  NOT  IN(SELECT ceo FROM dept b2 WHERE ceo IS NOT NULL) group by age having count(*)<10000

image.png
image.png

1. 2.用left outer join on xxx is null 替代

image.png

2.用in 还是exists

https://blog.csdn.net/weixin_41979002/article/details/118730611

1.有索引的情况下 用 inner join 是最好的 其次是 in ,exists最糟糕

2.无索引的情况下用

小表驱动大表 因为join 方式需要distinct ,没有索引distinct消耗性能较大
所以 exists性能最佳 in其次 join性能最差?

3.无索引的情况下大表驱动小表

in 和 exists 的性能应该是接近的 都比较糟糕 exists稍微好一点 超不过5% 但是inner join 优于使用了 join buffer 所以快很多 如果left join 则最慢

5.排序分组优化

1.case

create index idx_age_deptid_name on emp (age,deptid,name)
以下  是否能使用到索引,能否去掉using filesort
1、explain select SQL_NO_CACHE * from emp order by age,deptid; 
2、explain  select SQL_NO_CACHE * from emp order by age,deptid limit 10; 
#无过滤 不索引
3、explain  select * from emp where age=45 order by deptid;
4、explain  select * from emp where age=45 order by   deptid,name; 
5、explain  select * from emp where age=45 order by  deptid,empno;
6、explain  select * from emp where age=45 order by  name,deptid;
7、explain select * from emp where deptid=45 order by age;
#顺序错,必排序
8、  explain select * from emp where age=45 order by  deptid desc, name desc ;
9、 explain select * from emp where age=45 order by  deptid asc, name desc ;
#方向反 必排序

2. ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序

3.索引的选择

1.执行案例前先清除emp上的索引,只留主键

call proc_drop_index(“test”,”emp”);

2.索引的选择

查询 年龄为30岁的,且员工编号小于101000的用户,按用户名称排序

SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;
explain SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;

image.png
结论:很显然,type 是 ALL,即最坏的情况Extra 里还出现了 Using filesort,也是最坏的情况。优化是必须的

1.开始优化:

思路: 尽量让where的过滤条件和排序使用上索引
但是一共两个字段(deptno,empno)上有过滤条件,一个字段(ename)有索引
1、我们建一个三个字段的组合索引可否?
CREATE INDEX idx_age_empno_name ON emp(age,empno,NAME);
image.png
我们发现using filesort 依然存在,所以name 并没有用到索引。原因是因为empno是一个范围过滤,所以索引后面的字段不会再使用索引了!
所以我们建一个3值索引是没有意义的 那么我们先删掉这个索引,
DROP INDEX idx_age_empno_name ON emp
为了去掉filesort我们可以把索引建成
CREATE INDEX idx_age_name ON emp(age,NAME);
也就是说empno 和name这个两个字段我只能二选其一。这样我们优化掉了 using filesort。
image.png

优化前:

image.png
image.png

优化后:

image.png
image.png
速度果然提高了4倍。

2.但是 如果我们选择那个范围过滤,而放弃排序上的索引呢

1.建立
DROP INDEX idx_age_name ON emp
create index idx_age_eno on emp(age,empno);

image.png
果然出现了filesort,而且type还是range光看字面其实并不美好。

SELECT SQL_NO_CACHE * FROM emp WHERE age =30 AND empno <101000 ORDER BY NAME ;

image.png
image.png
结果竟然有 filesort的 sql 运行速度,超过了已经优化掉 filesort的 sql ,而且快了好多倍。何故?
原因所有的排序都是在条件过滤之后才执行的,所以如果条件过滤了大部分数据的话,几百几千条数据进行排序其实并不是很消耗性能,即使索引优化了排序但实际提升性能很有限。 相对的 empno<101000 这个条件如果没有用到索引的话,要对几万条的数据进行扫描,这是非常消耗性能的,所以索引放在这个字段上性价比最高,是最优选择

2.结论:

当范围条件和group by 或者 order by 的字段出现二选一时 ,优先观察条件字段的过滤数量,如果过滤的数据足够多,而需要排序的数据并不多时,优先把索引放在范围字段上。反之,亦然

4.如果不在索引列上,filesort有两种算法: mysql就要启动双路排序和单路排序

1.双路排序

1 .MySQL 4.1之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和orderby列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出<br />    2. 从磁盘取排序字段,在buffer进行排序,再从磁盘取其他字段。<br />  3.取一批数据,要对磁盘进行了两次扫描,众所周知,I\O是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。

2.单路排序

从磁盘读取查询需要的所有列,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。

3。结论及引申出的问题

由于单路是后出的,总体而言好过双路   但是**用单路有问题**<br />**在sort_buffer中,方法B比方法A要多占用很多空间,因为方法B是把所有字段都取出, 所以有可能取出的数据的总大小超出了sort_buffer的容量,导致每次只能取sort_buffer容量大小的数据,进行排序(创建tmp文件,多路合并),排完再取取sort_buffer容量大小,再排……从而多次I/O。本来想省一次I/O操作,反而导致了大量的I/O操作,反而得不偿失。**

4.优化策略

1.增大sort_buffer_size参数的设置

2.增大max_length_for_sort_data参数的设置

3.减少select 后面的查询的字段。

4.Why

提高Order By的速度

1. Order by时select * 是一个大忌只Query需要的字段, 这点非常重要。在这里的影响是:

1.1 当Query的字段大小总和小于max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法——单路排序, 否则用老算法——多路排序。
1.2 两种算法的数据都有可能超出sort_buffer的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是用单路排序算法的风险会更大一些,所以要提高sort_buffer_size。

2. 尝试提高 sort_buffer_size

不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的 1M-8M之间调整

3. 尝试提高 max_length_for_sort_data

提高这个参数, 会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘I/O活动和低的处理器使用率. 1024-8192之间调整

5.GROUP BY关键字优化

  **group by 使用索引的原则几乎跟order by一致 ,唯一区别是groupby 即使没有过滤条件用到索引,也可以直接使用索引。**

1.group by实质是先排序后进行分组,遵照索引建的最佳左前缀

2.当无法使用索引列,增大max_length_for_sort_data参数的设置+增大sort_buffer_size参数的设置

3.where高于having,能写在where限定的条件就不要去having限定了。

6. 最后使用索引的手段:覆盖索引

1.什么是覆盖索引?

简单说就是,select 到 from 之间查询的列 <=使用的索引列+主键
image.png
explain select * from emp where name like ‘%abc’;
image.png
使用覆盖索引前
image.png
使用覆盖索引后
image.png

7.分页查询的优化—-limit

EXPLAIN SELECT SQL_NO_CACHE FROM emp ORDER BY deptno LIMIT 10000,40
image.png
那我们就给deptno这个字段加上索引吧。
image.png
然并卵。
优化: 先利用覆盖索引把要取的数据行的主键取到,然后再用这个主键列与数据表做关联:(查询的数据量小了后)
EXPLAIN SELECT SQL_NO_CACHE
FROM emp INNER JOIN (SELECT id FROM emp e ORDER BY deptno LIMIT 10000,40) a ON a.id=emp.id
image.png
最后比较一下查询速度:
优化前:
image.png
优化后:
image.png

实践证明:
①、order by 后的字段(XXX)有索引
②、sql 中有 limit 时,
当 select id 或 XXX字段索引包含字段时 ,显示 using index
当 select 后的字段含有 bouder by 字段索引不包含的字段时,将显示 using filesort

8.尽量不要使用 distinct 关键字去重:优化

t_mall_sku 表
id shp_id kcdz
——— ——— ——————————
3 1 北京市昌平区
4 1 北京市昌平区
5 5 北京市昌平区
6 3 重庆
8 8 天津
例子:select kcdz form t_mall_sku where id in( 3,4,5,6,8 ) 将产生重复数据,
使用 distinct 关键字去重消耗性能:select distinct kcdz form t_mall_sku where id in( 3,4,5,6,8 )
优化: select kcdz form t_mall_sku where id in( 3,4,5,6,8 ) group by kcdz 能够利用到索引