MySQL中的SQL调优设计

SQL调优有哪些基本原则?

导致SQL查询效率比较低的原因,主要包括数据量,数据访问量,数据计算,SQL语句的设计几个层面

  • 减少数据量(表中数据太多可以分表,例如双11是一个小时一张订单表)
  • 减少数据访问量(将全表扫描可以调整为基于索引去查询)
  • 减少数据计算操作(将数据库中的计算拿到程序内存中计算)

SQL优化的基本逻辑是怎样的?

  • 良好SQL编码的习惯(熟悉SQL编码规范、例如关键字大写,避免使用select *)
  • 优秀SQL的编写逻辑(例如表关联时小表驱动大表)
  • 定位需要优化的慢SQL语句(耗时多长时间的SQL是慢SQL)
  • 调整优化策略并进行测试。(SQL结构上的调整、索引应用)
  • 按业务进行分库分表。(分表可以在应用逻辑中减少单表数据量)

有哪些优秀SQL编写案例?

  • 查询时尽量避免使用select *;
  • 尽量避免在where子句中使用or作为查询条件。
  • where 条件中尽量不要出现与null值的比较。
  • 避免在查询中存在隐式转换。(... where id='1')
  • 避免在where子句中使用!=或者<>操作符。
  • 使用like查询条件时应尽量避免前缀使用"%"。
  • 执行查询时尽量采用最左匹配原则。(where first_name='A' and email='A@t.com')
  • 避免在查询条件中使用一些内置的SQL函数。(MySQL8中现在可以基于SQL函数 添加索引了)
  • 假如in表达式后面的数据太多,尽量避免使用in作为查询条件。(where id in (1,2,3,4,5,....))
  • 当有多个查询条件、分组条件、排序条件时,尽量使用联合索引(组合索引)
  • 表连接时优先使用内连接(inner join),使用小表驱动大表。
  • 进行表关联的字段尽量使用相同的编码(不能一个字段utf8,一个字段utf8mb4)
  • 表设计时字段类型能用简单数据类型不用复杂类型。(例如能用 int 不用 varchar)
  • 清空表中数据可优先使用truncate.(truncate删除数据时不记录日志)
  • 插入多条数据时可考虑使用批量插入。(insert into xxx values (...),(...),(...))

如何基于慢SQL日志查询慢SQL?

线上环境,我们对SQL进行调优,首先要发现执行慢的SQL,然后再对SQL进行分析和优化。如何找到执行慢的SQL呢,可以通过慢查询日志进行分析,具体可以参考如下步骤:

  1. 开启慢查询日志(一般默认是关闭状态)
  2. 设置慢查询阀值(响应速度是多长时间被定为是慢查询)
  3. 确定慢查询日志路径(日志文件在哪里)
  4. 确定慢查询日志的文件名(具体日志文件是哪个),然后对文件内容进行分析。
  5. 打开慢查询日志文件,检查慢SQL。

实操演示:

查看慢查询日志的打开状态?

sql 复制代码
show variables like '%slow_query_log%';

默认环境下,MySQL5.7默认慢查询日志状态是关闭的(OFF)。

如何开启慢查询日志?

sql 复制代码
set global slow_query_log=ON --MySQL5.7,MySQL8.0
set slow_query_log=ON  --10.5.17-MariaDB

查看默认慢查询阈值 (默认为10秒,假如一个SQL查询耗时超过了10秒钟,就会认为是慢SQL)

sql 复制代码
show variables like '%long_query_time%';

如何设置慢查询的阈值?

MySQL5.7设置慢查询时间阀值(响应时间是多长时间是慢查询)

sql 复制代码
mysql> set  long_query_time = 1;
Query OK, 0 rows affected (0.00 sec)
  • 如何知道慢查询日志路径?

慢查询日志的路径默认是 MySQL 的数据目录

sql 复制代码
mysql> show global variables like 'datadir';
 
+---------------+------------------------+
| Variable_name | Value                  |
+---------------+------------------------+
| datadir       | /mysql/data/           |
+---------------+------------------------+
1 row in set (0.00 sec)
  • 如何知道慢查询日志的文件名?
sql 复制代码
mysql> show global variables like 'slow_query_log_file';
+---------------------+----------------+
| Variable_name       | Value          |
+---------------------+----------------+
| slow_query_log_file | mysql-slow.log |
+---------------------+----------------+
1 row in set (0.00 sec)

执行一个耗时SQL,然后查看慢SQL日志,例如

sql 复制代码
select * from employees where salary between 100 and 30000
union
select * from employees where salary between 100 and 30000

打开日志文件,可以对日志文件中的内容进行分析,常用选项说明:

Time:慢查询发生的时间

User@Host:客户端用户和IP

Query_time:查询时间

Lock_time:等待表锁的时间

Rows_sent:语句返回的行数

Rows_examined:语句执行期间从存储引擎读取的行数

如何对慢SQL查询进行分析?

工欲善其事,必先利其器,分析慢查询可以通过 explain、show profile 等工具来实现。

执行计划(Explain)是什么?

执行计划是mysql优化器对SQL进行默认调优后,给出的一种执行方案,这个方案我们可以通过explain 这个指令进行查询。例如,对select语句进行分析,并输出select执行时的详细信息,开发人员可以 基于这些信息进行有针对性的优化,例如:

sql 复制代码
mysql> explain select * from employees where employee_id<100 \G;
 ****   1.   row   ***  *
id: 1
select_type: SIMPLE
table: employees
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: NULL
rows: 1
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)

分析执行计划的目的是什么?

分析执行计划的目的就为了了解SQL的执行逻辑,例如: - 检查关联查询、嵌套查询的执行顺序. - 查询操作的具体类型 - 哪些索引可能会命中以及实际命中的索引有哪些 - 每张表可能有多少条记录参与到了查询中

说说执行计划中几个常见的字段?

  • id
    select 的序列号,有几个select 就有几个 id,id 的顺序是按 select 出现的顺序增长的。 即:id 越大语句执行的优先级越高,id相同则从上往下依次执行,id 为NULL最后执行。
sql 复制代码
explain
select last_name,salary
from employees
where employee_id=(
  select manager_id
  from employees
  where employee_id=206);

当id值相同时,优先级从上到下,例如:

sql 复制代码
explain
select m.first_name,m.salary
from employees e join employees m
on e.manager_id=m.employee_id
where e.employee_id=206
  • select_type表示的查询类型有哪些?
  1. SIMPLE : 表示查询语句不包含子查询或 union
  2. PRIMARY:表示此查询是最外层的查询
  3. UNION:表示此查询是 union 的第二个或后续的查询
  4. UNION RESULT:union 的结果
  5. DEPENDENT UNION:子查询中的UNION操作,UNION后的所有select都是DEPENDENT UNION。
  6. SUBQUERY:SELECT 子查询语句
  7. DEPENDENT SUBQUERY:子查询中的第一个SELECT,SELECT 子查询语句依赖外层查询。
  8. DERIVED: from 子句后的相对比较复杂的子查询(相当于一个临时表),当看到derivedN时,这里N表时查询id

案例分析:

select_type为SIMPLE (表示查询语句不包含子查询或 union)

sql 复制代码
explain
select * 
from employees 
where employee_id<100;

select_type为PRIMARY、SUBQUERY (PRIMARY表示最外层查询,SUBQUERY表示嵌套查询)

sql 复制代码
explain
select last_name,salary
from employees
where employee_id=(
    select manager_id
    from employees
    where employee_id=206);

select_type为 UNION(union操作)、UNION RESULT(union的结果)

sql 复制代码
explain
select first_name,hire_date,salary
from employees
where job_id='AD_VP'
union
select first_name,hire_date,salary
from employees
where  salary>15000;

select_type为DEPENDENT UNION (子查询中的UNION操作,UNION后的所有select都是DEPENDENT UNION。)

sql 复制代码
explain
select *
from employees e3
where first_name in (
         select first_name
         from employees e1
         where job_id = 'AD_VP'
         union
         select first_name
         from employees e2
         where salary > 15000
    )

select_type 为 DEPENDENT SUBQUERY

sql 复制代码
explain
select  employee_id,first_name,salary
from employees e1
where salary=(
    select max(salary)
    from employees e2
    where e1.department_id=e2.department_id);

说明,一般出现DEPENDENT SUBQUERY时,SQL的执行效率都会比较低,可以调整为多表查询,例如:

sql 复制代码
explain
select e2.department_id,e2.employee_id,e2.first_name,e2.salary
from (
select department_id,max(salary) max_salary
from employees 
group by department_id) e1 join employees e2
on e1.department_id=e2.department_id
where  e1.max_salary=e2.salary

select_type为DERIVED(这里一般表示from后面的一个衍生表-临时表)

sql 复制代码
explain
select min(avg_salary)
from (
select avg(salary) avg_salary
from employees
group by department_id) emp;
  • type表示查询数据的方式。(重点)

type是一个比较重要的一个属性,通过它可以判断出查询是全表扫描还是基于索引的部分扫描。 常用属性值如下,从上至下效率依次增强。调优时,建议type类型至少要为range,才能提高查询效率。

  1. ALL:表示全表扫描,性能最差。(数据量小时无所谓)
  2. index:表示基于索引的全表扫描,先扫描索引再扫描全表数据。
  3. range:表示使用索引范围查询。使用 >、>=、<、<=、in 等等。
  4. index_merge: 表示查询中使用到了多个索引,然后进行了索引合并
  5. ref:表示使用非唯一索引进行单值查询。
  6. eq_ref:一般情况下出现在多表 join 查询,表示前面表的每一个记录,都只能匹配后面表的一行结果。
  7. const:表示使用主键或唯一索引做等值查询,常量查询。(效率非常高)
  8. NULL:表示不用访问表,也没有索引,速度最快。(了解,例如select version())

案例分析:

type为null

sql 复制代码
explain
select now()

type 为const (基于主键或唯一键执行的查询)

sql 复制代码
explain
select *
from employees
where employee_id=206
sql 复制代码
explain
select first_name,email
from employees
where email='SKING';

type 为eq_ref (多表join,前面表的每一行记录,只能匹配后面表的一行记录)

sql 复制代码
explain
select d.department_id,d.department_name,e.first_name
from departments d join employees e on d.manager_id = e.employee_id;

type为 ref (使用非唯一索引进行的等值查询)

sql 复制代码
create index index_first_name on employees(first_name);
explain
select *
from employees
where first_name='Steven';
sql 复制代码
create index index_salary on employees (salary);
explain
select salary,first_name from employees where salary=17000;

type 为 index_merge (索引合并,同时应用两个索引)

sql 复制代码
create index index_salary on employees(salary);
explain
select first_name,hire_date,salary
from employees
where job_id='AD_VP' or salary>15000;

type 为 range (这里的range表示一个范围查询)

sql 复制代码
create index index_salary on employees (salary);
explain
select first_name,salary
from employees
where salary between 10000 and 30000;

type 为index (基于索引的全表扫描)

sql 复制代码
explain
select count(*)
from employees
group by department_id;

type 为 all (表示全表扫描)

sql 复制代码
explain
select *
from employees
  • Extra 中值 的含义是什么? Extra 表示很多额外的信息,各种操作会在 Extra 提示相关信息,常见几种如下:

"Using where" 表示查询需要通过where条件查询数据(可能没有用到索引,也可能一部分用到了索引)。

sql 复制代码
explain
select *
from hr.employees
where salary>10000;

Using index 表示查询需要通过索引,索引就可以满足所需的数据(不需要再回表查询-基于普通索引找到主键,然后再基于主键查找对应纪录,当前查询中应用了覆盖索引-select列表中的值都是索引值)。

sql 复制代码
create index index_hire_date_salary on employees(hire_date,salary);
explain
select employee_id,hire_date,salary
from hr.employees
where hire_date>'2000-03-06' and salary>10000;

Using index condition 表示查询的记录,在索引中没有完全覆盖(可能要基于where或二级索引对应的主键再次查询-回表查询)。

sql 复制代码
create index index_hire_date_salary on employees (hire_date,salary)
explain
select employee_id,hire_date,salary,commission_pct
from hr.employees
where hire_date>'2000-03-06' and salary>10000;

Using filesort 表示查询出来的结果需要额外排序,数据量小在内存,大的话在磁盘,因此有 Using filesort 建议优化。

sql 复制代码
explain
select first_name,hire_date,salary
from hr.employees
order by hire_date

Using temprorary 表示查询使用到了临时表,一般出现于去重、分组等操作(这里一般也需要优化)。

sql 复制代码
explain
select first_name,salary
from hr.employees
where first_name like 'A%'
union
select first_name,salary
from hr.employees
where first_name like 'B%'
相关推荐
2401_8576226623 分钟前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_8575893627 分钟前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
vvvae12342 小时前
分布式数据库
数据库
哎呦没2 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
雪域迷影2 小时前
PostgreSQL Docker Error – 5432: 地址已被占用
数据库·docker·postgresql
bug菌¹3 小时前
滚雪球学Oracle[4.2讲]:PL/SQL基础语法
数据库·oracle
逸巽散人3 小时前
SQL基础教程
数据库·sql·oracle
月空MoonSky3 小时前
Oracle中TRUNC()函数详解
数据库·sql·oracle
momo小菜pa3 小时前
【MySQL 06】表的增删查改
数据库·mysql