【大话Mysql面试】-SQL语句优化
发布日期:2021-06-29 15:36:01 浏览次数:2 分类:技术文章

本文共 7047 字,大约阅读时间需要 23 分钟。

五、SQL优化

5.1 如何定义以及优化SQL语句的性能问题?创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?

对于低性能的SQL语句的定位,最重要也是最有效的方法就是使用执行计划,Mysql提供了explain命令来查看语句的执行计划。 我们知道,不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行执行的过程中都会做很多相关的优化。对于查询语句,最重要的优化方式就是使用索引,而执行计划,就是显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用的索引的相关信息等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PvkzHamz-1616914648154)(E:/笔记/JAVA/Java复习框架-数据库/Mysql/imgs_mysql/9.png)]

执行计划包含的信息 id 有一组数字组成。表示一个查询中各个子查询的执行顺序;

  • id相同执行顺序由上至下。
  • id不同,id值越大优先级越高,越先被执行。
  • id为null时表示一个结果集,不需要使用它查询,常出现在包含union等查询语句中。

select_type 每个子查询的查询类型,一些常见的查询类型。

id select_type description
1 SIMPLE 不包含任何子查询或union等查询
2 PRIMARY 包含子查询最外层查询就显示为 PRIMARY
3 SUBQUERY 在select或 where字句中包含的查询
4 DERIVED from字句中包含的查询
5 UNION 出现在union后的查询语句中
6 UNION RESULT 从UNION中获取结果集,例如上文的第三个例子

table 查询的数据表,当从衍生表中查数据时会显示 x 表示对应的执行计划id partitions 表分区、表创建的时候可以指定通过那个列进行表分区。 举个例子:

create table tmp (    id int unsigned not null AUTO_INCREMENT,    name varchar(255),    PRIMARY KEY (id)) engine = innodbpartition by key (id) partitions 5;

type(非常重要,可以看到有没有走索引) 访问类型

  • ALL 扫描全表数据
  • index 遍历索引
  • range 索引范围查找
  • index_subquery 在子查询中使用 ref
  • unique_subquery 在子查询中使用 eq_ref
  • ref_or_null 对Null进行索引的优化的 ref
  • fulltext 使用全文索引
  • ref 使用非唯一索引查找数据
  • eq_ref 在join查询中使用PRIMARY KEYorUNIQUE NOT NULL索引关联。

possible_keys 可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。

key 显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。

TIPS:查询中若使用了覆盖索引(覆盖索引:索引的数据覆盖了需要查询的所有数据),则该索引仅出现在key列表中

key_length 索引长度

ref 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

rows 返回估算的结果集数目,并不是一个准确的值。

extra 的信息非常丰富,常见的有:

  1. Using index 使用覆盖索引
  2. Using where 使用了用where子句来过滤结果集
  3. Using filesort 使用文件排序,使用非索引列进行排序时出现,非常消耗性能,尽量优化。
  4. Using temporary 使用了临时表 sql优化的目标可以参考阿里开发手册

【推荐】SQL性能优化的目标:至少要达到 range 级别,要求是ref级别,如果可以是consts最好。

说明:
1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2) ref 指的是使用普通的索引(normal index)。
3) range 对索引进行范围检索。
反例:explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫。

5.2 SQL的生命周期

  • 1.应用服务器与数据库服务器建立一个连接
  • 2.数据库进程拿到请求sql
  • 3.解析并生成执行计划,执行
  • 4.读取数据到内存并进行逻辑处理
  • 5.通过步骤一的连接,发送结果到客户端
  • 6.关掉连接,释放资源

5.3 大表数据查询,怎么优化?

  • 1.优化schema,sql语句+索引;
  • 2.加缓存,memcached redis;
  • 3.主从复制,读写分离;
  • 4.垂直拆分,根据模块的耦合度,将一个大的系统拆分成小的系统,即分布式系统;
  • 5.水平拆分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key,为了有更好的查询效率,表结构也要改动,做一定的冗余,sql中尽量待sharding key,将数据定位到限定的表上去查,而不是扫描全部的表。

5.4大分页怎么处理?(缓存)

超大的分页一般从两个方向上来解决.

  • 数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据.
  • 需求的角度减少这种请求…主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.

解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.

在阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种.

【推荐】利用延迟关联或者子查询优化超多分页场景。

说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。

正例:先快速定位需要获取的id段,然后再关联:

SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

5.5 Mysql 分页 limit

LIMIT子句可以被用于强制SELECT语句返回执行的记录数。LIMIT接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数执行返回记录行的最大数目。初始记录行的偏移量是0(而不是1)

mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15

为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:

mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.

如果只给定一个参数,它表示返回最大的记录行数目:

mysql> SELECT * FROM table LIMIT 5; //检索前 5 个记录行

换句话说,LIMIT n 等价于 LIMIT 0,n。

5.6 慢查询日志

用于记录执行时间超过某个临界值的SQL日志,用于快速定位慢查询,为我们的优化做参考。

开启慢查询日志

配置项:slow_query_log

可以使用show variables like ‘slov_query_log’查看是否开启,如果状态值为OFF,可以使用set GLOBAL slow_query_log = on来开启,它会在datadir下产生一个xxx-slow.log的文件。

设置临界时间

配置项:long_query_time

查看:show VARIABLES like 'long_query_time',单位秒

设置:set long_query_time=0.5

实操时应该从长时间设置到短的时间,即将最慢的SQL优化掉

查看日志,一旦SQL超过了我们设置的临界时间就会被记录到xxx-slow.log

5.7 关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?

慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?

所以优化也是针对这三个方向来的,

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
  • 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表,或者考虑缓存。

5.8 为什么要尽量设定一个主键?

主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。

5.9 主键使用自增ID还UUID

推荐使用自增ID,不要使用UUID。

因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降。

总之,在数据量大一些的情况下,用自增主键性能会好一些。

关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键。

5.10 字段为什么要求定义为not null?

null值会占用更多的字节,且会在程序中造成很多与预期不符的情况。

5.11 如果要存储用户的密码散列,应该使用什么字段进行存储?

密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率

5.12 优化查询过程中的数据访问

访问数据太多导致查询性能下降:

  • 确定应用程序是否在检索大量超过需要的数据,可能是太多行或列;
  • 确定Mysql服务器是否在分析大量不必要的数据行;

查询不需要的数据:

  • 使用limit解决

多表关联返回全部列:

  • 指定列名

总是返回全部列:

  • 避免使用select *

重复查询相同的数据:

  • 缓存数据,下次直接读取缓存

是否在扫描额外的记录:

  • 使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过以下技巧去优化:
    • 使用索引覆盖扫描,把所有的列放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果;
    • 改变数据库和表的结构,修改数据表范式
    • 重写SQL语句,让优化器可以更优的方式执行查询。

5.13 优化

5.13.1 优化长难的查询语句

使用一个复杂查询还是多个简单查询

  • 使用尽可能小的查询是好的,有时将一个大的查询分解为多个小的查询是很有必要的;
  • 切分查询,将一个大的查询划分为多个小的相同查询;
  • 一次性删除1000万的数据要比删除1万,暂停一会的方案更加损耗服务器开销;
  • 执行单个查询可以减少锁的竞争,查询效率会有大幅提升;

5.13.2 优化关联查询

  • 确定ON或者USING子句中是否有索引。
  • 确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。

5.13.3 优化子查询

  • 用关联查询替代
  • 优化GROUP BY和DISTINCT
  • 这两种查询据可以使用索引来优化,是最有效的优化方法
  • 关联查询中,使用标识列分组的效率更高
  • 如果不需要ORDER BY,进行GROUP BY时加ORDER BY NULL,MySQL不会再进行文件排序。
  • WITH ROLLUP超级聚合,可以挪到应用程序处理

5.13.4 优化LIMIT分页

  • LIMIT偏移量大的时候,查询效率较低
  • 可以记录上次查询的最大ID,下次查询时直接根据该ID来查询

5.13.5 优化UNION查询

  • UNION ALL的效率高于UNION

5.13.6 优化WHERE子句

对于此类考题,先说明如何定位低效SQL语句,然后根据SQL语句可能低效的原因做排查,先从索引着手,如果索引没有问题,考虑以上几个方面,数据访问的问题,长难查询句的问题还是一些特定类型优化的问题,逐一回答。

SQL语句优化的一些方法?

  • 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
  • 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null-- 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=
  • 3.应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
  • 4.应尽量避免在 where 子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20-- 可以这样查询:select id from t where num=10 union all select id from t where num=20
  • 5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3) -- 对于连续的数值,能用 between 就不要用 in 了:select id from t where num between 1 and 3
  • 6.下面的查询也将导致全表扫描:select id from t where name like ‘%李%’若要提高效率,可以考虑全文检索。
  • 7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num-- 可以改为强制查询使用索引:select id from t with(index(索引名)) where num=@num
  • 8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100-- 应改为:select id from t where num=100*2
  • 9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=’abc’-- name以abc开头的id应改为:select id from t where name like ‘abc%’

进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2=100-- 应改为:select id from t where num=100*2
  • 9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=’abc’-- name以abc开头的id应改为:select id from t where name like ‘abc%’
  • 10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

转载地址:https://codingchaozhang.blog.csdn.net/article/details/115281032 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:【大话Mysql面试】-Mysql事务以及隔离级别
下一篇:【大话Mysql面试】-常见SQL语句书写

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月13日 15时23分49秒