本文共 7047 字,大约阅读时间需要 23 分钟。
五、SQL优化
5.1 如何定义以及优化SQL语句的性能问题?创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?
对于低性能的SQL语句的定位,最重要也是最有效的方法就是使用执行计划,Mysql提供了explain命令来查看语句的执行计划。 我们知道,不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行执行的过程中都会做很多相关的优化。对于查询语句,最重要的优化方式就是使用索引,而执行计划,就是显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用的索引的相关信息等。
执行计划包含的信息 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 的信息非常丰富,常见的有:
- Using index 使用覆盖索引
- Using where 使用了用where子句来过滤结果集
- Using filesort 使用文件排序,使用非索引列进行排序时出现,非常消耗性能,尽量优化。
- 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!