前言

以我个人做的项目为例,因为早期开发项目上线以后用户量还不是很大,之前考虑过数据量大时是否做sql优化,但是一直因为项目还在开发中没来得及细心的调优下SQL,由于业务前期数据量比较小,基本都能满足要求,但是随着数据的订单量增长,数据量也随之增加,这时候从线上看查询sql的时间变长了,有的可能几百毫秒才能查询出来,用户体验性就变差了,这时候就需要对SQL进行优化。

下面就我所遇到的以及该怎样调优总结一下,希望对于小伙伴们有所帮助。
首先从以下几个方面对SQL进行调优:
1、sql语法优化
2、数据库表结构优化
3、SQL相关案例分析

1、SQL语法优化

sql语法优化有很多种,基本上大同小异的,以下列出来我个人接触比较多的出来。

一条完整的语句SQL语句如下:
select * from user where id = ‘1’ group by id order by id limit 0,1
我们一一讲解这条语句应该怎么优化法

select优化

不要使用 * 查询

为什么不使用select * 进行查询呢?
参考《阿里java开发手册(泰山版)》中 MySQL部分描述:

主要原因有:
1、使用 * 号查询,会查询出很多我们不需要的字段,增加sql执行的时间,同时大量的多余字段,会增加网络开销。

2、对于无用的大字段,如 text,会增加网络和IO带宽操作,由于返回的内容过大超过max_allowed_packet设置会导致程序报错,需要评估谨慎使用。

3、失去MySQL优化器“覆盖索引”策略优化的可能性
SELECT * 杜绝了覆盖索引的可能性,而基于MySQL优化器的“覆盖索引”策略又是速度极快,效率极高,业界极为推荐的查询优化方式。

使用group_concat要谨慎

select student_id,group_concat(company_id,student_id) from liancan_repast_student_order where student_id='1298585717559771137'

gorup_concat是一个字符串聚合函数,会影响SQL的响应时间,
group_concat_max_len它会限制使用group_concat返回的最大字符串长度,默认是1024,如果返回的值过大超过了max_allowed_packet设置会导致程序报错。

select中使用子查询

select student_id,(select user_name from liancan_system_personnel_information where id ='1349282137488007169') as userName  from liancan_repast_student_order where student_id='1298585717559771137'

在select后面有子查询的情况称为内联子查询,SQL返回多少行,子查询就需要执行过多少次,严重影响SQL性能。

from 优化

不建议使用子查询

比如

select a.student_id from liancan_repast_student_order a  where a.student_id in (select id from liancan_system_personnel_information where id = '1298585717559771137')

由于MySQL优化器对子查询的处理能力比较弱,所以不建议使用子查询,可以使用Inner Join来实现查询。

select a.student_id from liancan_repast_student_order a inner join (select id from liancan_system_personnel_information where id = '1298585717559771137') as b on a.member_id = b.member_id;

还有就是不太建议表的关联使用Left Join来实现,一般情况下有可能不会走索引,导致大量的数据行被扫描,消耗时间的同时SQL性能也会变差。

where优化

字段查询类型转换

比如我定义的user_id字段是varchar类型的,下面我进行查询

select id,user_id from account_tbl where user_id='1001'

因为user_id是字符串类型的,传入的是字符串可以走索引。

select id,user_id from account_tbl where user_id=1001

因为user_id是字符串类型的,传入的是int类型不走索引。

使用like %%查询优化

项目中对数据进行模糊查询是常见的,一般是用 like %张三% 或者 like %张三 的方式去实现,这样会导致一个问题是索引失效了,不会被使用到。这种方式查询数据是很慢的,那怎么去优化呢?

这里主要介绍两种方式
1、全文索引
2、生成列方式

全文索引

MySQL 5.6开始支持全文索引,可以在变长的字符串类型上创建全文索引,来加速模糊匹配业务场景的DML操作。它是一个inverted index(反向索引)。

全文索引有两种模式:
in natural language mode:自然语言模式,自然语言模式是MySQL 默认的全文检索模式。自然语言模式不能使用操作符,不能指定关键词必须出现或者必须不能出现等复杂查询。

in boolean mode:布尔模式,布尔模式可以使用操作符,可以支持指定关键词必须出现或者必须不能出现或者关键词的权重高还是低等复杂查询,可以用+,-符号,比如 + 表示必须包含 , -表示必须不包含。

这里以第一种自然语言模式为例

首先对表account_tbl的user_id添加全文索引

alter table account_tbl add fulltext index idx_full_nickname(user_id) with parser ngram;

对应上面所说的like %张三%默认情况下,优化器是不会选择走user_id这个字段索引的,SQL改为全文索引匹配的方式:match(user_id) against(‘张三’)。

select * from account_tbl where match(user_id) against('张三');

计划分析中可以看到使用了全文索引的方式查询,type为fulltext,同时命中全文索引 idx_full_nickname,从上面的分析可知在MySQL中模糊匹配%%方式查询的SQL可以通过全文索引提高效率。

生成列

MySQL 5.7引入了一个名为generated column 的新功能。又称为生成列,生成列通常又叫做虚拟列或计算列。这个生成列的值是在列定义时包含了一个计算表达式计算得到的,有两种模式:

virtual(虚拟)默认:当从表中读取记录时自动计算此列的结果并返回。
stored(存储):当向表中写入新记录时自动计算对应的值,并插入到这个列中,那么这个列会作为一个常规列存在表中。

virtual生成列比stored生成列更有用,因为一个虚拟的列不占用任何存储空间。你可以使用触发器模拟stored生成列的行为。

生成列语法如下:

col_name data_type [GENERATED ALWAYS] AS (expr)  [**VIRTUAL** | **STORED**]
[NOT NULL | NULL]

创建生成列

alter table account_tbl add reverse_user_id varchar(200) generated always as (reverse(user_id));

利用内置reverse函数将like '%张三’反转为like ‘三张%’,基于此函数添加虚拟生成列。

创建索引

alter table account_tbl add index idx_reverse_userId(reverse_user_id);

在虚拟生成列上创建索引。

查询张三

select * from account_tbl where reverse_user_id like reverse('%张三')

将SQL改写成通过生成列like reverse(’%张三’)去过滤,走生成列上的索引。

索引列被运算

当一个字段被索引,同时出现where条件后面,是不能进行任何运算,会导致索引失效。
!=或者<>(不等于),可能都会导致不走索引,也可能走 INDEX FAST FULL SCAN

select * from account_tbl where user_id = '张三' and money != 0

比如上面的money列做了索引,但是列被用作了运算,所以索引会失效。

group by优化

函数运算

比如我项目现在做的模块功能经常使用到create_time来分组显示数据并且按照时间,这就会导致一个问题是create_time走了索引,但是不能消除排序带来的CPU开销,怎么解决呢,那就是允许表中添加冗余一个字段来解决。

EXPLAIN select DATE_FORMAT(dinner_date, '%Y-%m-%d')
from liancan_repast_student_order
where dinner_date between '2020-09-01 00:00:00' and '2021-09-30 23:59:59' group by DATE_FORMAT(dinner_date, '%Y-%m-%d');

order by优化

前缀索引

order by后面的列有索引,索引可以消除排序带来的CPU开销,如果是前缀索引,是不能消除排序的。

字段顺序

排序字段顺序,asc/desc升降要跟索引保持一致,充分利用索引的有序性来消除排序带来的CPU开销。

limit优化

对于limit m, n分页查询,越往后面翻页即m越大的情况下SQL的耗时会越来越长,为了避免数据量大时扫描、读取过多的记录。

以后分页sql:

select * from order a inner join (select id from order where name=“张三1” limit 1000000,10) b on a.id=b.id;

可以利用覆盖索引快速查询出id,然后通过主键id跟原表进行Join关联查询10个数据的具体值,这样就能大量的减少数据库的IO,从而大幅提升性能。

数据库表结构优化

TEXT类型

尽量避免使用text类型,它不但消耗大量的网络和IO带宽,同时在表上的DML操作都会变得很慢,而且使用group_concat返回的结果集的大小超过max_allowed_packet限制的时候,程序会报错,建议采用es或者对象存储OSS来存储和检索。

字段 DEFAULT设置为NOT NULL

在创建表的时候,建议每个字段尽量都有默认值,禁止DEFAULT NULL,尽量将字段都添加上NOT NULL DEFAULT,如果列值存储了大量的NULL,会影响索引的稳定性。

COMMENT 填写注释

这个真的很重要,不管是自己看还是为了以后接手项目的人看都要把注释写上,我就遇到这种表中字段不标明是什么,实体也没有注释的人,我看着很头疼。

表存储引擎

如果是数据很重要的话建议使用InnoDB引擎获取更好的性能,而且支持事务操作。

主键索引

主键索引id的查询是最高的,在MySQL中尽量所有的update都使用主键id去更新,因为id是聚集索引存储着整行数据,不需要回表,性能是最高的。

前缀索引

对于变长字符串类型varchar(m),为了减少key_len,可以考虑创建前缀索引,但是前缀索引不能消除group by, order by带来排序开销。如果字段的实际最大值比m小很多,建议缩小字段长度。

alter table useradd index idx_user_id(user_id(10));

SQL相关案例分析

慢查询SQL

慢查询日志用来记录在 MySQL 中执行时间超过指定时间的查询语句。通过慢查询日志,可以查找出哪些查询语句的执行效率低,以便进行优化。

默认情况下,慢查询日志功能是关闭的。通过以下命令查看是否开启慢查询日志功能。

mysql> show variables like 'slow_query%';
+---------------------+---------------------------------------------------------------------+
| Variable_name       | Value                                                               |
+---------------------+---------------------------------------------------------------------+
| slow_query_log      | OFF                                                                 |
| slow_query_log_file | C:\ProgramData\MySQL\MySQL Server 5.7\Data\PS2019SQGNZEUR-slow.log |
+---------------------+---------------------------------------------------------------------+
2 rows in set, 1 warning (0.02 sec)mysql> show variables like 'long_query_time';
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set, 1 warning (0.01 sec)

参数说明如下:
slow_query_log:慢查询开启状态
slow_query_log_file:慢查询日志存放的位置(一般设置为 MySQL 的数据存放目录)
long_query_time:查询超过多少秒才记录

开启 MySQL 慢查询日志功能,并设置时间,命令和执行过程如下:

mysql> set global slow_query_log=ON;
Query OK, 0 rows affected (0.05 sec)mysql> set global long_query_time=0.01;
Query OK, 0 rows affected (0.00 sec)

当查询的SQL超过这个时间就会记录在慢查询日志中。

SQL执行计划分析

从上面的慢查询查找出执行效率低的SQL,通过explain来分析SQL查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。

Explain 中包含的字段意思

id 列

是 select 语句的序号,MySQL将 select 查询分为简单查询和复杂查询。

select_type

常用的有 SIMPLE 简单查询,UNION 联合查询,SUBQUERY 子查询等,表示对应行是是简单还是复杂的查询。

table

表示 explain 的一行正在访问哪个表

type

索引查询类型,经常用到的索引查询类型:
从最优到最差分别为:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > all

system

表只有一行,这是一个 const type 的特殊情况

const

在使用主键或者唯一索引进行查询的时候只有一行匹配。

eq_ref

在进行联接查询的,使用主键或者唯一索引并且只匹配到一行记录的时候

ref

使用非唯一索引

range

只有在使用主键、单个字段的辅助索引、多个字段的辅助索引的最后一个字段进行范围查询才是 range

index

只扫描索引树

all

全表扫描,不走索引

possible_keys

显示查询可能使用哪些索引来查找。

key

实现条件中可以选择的索引

key_len

显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。

ref

这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),func,NULL,字段名。

rows

扫描数据的行数,这一列是 mysql 估计要读取并检测的行数,这个不是结果集里的行数。

Extra

using index :使用覆盖索引的时候就会出现。

using where:在查找使用索引的情况下,需要回表去查询所需的数据。

using index condition:查找使用了索引,但是需要回表查询数据。

using index & using where:查找使用了索引,但是需要的数据都在索引列中能找到,所以不需要回表查询数据。

sql分析的案例:
有这么一段SQL

select id,team_id,reason,status,type,created_time,invite_id,falg_admin,file_id from t_user_msg where 1 and (team_id in (3212) and app_id is not null) or (invite_id=12395 or app_id=12395) order by created_time desc limit 0,10;

表中创建了invite_id,app_id,team_id,created_time了这四个索引。发现key 走的是create_time这个索引,而且type=index索引全扫描,其他索引没有使用上,如果按照这种查询法,数据量越大SQL查询越慢,期望走的是invite_id,app_id,team_id三个索引,但是没有效果,数据根据or拆分以后索引走是正常的。
执行完计划事这样的
±—±------------±-------------±------±--------------------------------±-----------±--------±-----±-----±------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
±—±------------±-------------±------±--------------------------------±-----------±--------±-----±-----±------------+
| 1 | SIMPLE | t_user_msg | index | invite_id,app_id,team_id | created_time | 5 | NULL | 10 | Using where |
±—±------------±-------------±------±--------------------------------±-----------±--------±-----±-----±------------+
1 row in set (0.00 sec)
从执行计划可以看到,表上有单列索引invite_id,app_id,team_id,created_time,走的是create_time的索引,而且type=index索引全扫描,因为create_time没有出现在where条件后,只出现在order by后面,只能是type=index,这也预示着表数据量越大该SQL越慢,我们期望是走三个单列索引invite_id,app_id,team_id,数据根据or拆分以后索引走是正常的。怀疑是不是app_id is not null这个的问题,因为上面也说过如果字段的值为null会导致索引也用不了。这个条件影响了优化器对最终执行计划的选择有可能。

去掉这个查询以后

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
±—±------------±-------------±------------±--------------------------------±--------------------------------±--------±-----±-----±--------------------------------------------------------------------------+
| 1 | SIMPLE | t_user_msg | index_merge | invite_id,app_id,teadm_id | team_id,invite_id,app_id | 8,9,9 | NULL | 32 | Using union(team_id,invite_id,app_id); Using where; Using filesort |

确实走了invite_id,app_id,team_id三个索引。

有两种方法可以解决,就是不让它为空。

app_id is not null 改写为IFNULL(app_id, 0) >0):

select id,team_id,reason,status,type,created_time,invite_id,falg_admin,file_id from t_user_msg where 1 and (team_id in (3212) and **IFNULL(app_id, 0) >0)**) or (invite_id=12395 or app_id=12395) order by created_time desc limit 0,10;

将字段app_id 的默认为NULL 改成NOT NULL DEFAULT 0

select id,team_id,reason,status,type,created_at,invite_id,falg_admin,file_id from t_user_msg where 1 and (team_id in (3212) and **app_id > 0)**) or (invite_id=12395 or app_id=12395) order by created_time desc limit 0,10;

最后写SQL还是要遵循MySQL开发规范避免问题的发生,这只是个案例说明,最终要根据自己实现的SQL语句按照以上的步骤找到问题的所在。

面试必备SQL调优方案相关推荐

  1. 面试中sql调优的几种方式_面试方式

    面试中sql调优的几种方式 The first question I ask someone in an interview for a cybersecurity position is, &quo ...

  2. Mysql高级调优篇——第五章:Sql调优在面试中深度剖析

    上节讲了Sql调优实战,本章聊聊面试中Sql调优深度的剖析场景! 在讲之前我们先做一些准备工作,建立一些需要用到的表: Mysql高级调优篇表补充--建表SQL_风清扬逍遥子的博客-CSDN博客⭐️t ...

  3. 读《程序员的SQL金典》[4]--SQL调优

    一.SQL注入 如果程序中采用sql拼接的方式书写代码,那么很可能存在SQL注入漏洞.避免的方式有两种: 1. 对于用户输入过滤敏感字母: 2. 参数化SQL(推荐). 二.索引 ①索引分类 聚簇索引 ...

  4. sql调优的几种方式_「数据库调优」屡试不爽的面试连环combo

    点赞再看,养成习惯,微信搜索[三太子敖丙]关注这个互联网苟且偷生的工具人. 本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点.资料以及我的 ...

  5. Mysql排序添加名词_记面试中问到的MySQL的SQL调优问题

    目录 1.场景还原 2.名词解释 3.实践情况 4.个人观点 5.引申问题 1.场景还原 面试官:小伙子,听说你会SQL调优,那我这里有一条SQL,你来帮我调优一下.SQL如下: SELECT * F ...

  6. 记面试中问到的MySQL的SQL调优问题

    目录 1.场景还原 2.名词解释 3.实践情况 4.个人观点 5.引申问题 1.场景还原 面试官:小伙子,听说你会SQL调优,那我这里有一条SQL,你来帮我调优一下.SQL如下: SELECT * F ...

  7. 绝了!超全面的Java调优方案,我司已用7年,并得到验证!(万能通用)

    很多程序员在工作中也会遇到类似高并发场景的问题,往往束手无策,来看下这些场景你是否似曾相识: 线上系统CPU.IO.内存突然被打满,接口响应时间过长 线上系统突然卡死无法访问,频繁收到GC报警 线上系 ...

  8. 2021年逆天Java调优方案出来了!直接省掉一半成本,吹爆!!!

    01 搞java的应该都听过这句话:在没有遇到性能问题的时候去优化,那就是灾难.既然遇到性能问题,开始优化的第一步就是找到性能慢的关键原因. 优化必须针对最痛的那个点,花80%努力去提升那20%,不如 ...

  9. 这套Java调优方案太顶了!同时支撑10个618大促都不是问题

    为了有条不紊的化解订单洪峰,每年京东 618 大促之前,都会对系统进行一些优化和测试,以保证订单系统.库存系统.结算系统等正常运转. 数据库系统作为重要的基础设备,就好比人的大脑记忆系统,没有了数据库 ...

最新文章

  1. Leetcode264. Ugly Number II丑数2
  2. 面试问到 Redis 事务,我脸都绿了。。
  3. Oracle 大数据集成实施
  4. 杀毒软件:看企业版与单机版之间区别
  5. spring-security问题记录
  6. 弹体飞行姿态仿真软件程序代写
  7. centos安装python3.7详细过程 2020
  8. python函数修饰参数_Python 函数参数的填坑之路
  9. 计算机调试致sa登录失败,无法打开登录 'xxxx' 中请求的数据库。登录失败。用户 'sa' 登录失败。解决思路...
  10. ROS功能包或其中一部分找不到的奇葩问题及对应解决方案
  11. Mac入门——快捷键
  12. 2清空所有表_mysql数据库学习(第十二篇) - 数据高级操作(2)
  13. 可解释人工智能XAI
  14. 计算机科学导论实验,《计算机科学导论》实验.doc
  15. 开发Web版一对一远程直播教室只需30分钟 - 使用face2face网络教室
  16. jqprint去除页眉页脚
  17. C51与4*4矩阵键盘
  18. 血氧仪语音芯片,超低功耗待机语音IC方案,可自己烧写语音WT588F
  19. 2018年GIS学术年会总结
  20. Pygame:动画精灵

热门文章

  1. 【ArcGIS】空间表无法删除的问题处理
  2. Git 使用详解,日常使用 | 什么是git
  3. MxN螺旋矩阵(由外向内)
  4. python自己的手稿四之互动沟通
  5. 有密码的PDF文件如何编辑?
  6. 国惠光电短波红外相机资料下载
  7. 帆软填报-根据角色/权限设置校验规则(含开关配置表校验)
  8. CUDA11.3以及PyTorch-GPU版本安装
  9. 基于hi3531、ffmpeg、x264的h264压缩
  10. 29 Redis 应对并发访问的无锁原子操作