ToB企服应用市场:ToB评测及商务社交产业平台

标题: SQL执行慢的原因分析以及调优手段 [打印本页]

作者: 渣渣兔    时间: 2023-2-14 01:12
标题: SQL执行慢的原因分析以及调优手段
目录

1、大多数情况下很正常,偶尔很慢,则有如下原因
(1)、数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘。
(2)、执行的时候,遇到锁,如表锁、行锁。
(3)、sql写的烂
2、这条 SQL 语句一直执行的很慢,则有如下原因
(1)、没有用上索引或索引失效:例如该字段没有索引;或则由于对字段进行运算、函数操作导致无法用索引。
(2)、有索引但走了全表扫描
怎样判断是否走全表扫描:
索引区分度(索引的值不同越多,区分度越高),称为基数,而数据量大时不可能全部扫描一遍得到基数,而是采样部分数据进行预测,那有可能预测错了,导致走全表扫描。
优化角度:索引+sql语句+数据库结构优化+优化器优化+架构优化
对开发者来说,调优重点在开发规范、索引和线上慢查询
开发规范

※谨慎使用 MySQL 分区表

分区表在物理上表现为多个文件,在逻辑上表现为一个表;
谨慎选择分区键,跨分区查询效率可能更低;
建议采用物理分表的方式管理大数据。
※经常一起使用的列放到一个表中

避免更多的关联操作。经常联合查询的表,可以考虑建立中间表。
※禁止在数据库中存储文件(比如图片)这类大的二进制数据

在数据库中存储文件会严重影响数据库性能,消耗过多存储空间。
文件(比如图片)这类大的二进制数据通常存储于文件服务器数据库只存储文件地址信息
所有表必须使用 InnoDB 存储引擎

InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。
数据库和表的字符集统一使用 UTF8

兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储 emoji 表情的需要,字符集需要采用 utf8mb4 字符集。
尽量控制单表数据量的大小,建议控制在 500 万以内

可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小
※优先选择符合存储需要的最小的数据类型

存储字节越小,占用也就空间越小,性能也越好。
数字是连续的,性能更好,占用空间也更小。
MySQL 提供了两个方法来处理 ip 地址
插入数据前,先用 INET_ATON() 把 ip 地址转为整型,显示数据时,使用 INET_NTOA() 把整型的 ip 地址转为地址显示即可。
因为无符号相对于有符号可以多出一倍的存储空间
※尽可能把所有列定义为 NOT NULL

除非有特别的原因使用 NULL 值,应该总是让字段保持 NOT NULL。
使用 TIMESTAMP(4 个字节) 或 DATETIME 类型 (8 个字节) 存储时间

TIMESTAMP 存储的时间范围 1970-01-01 00:00:01 ~ 2038-01-19-03:14:07
TIMESTAMP 占用 4 字节和 INT 相同,但比 INT 可读性高
超出 TIMESTAMP 取值范围的使用 DATETIME 类型存储
经常会有人用字符串存储日期型的数据(不正确的做法)
索引规范

建议单张表索引不超过 5 个

因为 MySQL 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划,如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。
禁止给表中的每一列都建立单独的索引

不如使用一个联合索引
每个 InnoDB 表必须有个主键

InnoDB 是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引,但是表的存储顺序只能有一种。
InnoDB 是按照主键索引的顺序来组织表的
常见索引列建议

创建联合索引如何选择索引列的顺序

建立索引的目的是:希望通过索引进行数据查找,减少随机 IO,增加查询性能 ,索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。
对于频繁的查询优先考虑使用覆盖索引

覆盖索引:就是包含了所有查询字段 (where, select, order by, group by 包含的字段) 的索引
※索引 SET 规范

尽量避免使用外键约束
SQL语句优化

如何找出需要优化的 SQL 语句

禁止使用 SELECT * 必须使用 SELECT  查询

充分利用表上已经存在的索引

一个 SQL 只能利用到复合索引中的一列进行范围查询。如:有 a,b,c 列的联合索引,在查询条件中有 a 列的范围查询,则在 b,c 列上的索引将不会被用到。
在定义联合索引时,如果 a 列要用到范围查找的话,就要把 a 列放到联合索引的右侧,使用 left join 或 not exists 来优化 not in 操作,因为 not in 也通常会使用索引失效。
尽量避免使用子查询

用join连接
用IN来替换OR
  1. # 优化前
  2. SELECT * FROM t WHERE id = 10 OR id = 30;
  3. # 优化后
  4. SELECT * FROM t WHERE id IN (10, 30);
复制代码
对于连续的数值,能用between就不要用in了;再或者使用join连接来替换。
读取适当的记录LIMIT M,N
  1. # 优化前
  2. SELECT id.name FROM t LIMIT 866613, 20
复制代码
对于limit m,n 的分页查询,越往后面翻页(即m越大的情况下)SQL的耗时会越来越长,对于这种应该先取出主键id,然后通过主键id跟原表进行Join关联查询。因为MySQL并不是跳过offset行,而是取offset+N行,然后放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。
优化的方法如下:可以取前一页的最大行数的id(将上一次遍历到的最末尾的数据ID传给数据库,然后直接定位到该ID处,再往后面遍历数据),然后根据这个最大的id来限制下一页的起点。比如此列中,上一页最大的id是866612。sql可以采用如下的写法:
  1. # 优化后
  2. SELECT id.name FROM table_name WHERE id> 866612 LIMIT 20
复制代码
若两个结果集没有重复使用UNION ALL

union 和 union all 的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。
当然,union all 的前提条件是两个结果集没有重复数据。所以一般是我们明确知道不会出现重复数据的时候才建议使用 union all 提高速度。
尽可能批量Insert插入
  1. INSERT INTO t(id, name) VALUES(1, 'aaa');
  2. INSERT INTO t(id, name) VALUES(2, 'bbb');
  3. INSERT INTO t(id, name) VALUES(3, 'ccc');
  4. -->
  5. INSERT INTO t(id, name) VALUES(1, 'aaa'),(2, 'bbb'),(3, 'ccc');
复制代码
WHERE 从句中禁止对列进行函数转换和计算

对列进行函数转换或计算时会导致无法使用索引不推荐:
  1. where date(create_time)='20190101'
复制代码
推荐:
  1. where create_time >= '20190101' and create_time < '20190102'
复制代码
优化Group By语句

如果对group by 语句的结果没有排序要求,要在语句后面加上order by null (group默认会排序);
尽量让group by 过程用上表的索引,确认方法是explain结果里没有Using temporary和Using filesort;
如果group by 需要统计的数据量不大,尽量只使用内存临时表;也可以通过适当调大tmp_table_size参数,来避免用到磁盘临时表;
如果数据量实在太大,使用SQL_BIG_RESULT这个提示,来告诉优化器直接使用排序算法(直接用磁盘临时表)得到group by 的结果。
使用where字句替换having子句:避免使用having子句,having只会在检索出所有记录之后才会对结果集进行过滤,这个处理需要排序分组,如果能通过where子句提前过滤查询的数目,就可以减少这方面的开销。
  1. # 优化前
  2. SELECT JOB, AVG(SAL) FROM EMP GROUP BY JOB HAVING JOB = 'PRESIDENT' OR JOB = 'MANAGER'
  3. # 优化后
  4. SELECT JOB, AVG(SAL) FROM EMP WHERE JOB = 'PRESIDENT' OR JOB = 'MANAGER' GROUP BY JOB
复制代码
优化Join语句

当我们执行两个表的Join的时候,就会有一个比较的过程,逐条比较两个表的语句是比较慢的,因此可以把两个表中数据一次读进一个内存块中,在MySQL中执行:
  1. show variables like 'join_buffer_size'
复制代码
可以看到Join在内存中的缓存池大小,其大小将会影响Join语句的性能。在执行Join的时候,数据库会选择一个表把它要返回以及需要进行和其他表进行比较的数据放进join_buffer。

假如有表如右边:t1与t2表完全一样,a字段有索引,b无索引,t1有100条数据,t2有1000条数据

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4