7个常见的SQL慢查询问题及其解决方法

大家好,得益于摩尔定律,计算机性能已大幅提升,加上数据库的进步以及微服务所倡导的各种反模式设计,因此现在编写复杂SQL查询的机会越来越少。业界已经开始提倡不要进行专门的SQL优化,因为节省下来的资源并不足以抵消员工的工资成本。本文将介绍7个常见的SQL慢查询语句,并解释如何优化它们的性能。

1.LIMIT语句

分页是最常用的方案之一,但也容易出现问题。例如对于以下简单的语句,DBA通常建议的解决方案是添加一个包含typenamecreate_time字段的复合索引。这样,条件和排序就可以有效利用索引,从而显著提高性能。

SELECT *
FROM   operation
WHERE  type = 'SQLStats'AND name = 'SlowLog'
ORDER  BY create_time
LIMIT  1000, 10;

这可能会解决90%以上DBA的问题,但是当LIMIT子句变成“LIMIT 1000000, 10”时,程序员仍会抱怨“为什么在只查询10条记录的时候,速度还这么慢” 。要知道,数据库不知道第1000000条记录从何处开始,所以即使有索引,它仍需要从头开始计算。在大多数情况下,这个性能问题是由于懒惰编程造成的。

在前端数据浏览或批量导出大量数据的场景中,可以使用上一页的最大值作为查询参数。SQL可以重新设计如下:

SELECT   *
FROM     operation
WHERE    type = 'SQLStats'
AND      name = 'SlowLog'
AND      create_time > '2017-03-16 14:00:00'
ORDER BY create_time
LIMIT    10;

采用这种新设计后,查询时间保持不变,不会随着数据量的增加而变化。

2.隐式转换

SQL语句中另一个常见的错误是查询变量和字段定义的类型不匹配,以下面的语句为例:

mysql> explain extended SELECT *> FROM   my_balance b> WHERE  b.bpn = 14000000123>       AND b.isverified IS NULL ;
mysql> show warnings;
| Warning | 1739 | Cannot use ref access on index 'bpn' due to type or collation conversion on field 'bpn'

在这种情况下,字段bpn被定义为varchar(20),而MySQL的策略是在比较之前将字符串转换为数字。这会导致函数被应用到表字段上,从而使索引失效。

这种情况可能是由应用程序框架自动填充参数造成的,而不是程序员的本意。如今,应用程序框架通常都很复杂,虽然它们提供了便利,但也可能带来隐患。

3.连接更新和删除

尽管MySQL 5.6引入了物化,但它只优化了SELECT语句。对于UPDATE或DELETE语句,需要使用JOIN手动重写。

例如,请看下面的UPDATE语句。MySQL实际上执行了一个循环/嵌套子查询(DEPENDENT SUBQUERY),执行时间可想而知。

UPDATE operation o
SET    status = 'applying'
WHERE  o.id IN (SELECT idFROM   (SELECT o.id,o.statusFROM   operation oWHERE  o.group = 123AND o.status NOT IN ( 'done' )ORDER  BY o.parent,o.idLIMIT  1) t);

执行计划如下:

+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
| id | select_type        | table | type  | possible_keys | key     | key_len | ref   | rows | Extra                                               |
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+
| 1  | PRIMARY            | o     | index |               | PRIMARY | 8       |       | 24   | Using where; Using temporary                        |
| 2  | DEPENDENT SUBQUERY |       |       |               |         |         |       |      | Impossible WHERE noticed after reading const tables |
| 3  | DERIVED            | o     | ref   | idx_2,idx_5   | idx_5   | 8       | const | 1    | Using where; Using filesort                         |
+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+

将其重写为JOIN后,子查询的选择类型从DEPENDENT SUBQUERY变为DERIVED,执行时间显著得从7秒缩短到2毫秒。

UPDATE operation oJOIN  (SELECT o.id,o.statusFROM   operation oWHERE  o.group = 123AND o.status NOT IN ( 'done' )ORDER  BY o.parent,o.idLIMIT  1) tON o.id = t.id
SET    status = 'applying';

简化后的执行计划如下:

+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
| id | select_type | table | type | possible_keys | key   | key_len | ref   | rows | Extra                                               |
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+
| 1  | PRIMARY     |       |      |               |       |         |       |      | Impossible WHERE noticed after reading const tables |
| 2  | DERIVED     | o     | ref  | idx_2,idx_5   | idx_5 | 8       | const | 1    | Using where; Using filesort                         |
+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+

4.混合排序

MySQL无法利用索引进行混合排序,但是在某些场景下,仍然可以使用特殊方法来提高性能。

SELECT *
FROM   my_order oINNER JOIN my_appraise a ON a.orderid = o.id
ORDER  BY a.is_reply ASC,a.appraise_time DESC
LIMIT  0, 20;

执行计划显示的是全表扫描:

+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+
| id | select_type | table | type   | possible_keys     | key     | key_len | ref      | rows    | Extra
+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+
|  1 | SIMPLE      | a     | ALL    | idx_orderid | NULL    | NULL    | NULL    | 1967647 | Using filesort |
|  1 | SIMPLE      | o     | eq_ref | PRIMARY     | PRIMARY | 122     | a.orderid |       1 | NULL           |
+----+-------------+-------+--------+---------+---------+---------+-----------------+---------+-+

由于is_reply只有0和1两种状态,可以将其重写如下,从而将执行时间从1.58秒缩短到2毫秒:

SELECT *
FROM   ((SELECT *FROM   my_order oINNER JOIN my_appraise aON a.orderid = o.idAND is_reply = 0ORDER  BY appraise_time DESCLIMIT  0, 20)UNION ALL(SELECT *FROM   my_order oINNER JOIN my_appraise aON a.orderid = o.idAND is_reply = 1ORDER  BY appraise_time DESCLIMIT  0, 20)) t
ORDER  BY  is_reply ASC,appraisetime DESC
LIMIT  20;

5.EXISTS语句

在处理EXISTS子句时,MySQL仍然使用嵌套子查询进行执行。以下面的SQL语句为例:

SELECT *
FROM   my_neighbor nLEFT JOIN my_neighbor_apply sraON n.id = sra.neighbor_idAND sra.user_id = 'xxx'
WHERE  n.topic_status < 4AND EXISTS(SELECT 1FROM   message_info mWHERE  n.id = m.neighbor_idAND m.inuser = 'xxx')AND n.topic_type <> 5;
+----+--------------------+-------+------+-----+------------------------------------------+---------+-------+---------+ -----+
| id | select_type        | table | type | possible_keys | key     | key_len | ref      | rows    | Extra
+----+--------------------+-------+------+ -----+------------------------------------------+---------+-------+---------+ -----+
|  1 | PRIMARY            | n     | ALL  |  | NULL     | NULL    | NULL    | 1086041 | Using where                   |
|  1 | PRIMARY            | sra   | ref  |  | idx_user_id | 123     | const |       1 | Using where          |
|  2 | DEPENDENT SUBQUERY | m     | ref  |  | idx_message_info   | 122     | const |       1 | Using index condition; Using where |
+----+--------------------+-------+------+ -----+------------------------------------------+---------+-------+---------+ -----+

通过删除EXISTS子句并将其更改为JOIN, 我们可以避免嵌套子查询,并将执行时间从1.93秒减少到1毫秒。

SELECT *
FROM   my_neighbor nINNER JOIN message_info mON n.id = m.neighbor_idAND m.inuser = 'xxx'LEFT JOIN my_neighbor_apply sraON n.id = sra.neighbor_idAND sra.user_id = 'xxx'
WHERE  n.topic_status < 4AND n.topic_type <> 5;

新的执行计划如下:

+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+
| id | select_type | table | type   | possible_keys | key   | key_len | ref   | rows | Extra |
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+
|  1 | SIMPLE      | m     | ref    | | idx_message_info   | 122     | const |    1 | Using index condition |
|  1 | SIMPLE      | n     | eq_ref | | PRIMARY   | 122     | ighbor_id |    1 | Using where      |
|  1 | SIMPLE      | sra   | ref    | | idx_user_id | 123     | const |    1 | Using where           |
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+

6.条件下推

在某些情况下,外部查询条件无法下推到复杂的视图或子查询中:

  • 聚合子查询。

  • 带有LIMIT的子查询。

  • UNION或UNION ALL子查询。

  • 输出字段中的子查询。

请看下面的语句,其中的条件会影响聚合子查询:

SELECT *
FROM   (SELECT target,Count(*)FROM   operationGROUP  BY target) t
WHERE  target = 'rm-xxxx';
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
| id | select_type | table      | type  | possible_keys | key         | key_len | ref   | rows | Extra       |
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+
|  1 | PRIMARY     | n          | ALL   | NULL          | NULL        | NULL    | NULL  | 1086041 | Using where |
|  1 | PRIMARY     | sra        | ref   | NULL          | idx_user_id | 123     | const |    1 | Using where |
|  2 | DEPENDENT SUBQUERY | m | ref   | NULL          | idx_message_info   | 122     | const |    1 | Using index condition; Using where |
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+

通过删除EXISTS子句并将其更改为JOIN,我们可以避免嵌套子查询并将执行时间从1.93秒减少到1毫秒。

SELECT *
FROM   my_neighbor nINNER JOIN message_info mON n.id = m.neighbor_idAND m.inuser = 'xxx'LEFT JOIN my_neighbor_apply sraON n.id = sra.neighbor_idAND sra.user_id = 'xxx'
WHERE  n.topic_status < 4AND n.topic_type <> 5;

新的执行计划如下:

+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+
| id | select_type | table | type   | possible_keys | key   | key_len | ref   | rows | Extra |
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+
|  1 | SIMPLE      | m     | ref    | | idx_message_info   | 122     | const |    1 | Using index condition |
|  1 | SIMPLE      | n     | eq_ref | | PRIMARY   | 122     | ighbor_id |    1 | Using where      |
|  1 | SIMPLE      | sra   | ref    | | idx_user_id | 123     | const |    1 | Using where           |
+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+

7.提前缩小范围

以下经过部分优化的示例(左连接中的主表作为主查询条件):

SELECT    a.*,c.allocated
FROM      (SELECT   resourceidFROM     my_distribute dWHERE    isdelete = 0AND      cusmanagercode = '1234567'ORDER BY salecode limit 20) a
LEFT JOIN(SELECT   resourcesid, sum(ifnull(allocation, 0) * 12345) allocatedFROM     my_resourcesGROUP BY resourcesid) c
ON        a.resourceid = c.resourcesid;

很明显,子查询c是对整个表进行聚合查询,在处理大量表时可能会导致性能下降。

事实上,对于子查询c,左连接的结果集只关心可以与主表的resourceid匹配的数据。因此我们可以将语句重写如下,将执行时间从2秒减少到2毫秒:

SELECT    a.*,c.allocated
FROM      (SELECT   resourceidFROM     my_distribute dWHERE    isdelete = 0AND      cusmanagercode = '1234567'ORDER BY salecode limit 20) a
LEFT JOIN(SELECT   resourcesid, sum(ifnull(allocation, 0) * 12345) allocatedFROM     my_resources r,(SELECT   resourceidFROM     my_distribute dWHERE    isdelete = 0AND      cusmanagercode = '1234567'ORDER BY salecode limit 20) aWHERE    r.resourcesid = a.resourcesidGROUP BY resourcesid) c
ON        a.resourceid = c.resourcesid;

然而子查询a在SQL语句中出现了多次,这种方法不仅会产生额外的成本,而且也会使语句变得更加复杂。可以使用WITH语句来简化它:

WITH a AS
(SELECT   resourceidFROM     my_distribute dWHERE    isdelete = 0AND      cusmanagercode = '1234567'ORDER BY salecode limit 20)
SELECT    a.*,c.allocated
FROM      a
LEFT JOIN(SELECT   resourcesid, sum(ifnull(allocation, 0) * 12345) allocatedFROM     my_resources r,aWHERE    r.resourcesid = a.resourcesidGROUP BY resourcesid) c
ON        a.resourceid = c.resourcesid;

数据库编译器生成的执行计划决定了SQL语句的实际执行方式,但是编译器只能尽力提供服务,没有一个数据库编译器是完美的,上述情况在其他数据库中也同样存在。了解了数据库编译器的特性,我们就能绕过它的限制,编写出高性能的SQL语句。

在设计数据模型和编写SQL语句时,将算法思维或算法意识引入到这个过程非常重要。在编写复杂的SQL语句时,养成使用WITH语句的习惯可以简化语句,减轻数据库的负担。

最后,下面是SQL语句的执行顺序:

FROMONJOINWHEREGROUP BYHAVINGSELECT
DISTINCTORDER BYLIMIT

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/14180.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人工智能的明天:机器学习与自动化的演进之旅

方向一&#xff1a;技术革新与行业应用 现状分析&#xff1a; 当前的IT行业正处于一个技术革新的高峰期。量子计算虽然还处于研究和开发阶段&#xff0c;但其潜力巨大&#xff0c;未来可能在药物发现、材料科学和复杂系统模拟等领域带来突破。虚拟现实&#xff08;VR&#xff…

JAVA面试题大全(九)

1、为什么要使用 spring&#xff1f; 方便解耦&#xff0c;便于开发支持aop编程声明式事务的支持方便程序的测试方便集成各种优秀的框架降低JavaEE API的使用难度 2、解释一下什么是 aop&#xff1f; AOP 是 Aspect-Oriented Programming 的缩写&#xff0c;中文翻译为“面向…

HTML5表单控件:新时代的交互魔法手册

&#x1f680;HTML5表单控件&#xff1a;新时代的交互魔法手册 &#x1f3af;HTML5表单控件速览&#xff1a;新面孔&#xff0c;新功能1. 日期时间选择器&#xff08;Date & Time Picker&#xff09;2. 数字输入框&#xff08;Number Input&#xff09;3. 搜索框&#xff0…

argparse.ArgumentParser()用法举例

1. 应用场景 我们在玩深度学习&#xff0c;训练模型的时候&#xff0c;会涉及到很多的参数&#xff0c;这个时候就需要用到argparse.ArgumentParser()方法&#xff0c;它的优点是方便在命令行调用的时候修改参数&#xff0c;为了快速了解该方法的应用&#xff0c;这里举例说明…

如何通过LoadRunner进行全链路压测

LoadRunner是Micro Focus公司开发的一款性能测试工具&#xff0c;广泛应用于企业级应用的性能和负载测试。全链路压测&#xff08;End-to-End Load Testing&#xff09;是对整个系统在真实负载下的表现进行测试&#xff0c;涵盖前端、后端和所有中间组件。以下是通过LoadRunner…

深度学习之Tensorflow卷积神经网络手势识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手势识别是计算机视觉和人工智能领域的重要应用之一&#xff0c;具有广泛的应用前景&#xff…

日用百货元宇宙 牛奶、羊奶、骆驼奶……到底哪个奶营养价值更高?

如今生活水平高了&#xff0c;奶的营养价值日益受到重视。从开始只有牛奶&#xff0c;到现在市面上羊奶、马奶、骆驼奶……花样变得越来越多。但同时很多疑问也随之而来&#xff0c;到底哪种奶营养价值更高&#xff1f;牛奶、羊奶、马奶……有什么区别&#xff1f; 牛奶&#x…

iOS技术之AFNetworking 将数组作为字段参数传给服务器不成功的两种解决方法

第一种方法 1.在AFNetworking的源码中找到AFURLRequestSerialization这个类&#xff1b; 2.在.m文件中搜索AFQueryStringPairsFromKeyAndValue字段, 找到该方法 else if ([value isKindOfClass:[NSArray class]]) {NSArray *array value;for (id nestedValue in array) …

编曲软件FL Studio如何为自己制作的歌曲编写工程信息 flstudio自带工程在哪

FL Studio有着很多的功能&#xff0c;覆盖面非常广&#xff0c;不管是音色调整、界面个性化还是为工程编写信息&#xff0c;都可以在FL Studio中使用。每个工程文件都有它的各种信息&#xff0c;比如标题名称、作者、音乐类型、工程介绍、创建时间等&#xff0c;编写工程信息能…

JVM优化之使用Jstat命令预估JVM运行情况

JVM优化之使用Jstat命令预估JVM运行情况 用 jstat gc -pid 命令可以计算出如下一些关键数据 先给自己的系统设置一些初始性的JVM参数&#xff0c;比如堆内存大小&#xff0c;年轻代大小&#xff0c;Eden和Survivor的比例&#xff0c;老年代的大小&#xff0c;大对象的阈值&…

JavaScript 基础 - 第1天

了解变量、数据类型、运算符等基础概念&#xff0c;能够实现数据类型的转换&#xff0c;结合四则运算体会如何编程。 体会现实世界中的事物与计算机的关系 理解什么是数据并知道数据的分类 理解变量存储数据的“容器” 掌握常见运算符的使用&#xff0c;了解优先级关系 知道…

第十五节 huggingface的trainner的_inner_training_loop函数源码解读(epoch)

文章目录 前言一、完整源码呈现1、训练前源码3、训练源码(epoch)二、训练epoch循环源码解读1、epoch循环体前的变量2、开始epochs循环2、训练数据3、每个epoch迭代数step获得4、self.callback_handler.on_epoch_begin调用函数on_epoch_begin函数5、self._load_rng_state载入

Vue前端项目打包,并部署Vue项目到Linux云服务器上

一. vue前端项目打包 1.使用vscode开发项目 2.在config目录下的prod.env.js文件当中配置我们后端服务器的IP地址和端口号&#xff0c;因为这是在实际的部署当中所以必须要在生成环境下进行项目的部署。 如图所示&#xff1a; 3.在config目录下的index.js文件当中要改assetsPu…

Linux配置nginx代理功能

ywtool运维工具下载链接及介绍: 工具下载/介绍/安装页面 目录 一.nginx proxy功能介绍二.配置nginx proxy功能2.1 新增nginx代理配置2.1.1 反向代理(当前只举例https转https)2.1.2 负载均衡(当前只举例https转https) 2.2 修改nginx代理配置2.2.1 手动修改配置文件2.2.2 通过此脚…

U盘文件神秘失踪?别担心,恢复与预防攻略在此!

一、遭遇困境&#xff1a;U盘文件突然不见 在数字时代&#xff0c;U盘已成为我们日常工作中不可或缺的数据存储工具。然而&#xff0c;有时我们可能会遭遇一个令人头疼的问题——U盘中的文件突然不见了。这种情况往往让人措手不及&#xff0c;尤其是对于那些没有备份重要文件的…

显示旋转物体转动速度的仪表

转速表是一种测量并显示旋转物体转动速度的仪表&#xff0c;广泛应用于汽车、飞机、船舶、工业机械等各种领域。它的工作原理和特点概括如下&#xff1a;定义与应用• 定义&#xff1a;转速表是用于测量如汽车发动机轴、电机轴等旋转部件每分钟转数&#xff08;RPM, Revolution…

27.STL vector容器emplace_back和push_back的区别

文章目录 STL vector容器emplace_back和push_back的区别C的三法则和五法则比较emplace_back和push_back函数总结reference 欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; STL vector容器emplace_back和push_back的区别 这个问题可以讲是…

Gitlab OpenSSL::Cipher::CipherError(gitlab修改项目500错误)

问题描述 在对 gitlab 进行项目修改保存时候&#xff0c;出现了 500 错误&#xff0c;经查看日志&#xff0c;发现 OpenSSL::Cipher::CipherError 异常&#xff0c;如下图所示&#xff1a; > /var/log/gitlab/gitlab-rails/production.log <OpenSSL::Cipher::CipherErro…

“深度解析:等级保护测评的核心要素与实施流程“

等级保护测评的核心要素与实施流程是确保信息系统安全的重要环节。以下是对等级保护测评的核心要素和实施流程的深度解析&#xff1a; 核心要素 等级测评概述 1 等级测评是依据国家信息安全等级保护制度规定&#xff0c;对信息系统的安全状况进行检测评估&#xff0c;判定系统…

【Hive SQL 每日一题】行列转换

文章目录 行转列列传行 行转列 测试数据&#xff1a; DROP TABLE IF EXISTS student_scores;CREATE TABLE student_scores (student_id INT,subject STRING,score INT );INSERT INTO student_scores (student_id, subject, score) VALUES (1, Math, 85), (1, English, 78), (…