问题描述
一个用户反映线上一个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的字段尽量设计为类型完全相同。
3 楼 zhengyutao 24 小时前 引用
我这儿有个慢查询,网上找了好久没有找到答案,在此咨询一下。
table_test表建立了c1, c2, c1|c2 三个索引。
select * from table_test where c1 = ? and c2 = ?
这个查询速度很快
下面的搜索语句就非常慢。
select * from table_test where (c1, c2) = (?, ?)
不知道什么问题导致。
求解答。
http://dinglin.iteye.com/blog/2070165
分享到:
相关推荐
以下是对SQL查询慢的十大原因的详细分析: 1. **没有索引或者索引使用不当**:索引是数据库优化的关键工具,用于快速定位和检索数据。如果没有为查询的字段创建索引,或者索引设计不合理,数据库必须进行全表扫描,...
SQL查询分析器是一种重要的数据库管理工具,它允许用户编写、测试和优化SQL语句,以高效地查询和操作数据。在数据库开发、管理和维护中,SQL查询分析器扮演着不可或缺的角色。下面将详细介绍SQL查询分析器及其相关...
帆软报表查询性能SQL分析插件是一款专门针对帆软报表系统的增强工具,旨在提升报表查询效率和性能优化。此插件能够深入解析SQL查询过程,帮助用户识别并解决潜在的性能瓶颈,确保报表的快速准确生成。以下是关于这款...
本压缩包文件"sql 查询慢的48个原因分析.doc"提供了对这一问题的深入探讨,旨在帮助用户识别并解决SQL查询速度减慢的各种因素。以下是对这48个原因的详细分析: 1. **不恰当的索引设计**:缺乏索引或索引设计不合理...
7. 性能优化:通过分析查询执行的时间和资源消耗,SQL查询分析器可以帮助用户识别慢查询并进行优化。这可能包括改写SQL语句、添加索引、调整数据库参数等。 8. 多会话管理:用户可以在一个界面上同时打开多个查询...
3. SQL优化:工具可能提供性能分析功能,帮助用户识别并优化慢查询,提升数据库性能。 4. 数据导入导出:用户可能能够方便地将数据从数据库导出为CSV或其他格式,或者从文件导入数据到数据库。 5. 安全性:工具可能...
这个“SQL查询分析器修正分离版”很可能是一个独立版本的软件,专门设计用于解决某些特定问题或提供更高效的工作环境。在本文中,我们将深入探讨SQL查询分析器的功能、其在数据库管理中的应用,以及可能的修正和分离...
SQL查询分析器是一款用于执行和测试SQL语句的工具,尤其在数据库开发和管理中扮演着重要角色。这款“SQL查询分析器绿色精简版”更强调便携性和轻量化,使得用户无论在哪里都能快速地进行SQL操作。下面将详细阐述SQL...
SQL查询分离器休闲庄版,正如其名,可能是该工具的一个特定版本,专为用户提供更加便捷和稳定的体验。 在数据库管理中,SQL(Structured Query Language)是用于操作关系型数据库的标准语言,包括创建、更新、查询...
查询分析器是SQL Server 2000中的一个核心组件,它允许数据库管理员和开发人员编写、测试和执行SQL语句。精简版的查询分析器可能意味着它提供了基础的SQL查询功能,对于日常的数据库查询和性能调试非常实用。 1. **...
《SQL Server 2008查询性能优化》指出的性能要点之一是数据库随着用户和数据的日益增多而进行扩展的必要性。你需要理解性能低下的起因。以及识别并修复它们的方法。《SQL Server 2008查询性能优化》将帮助你: 使用...
在SQL Server数据库中,查询优化是一项至关重要的任务,它直接影响到系统的性能和效率。这篇论文集包含了一系列关于SQL语句查询优化的专业文献,对于数据库管理员、开发者以及对数据库性能优化感兴趣的学生来说,都...
SQL查询分析器SQLMon.exe是一款专门用于监测和优化SQL Server数据库查询性能的工具。它能够帮助数据库管理员(DBA)和开发人员深入了解SQL查询的执行情况,找出性能瓶颈,从而提高数据库系统的整体效率。 在数据库...
5. **查询执行计划分析**:通过EXPLAIN或查询分析器查看执行计划,找出低效的操作,如全表扫描,然后优化查询或调整索引。 二、分页算法方案 1. **传统OFFSET/FETCH分页**:SQL Server 2012引入了OFFSET/FETCH语法...
在企业级数据库管理中,SQL性能优化是一个至关重要的方面。尤其是在Oracle数据库中,慢SQL的存在可能会导致系统性能下降、响应时间延长甚至引发系统崩溃。因此,抓取运行较慢的SQL语句是数据库管理员的重要任务之一...
以下是一个示例查询: ```sql SELECT sa.SQL_TEXT, sa.SQL_FULLTEXT, sa.EXECUTIONS "执行次数", ROUND(sa.ELAPSED_TIME / 1000000, 2) "总执行时间", ROUND(sa.ELAPSED_TIME / 1000000 / sa.EXECUTIONS,...
1. **查询优化**:利用查询分析器的执行计划功能,找出慢查询的原因,优化SQL语句。 2. **模板和快捷键**:预设的SQL模板和快捷键可提高编写效率。 3. **代码高亮与提示**:自动代码完成和语法高亮让编码更轻松。 4....