我正在通过慢查询日志来尝试确定为什么某些查询行为不正常。为了保持一致性,在运行测试之前不会缓存查询并执行刷新以清除系统缓存。查询是这样的:
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。
答案 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)
一些事情:
为什么在SELECT和别名中计算P.price和E.rate的乘积为'ask',然后在where子句中再次计算?由于MySQL的工作方式,应该可以执行AND ask BETWEEN k? and k?
- 修改:这不起作用。显然,MySQL会在任何别名(sourced)之前评估WHERE子句。
您对Exchange.currency和Property.currency有什么样的索引?如果exchange是一个查找表,也许你最好用Property.Id和Exchange.Id添加一个pivot(链接)表
按floor_area的顺序强制MySQL创建临时表以便正确排序,你有机会在应用层进行排序吗?
在类型列上添加索引也会有所帮助。
- 修改
不确定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是显而易见的事情。