与MyISAM相比,innodb的距离搜索速度非常慢

时间:2016-07-27 17:44:08

标签: mysql query-optimization innodb distance myisam

我有一个简单的SELECT mysql请求,用于按距离排序用户,如下所示:

SELECT 
( 6371 * acos( cos( radians(48.85980226) ) * cos( radians( latitude ) ) * cos( radians
( longitude ) - radians(2.29202271) ) + sin( radians(48.85980226) ) * sin( radians( latitude
 ) ) ) ) AS distance,           
id FROM `users` 
HAVING distance <= '100' 
ORDER BY distance ASC

我的数据库中有大约50.000个用户(MySql 5.7)。 当我将我的表设置为MyISAM时,请求速度是合理的,大约0.2s;但如果我把发动机转到innodb,大约需要8秒! 我真的需要使用innodb,因为数据非常容易写入和读取(MyISAM导致很多“myisam等待表级锁定”)。 知道如何优化该查询的速度吗? 谢谢!

(对不起我的英文)

EDIT2 :我更改了coordonates的类型,从DECIMAL更改为FLOAT,查询速度更快:5s输入8s ...

编辑3 (来自评论,带有边界框)

SELECT  ( 6371 * acos( cos( radians(48.85980226) ) *
        cos( radians( latitude ) ) * cos( radians ( longitude ) -
           radians(2.29202271) ) + sin( radians(48.85980226) ) *
            sin( radians( latitude ) ) ) ) AS distance,
        uid
    FROM  users
    WHERE  longitude between 0.089154409442052 AND 4.4948910105579
      AND  latitude between 47.410526897681 AND 50.309077622319
    HAVING  distance <= '100'
    ORDER BY  distance ASC

编辑4 :这是我的表结构:

CREATE TABLE `users` 
( `id` mediumint(9) NOT NULL AUTO_INCREMENT, 
`uid` varchar(20) NOT NULL, 
`token` varchar(70) NOT NULL, 
`last_connection` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 
`email` varchar(255) NOT NULL, 
`longitude` float NOT NULL, 
`latitude` float NOT NULL, 
`presentation` text NOT NULL, 
PRIMARY KEY (`id`), 
KEY `uid` (`uid`), 
KEY `uid_token` (`uid`,`token`), 
KEY `longitude` (`longitude`), 
KEY `latitude` (`latitude`) 
) ENGINE=InnoDB AUTO_INCREMENT=53004 DEFAULT CHARSET=utf8

字段last_connection经常更新。在线用户越多,查询越慢...我猜是因为更新行暂时被锁定而查询变慢......:/ 使用MyISAM时,搜索查询是正常的,但更新速度很慢(为了锁定)

编辑5 这是我的更新查询:

UPDATE `users` SET `last_connection` = CURRENT_TIMESTAMP WHERE `uid` = 'XXXX';  

我更改了它并添加了限制1:

UPDATE `users` SET `last_connection` = CURRENT_TIMESTAMP WHERE `uid` = 'XXXX' LIMIT 1;  

这似乎更快。我需要等待更多用户连接以检查差异是否大

2 个答案:

答案 0 :(得分:0)

数据库引擎对每一行进行计算。

那么如何计算已存储为变量的值?

SET @cos_point1 = cos(radians(48.85980226));

SET @rad_point1 = radians(2.29202271);

SET @sin_point1 = sin(radians(48.85980226));

SELECT 
( 6371 * acos( @cos_point1 * cos( radians( latitude ) ) * cos( radians
( longitude ) - @rad_point1 ) + @sin_point1 * sin( radians( latitude
 ) ) ) ) AS distance,           
id FROM `users` 
HAVING distance <= 100
ORDER BY distance ASC;



我也有一个想法!

试试这个:

1)创建Memory类型的表users_geodata(因为实际数据将在users表中,让我们使用最快的引擎作为临时表):

CREATE TABLE `users_geodata` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `latitude` float NOT NULL,
  `longitude` float NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=Memory;

2)安排执行此操作的同步:

REPLACE INTO users_geodata 
SELECT id, latitude, longitude FROM users

3)运行您的查询:

SELECT 
( 6371 * acos( cos( radians(48.85980226) ) * cos( radians( latitude ) ) * cos( radians
( longitude ) - radians(2.29202271) ) + sin( radians(48.85980226) ) * sin( radians( latitude
 ) ) ) ) AS distance,           
id FROM `users_geodata` 
HAVING distance <= 100 
ORDER BY distance ASC

答案 1 :(得分:0)

由于InnoDB和MyISAM必须完成相同数量的工作,我怀疑真正的问题在于缓存。检查这些值:

key_buffer_size
innodb_buffer_pool_size

并注意你有多少RAM。

如果您只使用InnoDB,请检查buffer_pool是否约为可用内存的70%(适用于4GB或更多的计算机)。 More details

加快速度的下一步是让WHERE子句包含“边界框”,加上INDEX(latitude)INDEX(longitude)。 (使用复合索引没有任何优势。)

For huge lat/lng tables

使用“覆盖”索引

KEY(latitude)替换为KEY(latitude, longitude, uid),将KEY(longitude)替换为KEY(longitude, latitude, uid)。这些将是“覆盖”,因此有点快,并且可能不那么有争议。 (优化程序将根据基于实际查询中的值的统计信息在两个索引之间进行选择。)