第8章-8 优化技巧1

         上一篇:《第8章-7 explain》,接着学习常用的优化技巧

1,排序:

在使用order by时,经常出现Using filesort

索引做为排序时。

使用order by排序时,如果没有按照索引顶序(要完全一样),会出现Using filesort

EXPLAIN SELECT name, dep_id, age
from employee ORDER BY name, dep_id, age;

当索引引字段为常量时可以当作是存在索引的。

EXPLAIN SELECT name, dep_id, age
from employee where name = '李四' ORDER BY dep_id, age;

当使用*时order by即使使用了全部索引,也会也filesort

EXPLAIN SELECT *
from employee ORDER BY name, dep_id, age;

使用排序一升一降会造成filesort

EXPLAIN SELECT name, dep_id, age
from employee ORDER BY name, dep_id desc, age asc;

当排序出现了索引左侧列,则允许使用索引排序

左侧字段单字段排序时,索引支持升降序

explain select * from rtx_resource 
where  menu_id < 14206986 order by menu_id desc;

explain select * from rtx_resource 
where  menu_id < 14206986 order by source_id;

在多字段情况下,左侧字段必须是升序,且顺序不允许打乱

explain select * from rtx_resource 
where  menu_id < 14206986 order by menu_id,source_id;

使用group by时,使用不当,会出现Using temporary

这个规则 跟 order by相似。

EXPLAIN SELECT NAME, dep_id, age
FROM employee GROUP BY NAME, dep_id DESC, age ASC;

改成一样排序

EXPLAIN SELECT NAME, dep_id, age
FROM employee GROUP BY NAME DESC, dep_id DESC, age DESC;

少了中间的dep_id

EXPLAIN SELECT NAME, dep_id, age
FROM employee GROUP BY NAME,  age;

EXPLAIN SELECT NAME, dep_id, age
FROM employee where  GROUP BY NAME,  age;

面试题排序

说出以下语法使用索引的情况

假设建立复合索引(a,b,c),请说出以下条件是否使用到了索引及使用情况

where a = 4;

where a = 4 and b = 6;

where a = 4 and c = 5 and b = 6;

where b = 4 or b=5;

where a = 4 and c = 6;

where a = 4 and b > 5 and c=6;

where a = 4 and b like 'test%' and c=4;

where a = 4 order by b, c;

where b = 5 order a;

where b = 5 order c;

where a = 5 group by c, b;

解答:

where a = 4; 使用到索引a

where a = 4 and b = 6; 使用到索引a,b

where a = 4 and c = 5 and b = 6; 使用到索引a,b,c

where b = 4 or b=5; 有or索引失效

where a = 4 and c = 6; 使用到索引a

where a = 4 and b > 5 and c=6; 使用到索引a,b 范围右边的失效了。

where a = 4 and b like 'test%' and c=4; 使用到索引a,b, %相当于范围

where a = 4 order by b, c; 使用到a,不会有filesort

where b = 5 order by a; 用不到,order by 不会用上a,b也用不上,会有filesort

where b = 5 order by c; 用不到索引,会有filesort

where a = 5 group by c, b; 用到索引a,会生成temporary,
因为group by c, b 顺序变换了

2,分页:

大数据分页查询瓶颈

        limit offset,N 的时候,随着offset增加,语句耗时也在增加。因为MySQL是取 offset + N行记录,然后放弃前 offset 行,只返回N行记录。当offset特别大的时候,效率就非常低下。如何进行优化?

直接复制employee

CREATE TABLE staff LIKE employee;
-- 或者:
DROP TABLE IF EXISTS staff;
CREATE TABLE staff (
  id int(11) NOT NULL AUTO_INCREMENT,
  name varchar(20) DEFAULT NULL,
  dep_id int(11) DEFAULT NULL,
  age int(11) DEFAULT NULL,
  salary decimal(10,2) DEFAULT NULL,
  cus_id int(11) DEFAULT NULL,
  PRIMARY KEY (id) 
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

数据准备:

随机字符串
#随机生成一个指定个数的字符串
DELIMITER //
CREATE FUNCTION rand_str(n INT) RETURNS VARCHAR(255)
BEGIN
# 声明一个str 包含52个字母
DECLARE str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
# 记录当前是第几个
DECLARE i INT DEFAULT 0;
# 生成的结果
DECLARE res_str VARCHAR(255) DEFAULT '';
WHILE i < n DO
  SET res_str = CONCAT(res_str, SUBSTR(str,FLOOR(1 +RAND()*52),1));
  SET i=i+ 1;
END WHILE;
RETURN res_str;
END //

DELIMITER ;

报错:

This function has none of DETERMINISTIC, NO SQL, 
or READS SQL DATA in its declaration and binary logging 
is enabled (you *might* want to use the less safe
 log_bin_trust_function_creators variable)

设置:

SET GLOBAL log_bin_trust_function_creators = TRUE;

存储过程
DELIMITER //
CREATE PROCEDURE insert_staff(IN max_num INT)
BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0;
REPEAT
SET i=i+ 1;
INSERT INTO staff(NAME, dep_id, age, salary, cus_id) 
VALUES(rand_str(5), FLOOR(1 + RAND()*10), FLOOR(20 + RAND()*10),
  FLOOR(6000 + RAND()*10), FLOOR(1+RAND()*10));
UNTIL i = max_num
END REPEAT;
COMMIT;
END //


DELIMITER ;

插入数据
-- 插入1千万的数据
CALL insert_staff(10000000);

分页查询:

-- 执行耗时   : 0.018 sec
SELECT * FROM staff LIMIT 100000, 10;


-- 执行耗时   : 0.319 sec
SELECT * FROM staff LIMIT 1000000, 10;


-- 执行耗时   : 1.161 sec
SELECT * FROM staff LIMIT 3000000, 10;


-- 执行耗时   : 3.236 sec
 SELECT * FROM staff LIMIT 8000000, 10;

优化后分页查询

使用子查询优化 或者叫延迟关联 

        通过使用索引覆盖获取需要的主键,再根据主键关联原表获得需要的数据。

1.测试大数据分页查询瓶颈:
set profiling=1

select * from employees limit 100000,10; --0.17s

select * from employees limit 600000,10;  --0.46s

select * from employees limit 1200000,10; --1.31s

基数(offset)越大,语句耗时越多。

2.获取主键

         使用了索引覆盖,但employees是聚簇索引表,其叶子节点就是行记录,即使走索引覆盖,其效果也不是最理想的,因为叶子节点比较大

select emp_no from employees limit 100000,10;

        使用二级索引,走索引覆盖,其叶子节点为:emp_no + emp_no,这个叶子节点比较小,也就是最理想的索引覆盖

create index idx_emp_no on employees(emp_no);

select emp_no from employees limit 100000,10;

3.通过关联获取数据

  select * from employees m inner join 
    (select emp_no from employees limit 100000,10) s 
    on s.emp_no=m.emp_no;  -- 0.08 

select * from employees m inner join
    (select emp_no from employees limit 600000,10) s 
    on s.emp_no=m.emp_no; -- 0.21

select * from employees m inner join
    (select emp_no from employees limit 1200000,10) s 
    on s.emp_no=m.emp_no; -- 0.46

使用id限定优化

记录上一页最大的id号使用范围查询。限制是只能使用于明确知道id的情况,不过一般建立表的时候,都会添加基本的id字段,而且id必须是自增的,这为分页查询带来很多便利

 -- 执行耗时   : 0.001 sec
SELECT * FROM staff WHERE id > 8000000 LIMIT 10;

另外一种写法:

 SELECT * FROM staff WHERE id BETWEEN 8000000 AND 8000010 LIMIT 10;

记录上一页的的id值


 

设计的时候,限制到100页。其它通过搜索的方式!

3,max 优化: 使用索引

max的时候,对于其使用索引

create index idx_age on testemployee ( age ) ;

EXPLAIN SELCT max ( age) from testemployee;

4,count使用注意:

-- count (*) 统计所有包含了null

-- count (字段) 统计所有不包含了null

SELECT count (name or name is null) from employee;

5,小表驱动大表

小表驱动大表,即小的数据集驱动大得数据集

for(int i=5;....)

        小表驱动大表·

        for(int j-1000....)

类似嵌套循环

如果小的循环在外层,对于数据库连接来说就只连接5次,进行1000次操作

小表驱动大表

        如果1000在外,则需要进行1000次数据库连接,从而浪费资源,增加消耗。这就是为什么要小表驱动大表

in

-- SELECT * from employee whereid in(SELECT id from department) ;

for SELECT id from department d
   for SELECT * from employee e where e.id = d.id;

小表驱动大表

exist

SELECT * FROM employee e 
WHERE EXISTS (SELECT 1 FROM department d WHERE d.id = e.dep_id) ;

-- exist可以替代 in

for SELECT * FROM employee e

    for SELECT * FROM department d WHERE d.id = e.dep_id;

大表驱动小表

in与exist选挥

当A表中教据多于B表中的款据时,这时我门更利IN 优于EXISTS。

当B表中数据多于A表中的教据时,这时我们使用EXISTS 优于IN

        因此是使用IN还是使用EXSTS就需要根据我们的需求决定了。但是如果两张表中的数据差不多时那么是使用IN还是使用EXISTS差别不大。EXISTS子查询只返回TRUJE或FALSE,因此子查询中的SELECT *可以是SELECT 1或者其他。

要确定小表驱动大表的顺序,可以遵循以下步骤:

  1. 分析表大小:首先,你需要了解参与连接的各表的行数。可以通过 SHOW TABLE STATUS LIKE '表名'; 查询语句获取每个表的行数信息。
  2. 选择小表作为驱动表:选择行数最少的表作为驱动表,即先执行的小表。这通常意味着在 SQL 查询中的 FROM 子句中,小表应该放在前面
  3. 使用索引:确保大表上的连接字段有适当的索引。这样,当小表的数据被用来查找大表时,可以利用索引来快速定位,避免全表扫描。
  4. 考虑数据分布:除了表的行数,还要考虑数据分布情况。如果某个表虽然行数较多,但连接条件下的实际数据量很少,也可能成为较好的驱动表。
  5. 测试和调整:通过 EXPLAIN 分析查询计划,观察是否达到了预期的优化效果。可能需要多次调整表的顺序和索引,以找到最优的查询路径。

 小结:

        平时使用的时候,注意排序和分页的处理,尤其是大数据分页的,最直接的就说限制返回的数量,而不是全部数据。 小表驱动大表这个使用的时候根据实际的数据量去判断,一搬字典表,区域表,这些是小表,业务表是大表。

        上一篇:《第8章-7 explain》

        下一篇:

你可能感兴趣的:(mysql高级,数据库,mysql,优化)