`
丁林.tb
  • 浏览: 789566 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

一个用户SQL慢查询分析,原因及优化

阅读更多

问题描述

一个用户反映线上一个SQL语句执行时间慢得无法接受。SQL语句看上去很简单(本文描述中修改了表名和字段名):
SELECT count(*)  FROM  a  JOIN  b ON  a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00' ; 

且查询需要的字段都建了索引,表结构如下:
CREATE TABLE `a` (
  `L` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
  `I` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `A` varchar(32) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `S` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `F` tinyint(4) DEFAULT NULL,
  `V` varchar(256) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT '',
  `N` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  KEY `IX_L` (`L`),
  KEY `IX_I` (`I`),
  KEY `IX_S` (`S`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `b` (
  `R` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00',
  `V` varchar(32) DEFAULT NULL,
  `U` varchar(32) DEFAULT NULL,
  `C` varchar(16) DEFAULT NULL,
  `S` varchar(64) DEFAULT NULL,
  `I` varchar(64) DEFAULT NULL,
  `E` bigint(32) DEFAULT NULL,
  `ES` varchar(128) DEFAULT NULL,
  KEY `IX_R` (`R`),
  KEY `IX_C` (`C`),
  KEY `IX_S` (`S`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

从语句看,这个查询计划很自然的,就应该是先用a作为驱动表,先后使用 a.L和b.S这两个索引。而实际上explain的结果却是:
    +----+-------------+-------+-------+---------------+------+---------+----------+---------+-------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref      | rows    | Extra       |
+----+-------------+-------+-------+---------------+------+---------+----------+---------+-------------+
|  1 | SIMPLE      | b     | index | IX_S          | IX_S | 195     | NULL     | 1038165 | Using index |
|  1 | SIMPLE      | a     | ref   | IX_L,IX_S     | IX_S | 195     | test.b.S |       1 | Using where |
+----+-------------+-------+-------+---------------+------+---------+----------+---------+-------------+

分析

从explain的结果看,查询用了b作为驱动表。
上一篇文章我们介绍到,MySQL选择jion顺序是分别分析各种join顺序的代价后,选择最小代价的方法。
这个join只涉及到两个表,自然也与optimizer_search_depth无关。于是我们的问题就是,我们预期的那个join顺序的为什么没有被选中?

MySQL Tips: MySQL提供straight_join语法,强制设定连接顺序。

explain SELECT count(*)  FROM  a  straight_join  b ON  a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00' ;            
+----+-------------+-------+-------+---------------+------+---------+------+---------+---------------------------------------------+
| id | select_type | table | type  | possible_keys | key  | key_len | ref  | rows    | Extra                                       |
+----+-------------+-------+-------+---------------+------+---------+------+---------+---------------------------------------------+
|  1 | SIMPLE      | a     | range | IX_L,IX_S     | IX_L | 4       | NULL |      63 | Using where                                 |
|  1 | SIMPLE      | b     | index | IX_S          | IX_S | 195     | NULL | 1038165 | Using where; Using index; Using join buffer |
+----+-------------+-------+-------+---------------+------+---------+------+---------+---------------------------------------------+
MySQL Tips: explain结果中,join的查询代价可以用依次连乘rows估算。
join顺序对了,简单的分析查询代价:普通join是1038165*1, straight_join是 63*1038165. 貌似MySQL没有错。但一定哪里不对!

发现异常

回到我们最初的设想。我们预计表a作为驱动表,是因为认为表b能够用上IX_S索引,而实际上staight_join的时候确实用上了,但这个结果与我们预期的又不同。
我们知道,索引的过滤性是决定了一个索引在查询中是否会被选中的重要因素,那么是不是b.S的过滤性不好呢?
MySQL Tips: show index from tbname返回结果中Cardinality的值可以表明一个索引的过滤性。
show index的结果太多,也可以从information_schema表中取。
mysql> select * from information_schema.STATISTICS where table_name='b' and index_name='IX_S'\G
*************************** 1. row ***************************
TABLE_CATALOG: def
 TABLE_SCHEMA: test
   TABLE_NAME: b
   NON_UNIQUE: 1
 INDEX_SCHEMA: test
   INDEX_NAME: IX_S
 SEQ_IN_INDEX: 1
  COLUMN_NAME: S
    COLLATION: A
  CARDINALITY: 1038165
     SUB_PART: NULL
       PACKED: NULL
     NULLABLE: YES
   INDEX_TYPE: BTREE
      COMMENT: 
INDEX_COMMENT: 

可以这个索引的CARDINALITY: 1038165,已经很大了。那这个表的估算行是多少呢。
show table status like 'b'\G
*************************** 1. row ***************************
           Name: b
         Engine: InnoDB
        Version: 10
     Row_format: Compact
           Rows: 1038165
 Avg_row_length: 114
    Data_length: 119160832
Max_data_length: 0
   Index_length: 109953024
      Data_free: 5242880
 Auto_increment: NULL
    Create_time: 2014-05-23 00:24:25
    Update_time: NULL
     Check_time: NULL
      Collation: utf8_general_ci
       Checksum: NULL
 Create_options: 
        Comment: 
1 row in set (0.00 sec)
从Rows: 1038165看出,IX_S这个索引的区分度被认为非常好,已经近似于唯一索引。

MySQL Tips: 在show table status结果中看到的Rows用于表示表的当前行数。对于MyISAM表这是一个精确值,但对InnoDB这是个估算值。

虽然是估算值,但优化器是以此为指导的,也就是说,上面的某个explain里面的数据完全不符合期望:staight_join结果中第二行的rows。

目前为止

我们发现整个错误的逻辑是这样的:以a为驱动表的执行计划,由于索引b.S的rows估计为1038165导致优化器认为代价大于以b为驱动表。
而实际上这个索引的区分度为1.
(当然对explan结果比较熟悉的同学会发现,第二行的type字段和Extra字段一起诡异了)

也就是说,straight_join得到的每一行去b中查询的时候,都走了全表扫描。在MySQL里面出现这种情况的最常见的是类型转换。比如一个字符串字段,虽然包含的是全数字,但查询的时候传入的不是字符串格式。

在这个case里面,两个都是字符串。因此,就是字符集相关了。
回到两个表结构,发现S字段的声明差别在于 COLLATE utf8_bin -- 这个就是本case的根本原因了:a表得到的S值是utf8_bin,优化器认为类型不同,无法直接用上索引b.IX_S过滤。

至于为什么还会用上索引,这个是因为覆盖索引带来“误解”。
MySQL Tips:若查询的所有结果能够从某个索引完全得到,则会优先用遍历索引替代遍历数据。
作为验证,
mysql> explain SELECT *  FROM  a  straight_JOIN  b ON  binary a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00' ;        

+—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | 
+—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 
| 1 | SIMPLE | a | range | IX_L | IX_L | 4 | NULL | 63 | Using where | 
| 1 | SIMPLE | b | ALL | IX_S | NULL | NULL | NULL | 1038165 | Range checked for each record (index map: 0x4) | 
+—-+————-+——-+——-+—————+——+———+——+———+————————————————+ 
由于结果是select *, 无法使用覆盖索引,因此第二行的key就显示为NULL. (笔者泪:要是早出这个结果查起来可方便多了)。

优化

当然最直接的想法就是修改两个表的S字段的定义,改成相同即可。这个方法可以避免修改业务代码,但DDL代价略大。这里提供两种在SQL语句方面的优化。

1、select count(*) from  b join (select s from  a  WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00') ta on b.S=ta.s;
这个写法比较直观,需要注意最后b.S和ta.S的顺序

2、SELECT count(*)  FROM  a  JOIN  b ON  binary a.`S` = b.`S` WHERE a.`L` > '2014-03-30 00:55:00' AND a.`L` < '2014-03-30 01:00:00' ;
从前面的分析知道是由于b.S定义为utf8_bin.
MySQL Tips: MySQL中字符集命名规则中, XXX_bin与XXX的区别为大小写是否敏感。
这里我们将A.s全部增加binary限定,先转为小写,就是将临时结果集转成utf8_bin,之后使用b.S匹配时就能够直接利用索引。
其实两个改写方法的本质相同,区别是写法1是隐式转换。理论上说写法2速度更快些。

小结

做join的字段尽量设计为类型完全相同。
10
5
分享到:
评论
4 楼 di1984HIT 2014-07-29  
写的很好啊。
3 楼 zhengyutao 2014-05-24  
我这儿有个慢查询,网上找了好久没有找到答案,在此咨询一下。

table_test表建立了c1, c2, c1|c2 三个索引。

select * from table_test where c1 = ? and c2 = ?
这个查询速度很快

下面的搜索语句就非常慢。
select * from table_test where (c1, c2) = (?, ?)

不知道什么问题导致。
求解答。
2 楼 lvwenwen 2014-05-23  
写的非常详细,xx
1 楼 shyboy0358 2014-05-23  
lz分享的东西不错,可惜基本的帖子格式没掌握,内容显示不全,希望能改进

相关推荐

    SQL查询安全性及性能优化

     优化:【检查SQL语句是否能够使用索引,SQL语句如何执行效率高,制定一个执行计划】  编译:  执行SQL语句:【把编译过的查询要求提交并进行处理】 如何优化SQL 完善开发的管理 完善代码审核、测试机制,...

    SQL查询慢的48个原因分析.doc

    形成了瓶颈效应、内存不足、网络速度慢、查询出的数据量过大、锁或者死锁、sp_lock,sp_who,活动的用户查看,原因是读写竞争资源、返回了不必要的行和列、查询语句不好,没有优化……

    SQLServer2008查询性能优化 2/2

    《SQL Server 2008查询性能优化》指出的性能要点之一是数据库随着用户和数据的日益增多而进行扩展的必要性。你需要理解性能低下的起因。以及识别并修复它们的方法。《SQL Server 2008查询性能优化》将帮助你: 使用...

    SQLServer2008查询性能优化 1/2

    《SQL Server 2008查询性能优化》指出的性能要点之一是数据库随着用户和数据的日益增多而进行扩展的必要性。你需要理解性能低下的起因。以及识别并修复它们的方法。《SQL Server 2008查询性能优化》将帮助你: 使用...

    sql 查询慢的原因分析

    4、内存不足 5、网络速度慢 6、查询出的数据量过大(可以采用多次查询,其他的方法降低数据量) 7、锁或者死锁(这也是查询慢最常见的问题,是程序设计的缺陷) 8、sp_lock,sp_who,活动的用户查看,原因是读写竞争资源...

    MySQL SQL语句分析与查询优化详解

    如何获取有性能问题的SQL 1、通过用户反馈获取存在性能问题的SQL 2、通过慢查询日志获取...2、slow_query_log_file 指定慢查询日志的存储路径及文件 建议日志存储和数据存储分开存储 3、long_query_time 指定记录慢查询

    迷你版SQL2000服务器

    +数据库页面,集成外部工具“查询分析器”与“事件探查器”,系SQL自带程序,方便使用; -删除内置“查询分析”功能页; ^数据库页面,密码修改,允许任意字符,不再限制只能为0~9a~z了,但不允许为空; ^数据库...

    SQL Server 存储过程与实例

    因为存储过程是预编译的,在首次运行一个存储过程时,查询优化器对其进行分析、优化,并给出最终被存在系统表中的存储计划。而批处理的T-SQL语句每次运行都需要预编译和优化,所以速度就要慢一些。 C、 存储过程...

    SQL智能完成工具BDB

    新编译的BDB版本,支持更智能化的SQL 查询分析提示、 输入自动完成功能。 &lt;br&gt;该功能类似SQLPrompt或MySQL-Front的智能化输入。 不过与上述软件只能在单个特定数据库平台使用不同, BDB支持更多的数据库...

    MySQL的慢日志线上问题及优化方案

    本文将结合一个线上案例,分析如何正确设置 MySQL 慢日志参数和使用慢日志功能,并介绍下网易云 RDS 对 MySQL 慢日志功能的增强。 MySQL 参数组功能 网易云 RDS 实例提供了参数组管理功能,可通过参数管理界面查看...

    通过MySQL慢查询优化MySQL性能的方法讲解

    随着访问量的上升,MySQL数据库的压力就越大,几乎大部分使用MySQL架构的web应用在数据库上都会出现性能问题,通过mysql慢查询日志跟踪有问题的查询非常有用,可以分析出当前程序里有很耗费资源的sql语句。...

    一个优化MySQL查询操作的具体案例分析

    一个用户反映先线一个SQL语句执行时间慢得无法接受。SQL语句看上去很简单(本文描述中修改了表名和字段名): SELECT count(*) FROM a JOIN b ON a.`S` = b.`S` WHERE a.`L` &gt; ‘2014-03-30 00:55:00’ AND a.`L` &lt;...

    2017最新老男孩MySQL高级专业DBA实战课程全套【清晰不加密】,看完教程月入40万没毛病

    14-企业生产场景MySQL主从复制故障原因及实战解决.avi 15-从库开启BINLOG原因及开启实战讲解1.avi 16-从库开启BINLOG原因及开启实战讲解2.avi 17-企业场景一主多从宕机从库切换主继续和从同步过程详解1.avi 18-企业...

    Toad 使用快速入门

    使用Toad,我们可以通过一个图形化的用户界面快速访问数据库,完成复杂的SQL和PL/SQL代码编辑和测试工作。Toad由Oracle开发专家专门为开发人员而设计,是一个功能强大、结构紧凑的专业化PL/SQL开发环境。 Toad 主要...

    一种大数据智能分析平台的数据分析方法及实现技术.doc

    2 功能设计 2.1 总体架构 本文讲述的数据分析方法及实现技术是建立在Hadoop/Spark技术生态圈的基础 之上,以实现用户集成处理、、清理、分析的一个统一的数据处理平台;按数据类别分 为线数据、归档数据;按数据...

    基于Jsp的图书馆管理系统软件设计源码+数据库+sql+毕业论文文档资料.zip

    图书借阅管理系统是一个基于B/S模式的对图书馆进行高效率管理的应用系统,它的用户主要是读者和图书管理员,学生通过该系统进行图书查询进而对自己需要的图书进行借阅及自己的借阅情况进行查询,图书管理员则通过本系统...

    数据库设计与优化.pdf

    1.3.3 主键的设计 主键是必要的,SQL SERVER 的主键同时是一个唯一索引,而且在实际应用中,我们往往选择最小的键组合作为主键, 第 2 页 共 19 页 所以主键往往适合作为表的聚集索引。聚集索引对查询的影响是比较...

    公交查询系统修正版 仿

    再者同时有多个用户使用这个系统的话就会给他们一个刷新很慢的感觉。为了满足更多的用户同时在线使用。只有两种方法:一是优化JS程序段,使它的运行时间减少,也就是在茫茫数据海洋中搜索的时间减少,一些不需要的...

    公交车查询系统 v3.30.rar

    再者同时有多个用户使用这个系统的话就会给他们一个刷新很慢的感觉。为了满足更多的用户同时在线使用。只有两种方法:一是优化JS程序段,使它的运行时间减少,也就是在茫茫数据海洋中搜索的时间减少,一些不需要的...

    全国公交查询asp源码

    再者同时有多个用户使用这个系统的话就会给他们一个刷新很慢的感觉。为了满足更多的用户同时在线使用。只有两种方法:一是优化JS程序段,使它的运行时间减少,也就是在茫茫数据海洋中搜索的时间减少,一些不需要的...

Global site tag (gtag.js) - Google Analytics