尽管检查了相似的行数,为什么查询时间会出现峰值?

时间:2012-01-04 05:17:30

标签: mysql sql performance testing indexing

我正在通过慢查询日志来尝试确定为什么某些查询行为不正常。为了保持一致性,在运行测试之前不会缓存查询并执行刷新以清除系统缓存。查询是这样的:

SELECT P.id, P.name, P.lat, P.lng, P.price * E.rate AS 'ask' FROM Property P
 INNER JOIN Exchange E ON E.currency = P.currency
 WHERE P.floor_area >= k?
  AND P.closing_date >= CURDATE() // this and key_buffer_size=0 prevents caching
  AND P.type ='c'
  AND P.lat BETWEEN v? AND v?
  AND P.lng BETWEEN v? AND v?
  AND P.price * E.rate BETWEEN k? AND k?
 ORDER BY P.floor_area DESC LIMIT 100;

k?是用户定义的常量值; v?变量,随着用户在地图上拖动或缩放而更改。从表中提取100个结果,并按照楼面面积按降序排序。

id上的PRIMARY键和floor_area上的INDEX仅设置。没有创建其他索引,因此MySQL将始终使用floor_area作为唯一密钥。检查的查询时间和行记录如下:

query number              1    2    3    4    5    6    7    8    9    10
user action on map     start   >    +    +    <    ^    +    >    v    +
time in seconds          138  0.21 0.43 32.3 0.12 0.12 36.3 4.33 0.33 2.00
rows examined ('000)      43    43   43   60   43   43  111  139  133  176

查询执行计划如下:

+----+-------------+-------+--------+---------------+---------+---------+--------------------+---------+-------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                | rows    | Extra       |
+----+-------------+-------+--------+---------------+---------+---------+--------------------+---------+-------------+
|  1 | SIMPLE      | P     | range  | id_flA        | id_flA  | 3       | NULL               | 4223660 | Using where |
|  1 | SIMPLE      | E     | eq_ref | PRIMARY       | PRIMARY | 3       | BuySell.P.currency |       1 | Using where |
+----+-------------+-------+--------+---------------+---------+---------+--------------------+---------+-------------+

测试正在进行几次,结果与上述结果非常一致。在第4号和第7号查询中,查询时间高峰原因可能是什么,如何将其删除?

更新

Digital Precision建议删除ORDER BY的结果:

query number              1    2    3    4    5    6    7    8    9    10
user action on map     start   >    +    +    <    ^    +    >    v    +
time in seconds          255  3.10 3.16 3.08 3.18 3.21 3.32 3.18 3.17 3.80
rows examined ('000)     131  131  131  131  136  136  136  136  136  157

查询执行计划与上面相同,虽然看起来更像是表扫描。请注意,我使用的是 MyISAM 引擎,版本5.5.14。

根据要求,下面是架构:

| Property | CREATE TABLE `Property` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `type` char(1) NOT NULL DEFAULT '',
  `lat` decimal(6,4) NOT NULL DEFAULT '0.0000',
  `lng` decimal(7,4) NOT NULL DEFAULT '0.0000',
  `floor_area` mediumint(8) unsigned NOT NULL DEFAULT '0',
  `currency` char(3) NOT NULL DEFAULT '',
  `price` int(10) unsigned NOT NULL DEFAULT '0',
  `closing_date` date NOT NULL DEFAULT '0000-00-00',
  `name` char(25) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`),
  KEY `id_flA` (`floor_area`)
) ENGINE=MyISAM AUTO_INCREMENT=5000000 DEFAULT CHARSET=latin1

| Exchange | CREATE TABLE `Exchange` (
  `currency` char(3) NOT NULL,
  `rate` decimal(11,10) NOT NULL DEFAULT '0.0000000000',
  PRIMARY KEY (`currency`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1

2ND UPDATE:

我认为在my.cnf配置文件中发布非默认参数是合适的,因为有两位回答者提到了这些参数:

max_heap_table_size = 1300M
key_buffer_size = 0
read_buffer_size = 1300M
read_rnd_buffer_size = 1024M
sort_buffer_size = 1300M

我的测试服务器上有2GB的RAM。

4 个答案:

答案 0 :(得分:4)

我想我找出了尖峰的原因。这是怎么回事:

首先,我创建了表并在其上加载了一些随机生成的数据:

这是我的问题:

SELECT SQL_NO_CACHE P.id, P.name, P.lat, P.lng, P.price * E.rate AS 'ask' 
FROM Property P
 INNER JOIN Exchange E ON E.currency = P.currency
 WHERE P.floor_area >= 2000
  AND P.closing_date >= CURDATE()
  AND P.type ='c'
  AND P.lat BETWEEN 12.00 AND 22.00
  AND P.lng BETWEEN 10.00 AND 20.00
  AND P.price BETWEEN 100 / E.rate AND 10000 / E.rate
 ORDER BY P.floor_area DESC LIMIT 100;

以下是描述:

+----+-------------+-------+-------+---------------+--------+---------+------+---------+----------------------------------------------+
| id | select_type | table | type  | possible_keys | key    | key_len | ref  | rows    | Extra                                        |
+----+-------------+-------+-------+---------------+--------+---------+------+---------+----------------------------------------------+
|  1 | SIMPLE      | P     | range | id_flA        | id_flA | 3       | NULL | 4559537 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | E     | ALL   | PRIMARY       | NULL   | NULL    | NULL |       6 | Using where; Using join buffer               |
+----+-------------+-------+-------+---------------+--------+---------+------+---------+----------------------------------------------+

每次查询数据时都需要 3.5~3.9 秒(我使用的参数没有任何区别)。它没有意义所以我研究了Using join buffer

然后我想在没有“join buffer”的情况下尝试这个查询,所以我在Exchange表中再插入了1个随机数据。

INSERT INTO Exchange(currency, rate) VALUES('JJ', 1);

现在我使用相同的sql并且响应时间 0.3~0.5 秒。以下是描述:

+----+-------------+-------+--------+---------------+---------+---------+-----------------+---------+-------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref             | rows    | Extra       |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+---------+-------------+
|  1 | SIMPLE      | P     | range  | id_flA        | id_flA  | 3       | NULL            | 4559537 | Using where |
|  1 | SIMPLE      | E     | eq_ref | PRIMARY       | PRIMARY | 3       | test.P.currency |       1 | Using where |
+----+-------------+-------+--------+---------------+---------+---------+-----------------+---------+-------------+

所以问题(据我所知),优化器试图使用“连接缓冲区”。此问题的最佳解决方案是强制优化器不使用“连接缓冲区”。 (我找不到如何)或更改“join_buffer_size”值。我通过向Exchange表添加“虚拟”值来解决它(因此优化器不会使用连接缓冲区)但它不是一个精确的解决方案,它只是一个愚弄mysql的愚蠢技巧。

编辑:我在mysql论坛/ bug中研究了这个“连接缓冲区”的行为;然后在official forums询问了这件事。我将填写关于优化器的这种非理性行为的错误报告。

答案 1 :(得分:3)

一些事情:

  1. 为什么在SELECT和别名中计算P.price和E.rate的乘积为'ask',然后在where子句中再次计算?由于MySQL的工作方式,应该可以执行AND ask BETWEEN k? and k? - 修改这不起作用。显然,MySQL会在任何别名(sourced)之前评估WHERE子句。

  2. 您对Exchange.currency和Property.currency有什么样的索引?如果exchange是一个查找表,也许你最好用Property.Id和Exchange.Id添加一个pivot(链接)表

  3. 按floor_area的顺序强制MySQL创建临时表以便正确排序,你有机会在应用层进行排序吗?

  4. 在类型列上添加索引也会有所帮助。

  5. - 修改

    不确定CURDATE上注释// this and key_buffer_size=0 prevents caching的含义是什么条件,你可以使用select语句中的'SQL_NO_CACHE'标志强制不进行sql缓存。

    我现在建议您删除ORDER BY,更新查询语句如下(为列添加P别名以减少混淆):

    WHERE P.type ='condominium'
        AND P.floor_area >= k?
        AND P.closing_date >= CURDATE() // No longer necessary with SQL_NO_CACHE
        AND P.lat BETWEEN v? AND v?
        AND P.lng BETWEEN v? AND v?
        AND P.price * E.rate BETWEEN k? AND k?
    

    然后在'type'列中添加索引,在'type'和'floor_area'列上添加复合索引。正如您所说,类型列是一个低基数列,但表格很大,应该有所帮助。即使floor_area似乎是一个高基数列,复合索引也有助于加快查询时间。

    您可能还想研究是否使用BETWEEN而不是范围运算符(&gt;,&lt;,&lt; =等)

答案 2 :(得分:3)

尝试使用type和floor_area的索引(也可能是closing_date)。

按汇率而不是价格列修改常量:

P.price between ( k? / E.rate ) and ( k? / E.rate )

然后尝试价格指数。

答案 3 :(得分:2)

我对这个问题有点痴迷;穗很难解释。

这就是我的所作所为:

我重新创建了您的架构,并使用450万条记录填充了属性表,其中包含数字和日期列的随机值。这几乎肯定与你的数据不符 - 我猜测纬度/多头往往聚集在人口区域,10K倍数左右的价格,以及楼面空间将偏向低端值。

我使用lat,long,floorpace和price的一系列值运行查询。只有楼面面积的索引,我看到查询计划会忽略楼面面积的某些值的索引。这可能是因为查询分析器决定使用索引排除的记录数太少。但是,在重新运行各种不同场景的查询时,我注意到查询计划会不时地忽略索引 - 无法解释。

在处理这种奇怪现象时,总是值得运行ANALYZE TABLE

我确实得到了稍微不同的“解释”结果:具体来说,属性表选择了'使用where;使用临时;使用filesort'。这表明索引仅用于where子句,而不是用于排序结果。

这证实了性能峰值的最可能解释与查询引擎无关,而是与临时表的处理方式以及执行文件排序的要求有关。在尝试重现这个问题时,我注意到响应时间显着增加,因为从“where”子句返回的记录数量增加了 - 虽然我没有看到你注意到的尖峰。

我尝试了各种不同的指数;使用where子句中的所有键确实可以加快检索与where子句匹配的记录的时间,但不会对后续的顺序执行任何操作。

再一次,这表明临时表的性能是导致峰值的原因。 read_rnd_buffer_size是显而易见的事情。