如何获取有性能问题的SQL
- 通过用户反馈获取存在性能问题的SQL
- 通过慢查询日志获取存在性能问题的SQL
- 实时获取存在性能问题的SQL
通过慢查询日志获取存在性能问题的SQL
- slow_query_log:启动/停止记录慢查询日志
- slow_query_log_file:指定慢查询日志的存储路径及文件
- long_query_time:指定记录慢查询日志SQL执行时间的阀值,单位为秒,默认值为10秒。精确到微秒,如果为一毫秒这个值为 0.001 。
- log-queries-not-using-indexes:是否记录未使用索引的SQL
数据库中设置SQL慢查询
- 1、修改配置文件 在 my.ini 增加几行: 主要是慢查询的定义时间(超过2秒就是慢查询),以及慢查询log日志记录( slow_query_log)
# 定义查询耗时多少秒的查询算慢查询
long_query_time=0.01
# 5.0,5.1等版本配置如下选项
log-show-queries="mysql_show_query.log"
# 5.5及以上版本配置如下选项
slow_query_log=on
slow_query_log_file="mysql_show_query.log"
# 记录没有使用索引的sql
log-queries-not-using-indexes
- 2、通过MySQL数据库开启慢查询:
set global slow_query_log=on
set global long_query_time=0.01
set global log_queries_not_using_indexes=ON
慢查日志中记录的内容:
第一行记录了:用户信息,线程ID号 用户信息 sbtest ,线程ID为 17
第二行 :记录了查询时间
第三行 :锁的时间
第四行 : 返回的记录行数
第五行: 扫描的行数
第六行 : 执行的时间
第七行 : 执行的语句
常用慢查询日志分析工具
mysqldumpslow
汇总除查询条件外其他完全相同的SQL,并将分析结果按照参数中指定的顺序输出。
使用命令:
mysqldumpslow –s r –t 10 slow.log
参数详解:
-s order (c,t,l,r,at,al,ar):指定按哪种排序方式输出结果
c:总的次数
t:总的时间
l:锁的时间
r:总数据行
at,al,ar: t,l,r 的平均数(在这些字母前加上a)
at:总时间 /总次数
-t top:指定取前几条作为结果输出
pt-query-digest
pt-query-digest是用于分析mysql慢查询的一个工具,它可以分析binlog、General log、slowlog,也可以通过SHOWPROCESSLIST或者通过tcpdump抓取的MySQL协议数据来进行分析。可以把分析结果输出到文件中,分析过程是先对查询语句的条件进行参数化,然后对参数化以后的查询进行分组统计,统计出各查询的执行时间、次数、占比等,可以借助分析结果找出问题进行优化。
使用如下参数分析慢查询日志的分析结果,可以包括执行计划。
pt-query-digest --explain h=127.0.0.1,u=root,p=root slow-mysql.log > slow.rep
实时获取性能的问题的SQL
利用MySQL中的information_schema库中的processlist表实时发现具有性能问题的SQL
查询服务器中查询时间超过60秒的SQL
select id,user,host,db,command ,time,state,info from information_schema.processlist where time>60
可以在主数据库中修改上面sql中的time的值,通过脚本周期性地执行,就可以实时发现执行慢的sql
查询速度为什么会慢
- 1、客户端发送SQL请求给服务器
- 2、服务器检查是否可以在查询缓存中命中该SQL
- 3、服务器端进行SQL解析,预处理,在由优化器生成对应的执行计划
- 4、根据执行计划,调用存储引擎API来查询数据
- 5、将结果返回给客户端
这就是 MySQL 服务器处理查询请求的整个过程。在第二到第五步,都有可能对查询的响应速度造成影响,下面来分别看下这些过程可能对查询的响应速度有影响的因素都有些什么:
在解析查询语句前,如果查询缓存是打开的,那么 MySQL 优先检查这个查询是否命中查询缓存中的数据,这个检查是通过一个对大小写敏感的 Hash 查找实现的。由于 Hash 查找只能进行全值匹配,所以请求的查询和缓存中的查询就算只有一个字节的不同,那么也不会匹配到缓存中的结果,这种情况下,查询就会进入到下一阶段处理。如果正好命中查询缓存,在返回查询结果之前,MySQL 就会检查用户权限,也是无需解析 SQL 语句的,因为在查询缓存中,已经存放了当前查询所需要访问的表的信息,如果权限没有问题,MySQL 会跳过所有的其他阶段,直接从缓存中拿到结果,并返回给客户端,这种情况下查询是不会被解析的,也不会生成查询计划,不会被执行。
可以发现,从查询缓存中直接返回结果并不容易。
查询缓存对 SQL 性能的影响:
- 如果查询缓存,一旦数据更新,都要对缓存中数据进行刷新,影响性能;
- 每次在查询缓存中检查 SQL 是否被命中,都要对缓存加锁,影响性能;
对于一个读写频繁的系统来说,查询缓存很可能会降低查询处理的效率。所以在这种情况下建议大家不要使用查询缓存。
对查询缓存影响的一些系统参数:
- query_cache_type:设置查询缓存是否可用,可以设置为ON、OFF、DEMAND,DEMAND表示只有在查询语句中使用 SQL_CACHE 和 SQL_NO_CACHE 来控制是否需要缓存。
- query_cache_size:设置查询缓存的内存大小,必须是1024字节的整数倍。
- query_cache_limit:设置查询缓存可用存储的最大值,如果知道很大不会被缓存,可以在查询上加上 SQL_NO_CACHE 提高效率。
- query_cache_wlock_invalidate:设置数据表被锁后是否返回缓存中的数据,默认关闭。
- query_cache_min_res_unit:设置查询缓存分配的内存块最小单位。
对于一个读写频繁的系统来说,可以把 query_cache_type 设置为 OFF,并且把 query_cache_size 设置为 0。
当查询缓存未启用或者未命中则会进入下一阶段,也就是需要将一个 SQL 转换成一个执行计划,MySQL 再依据这个执行计划和存储引擎进行交互,这个阶段包括了多个子过程:解析 SQL,预处理,优化 SQL 执行计划。在这个过程中,出现任何错误,比如语法错误等,都有可能中止查询的过程。
在语法解析阶段,主要是通过关键字对 MySQL 语句进行解析,并生成一棵对应的 “解析树”。这一阶段,MySQL 解析器将使用 MySQL 语法规则验证和解析查询,包括检查语法是否使用了正确的关键字、关键字的顺序是否正确等。
预处理阶段则是根据 MySQL 规则进一步检查解析树是否合法,比如检查查询中所涉及的表和数据列是否存在、检查名字或别名是否存在歧义等。
如果语法检查全部都通过了,查询优化器就可以生成查询计划了。
会造成 MySQL 生成错误的执行计划的原因:
- 统计信息不准确;
- 执行计划中的成本估算不等同于实际的执行计划的成本;
- MySQL 查询优化器所认为的最优可能与你所认为的最优不一样;
- MySQL 从不考虑其他并发的查询,这可能会影响当前查询的速度;
- MySQL 有时候也会基于一些固定的规则来生成执行计划;
- MySQL 不会考虑不受其控制的成本,例如存储过程、用户自定义的函数等。
MySQL 的查询优化器可以优化的 SQL 类型:
- 重新定义表的关联顺序,优化器会根据统计信息来决定表的关联顺序;
- 将外连接转化为内连接,比如 where 条件和库表结构都可能让一个外连接等价于内连接;
- 使用等价变换规则,比如 (5=5 and a>5) 将被改写为 a>5;
- 利用索引和列是否为空来优化 count()、min() 和 max() 等聚合函数;
- 将一个表达式转换为常数表达式;
- 使用等价变换规则,比如覆盖索引,当 MySQL 查询优化器发现索引中的列包含所有查询中所需要的信息的时候,MySQL 就能使用索引返回需要的数据;
- 子查询优化,比如把子查询转换为关联查询,减少表的查询次数;
- 提前终止查询;
- 对 in() 条件进行优化。
以上这些就是 MySQL 查询优化器可以自动对查询所做的一些优化。经过查询优化器改写后的 SQL,查询优化器会对其生成一个 SQL 执行计划,然后 MySQL 服务器就可以根据执行计划调用存储引擎的 API,通过存储引擎获取数据了。
确定查询处理各个阶段的耗时
SQL 查询优化的主要目的就是减少查询所消耗的时间,加快查询的响应速度。下面来介绍如何度量查询处理各个阶段所消耗的时间。
对于一个存在性能问题的 SQL 来说,必须知道在查询的哪一阶段消耗的时间最多,然后才能有针对性的进行优化。度量查询处理各个阶段所消耗的时间,常用的方法有两种:
- 使用 profile;
- 使用 performance_schema;
特定SQL的查询优化
前面介绍的方法,已经可以获取一个存在性能问题的 SQL 和获取一个 SQL 在执行的各个阶段所消耗的时间了。得到这些信息后,我们就可以针对性的对 SQL 进行优化了,下面举几个对特定 SQL 优化的案例:
1、大表的更新和删除
对于大表的数据修改最好要分批处理,比如我们要在一个 1000 万行记录的表中删除/更新 100 万行记录,那么我们最好分多个批次进行删除/更新,一次只删除/更新 5000 行记录,避免长时间的阻塞,并且为了减少对主从复制带来的压力,每次删除/修改数据后需要暂停几秒。这里提供一个可以完成这样工作的 MySQL 存储过程的实例:
DELIMITER $$
USE 'db_name'$$
DROP PROCEDURE IF EXISTS 'p_delete_rows'$$
CREATE DEFINER='mysql'@'127.0.0.1' PROCEDURE 'p_delete_rows'()
BEGIN
DECLARE v_rows INT;
SET v_rows = 1;
WHERE v_rows > 0
DO
DELETE FROM table_name WHERE id >= 9000 AND id <= 290000 LIMIT 5000;
SELECT ROW_COUNT() INTO v_rows;
SELECT SLEEP(5);
END WHERE;
END$$
DELIMITER;
大家可以根据自己的情况来修改这个存储过程,或者使用自己熟悉的开发语言实现这个处理过程,使用这个存储过程只需要修改 DELETE FROM table_name WHERE id >= 9000 AND id <= 290000 LIMIT 5000; 部分的内容即可。
2、如何修改大表的表结构
对于 InnoDB 存储引擎来说,对表中的列的字段类型进行修改或者改变字段的宽度时还是会锁表,同时也无法解决主从数据库延迟的问题。
解决方案:
在主服务器上建立新表,新表的结构就是修改之后的结构,再把老表的数据导入到新表中,并且在老表上建立一系列的触发器,把老表数据的修改同步更新到新表中,当老表和新表的数据同步后,再对老表加一个排它锁,然后重新命名新表为老表的名字,最好删除重命名的老表,这样就完成了大表表结构修改的工作。这样处理的好处是可以尽量减少主从延迟,以及在重命名之前不需要加任何的锁,只需要在重命名的时候加一个短暂的锁,这对应用通常是无影响的,缺点就是操作比较复杂。好在有工具可以帮我们实行这个过程,这个工具同样是 percona 公司 MySQL 工具集中的一个,叫做 pt-online-schema-change:
pt-online-schema-change \
--alter="MODIFY c VARCHAR(150) NOT NULL DEFAULT ''" \
--user=root --password=password D=db_name,t=table_name \
--charset=utf8 --execute
这个命令就是把 db_name 数据库下的 table_name 表中 c 列的宽度改为 VARCHAR(150)。
3、如何优化not in和<>查询
MySQL 查询优化器可以自动的把一些子查询优化为关联查询,但是对于存在not in和<>这样的子查询语句来说,就无法进行自动优化了,这就造成了会循环多次来查找子表来确认是否满足过滤条件,如果子查询恰好是一个很大的表的话,这样做的效率会非常低,所以我们在进行 SQL 开发时,最好把这类查询自行改写成关联查询。
改写前:
SELECT id,name,email
FROM customer
WHERE id
NOT IN(SELECT id FROM payment)
优化改写后:
SELECT a.id,a.name,a.email
FROM customer a
LEFT JOIN payment b ON a.id=b.id
WHERE b.id IS NULL
使用 LEFT JOIN 关联替代了 NOT IN 过滤,这样避免了对 payment 表进行多次查询,这是一种非常常用的对 NOT IN 的优化方式。
4、使用汇总表优化查询
最常见的就是商品的评论数,如果我们在用户访问页面时,实时的访问商品的评论数,通常来说,查询的 SQL 会类似于下面这个样子:
SELECT COUNT(*) FROM product_comment WHERE product_id = 10001;
这个 SQL 就是统计出所有 product_id = 10001 的评论,假设评论表中有上亿条记录,那么这个 SQL 执行起来是非常的慢的,如果有大量的并发访问,则会对数据库带来很大的压力。对于这么情况,我们通常使用汇总表的方式进行优化。所谓的汇总表就是提前把要统计的数据进行汇总并记录到表中已备后续的查询使用。针对这个查询,我们可以使用下面的方式进行优化:
CREATE TABLE product_comment_cnt(product_id INT, cnt INT); //建立汇总表
//查询评论数
SELECT SUM(cnt) FROM(
SELECT cnt FROM product_comment_cnt WHERE product_id = 10001
UNION ALL
SELECT COUNT(*) FROM product_comment WHERE product_id = 10001
AND timestr > DATE(NOW())
);
MySQL SQL优化
建表
- 1、主键使用合适的无符号unsigned整型自增(避免数据增加时索引页分裂)
- 2、长度固定的字符串字段使用CHAR类型,Varchar会额外使用1或者2个字节(长度小于255使用1字节)存储字符串长度
- 3、选择合适的字符串数据类型和日期类型(优先TIMESTAMP,占用4字节)
索引
- 1、使用单独的列(不对索引进行表达式或者函数运算)
- 2、对于BLOB、TEXT或者很长的VARCHAR类型的列,使用前缀索引
- 3、多列索引选择合适的索引列顺序(索引列的选择度和最左原则)
- 4、能使用覆盖索引的使用覆盖索引、使用索引排序
- 5、索引列字段应不为NULL(会导致索引失效,若是唯一索引,多列可以有NULL值,使唯一索引失效)
- 6、索引保持在5条左右,多的话影响插入和更新性能
查询(避免索引失效)
- 1、使用EXPLAIN查询SQL优化器执行计划,调整SQL查询
- 2、使用not exists代替not in,not in不会使用索引
- 3、查询条件避免使用前导模糊查询,如'%xxx',因为无法使用索引
- 4、查询条件使用or的话,要保证or两边的列都要有索引,否则索引失效
- 5、字符串型字段为数字时,在where条件中要加单引号,否则索引失效(因为这样MySQL会讲表中字符串类型转换为数字之后再比较,导致索引失效)
- 6、多列索引要满足最左前缀要求
- 7、ISNULL判断不走索引,要慎用
- 8、LIMIT分页的页码不能太大,会查询出所有的结果然后丢弃掉不需要的。使用主键做连接查询
参考:
https://www.cnblogs.com/ghjbk/p/6709111.html
https://blog.csdn.net/qq_35571554/article/details/82800463
https://www.cnblogs.com/yangyongjie/p/11111452.html
https://www.cnblogs.com/AIPAOJIAO/p/11099602.html
https://blog.csdn.net/xs925048899/article/details/88736157
https://www.cnblogs.com/dukuan/p/9486166.html
https://www.cnblogs.com/yangyongjie/p/11111452.html