简体   繁体   中英

Distance search very slow with innodb compare to MyISAM

I have a simple SELECT mysql request for ordering the users by distance, like this :

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

I got about 50.000 users in my database (MySql 5.7). When I set my table to MyISAM, request speed is reasonable, about 0.2s; but if I turn the engine to innodb, it takes about 8s ! I really need to use innodb because datas are very offtenly write&read (MyISAM causing lot's of "myisam waiting for table level lock" ). Any idea of how to optimize the speed for that query? Thanks !

(sorry for my english)

EDIT2 : I change the type of the coordonates, from DECIMAL to FLOAT, and the query is a little faster : 5s insteed of 8s...

Edit3 (from comment, with bounding box)

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

Edit 4 : Here is my table structure :

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

The field last_connection is updated very frequently. The more users are online, the more the query going slower... I guess because of the updates the row is locked temporary and the query going slow... :/ When using MyISAM, the search query is OK, but the updates ones going slow (whaiting for lock)

EDIT 5 here is my update query :

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

I changed it and added a limit 1 :

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

This seems to be faster. I need to wait for more users to be connected to check if the difference is big or not

Database engine does calculation on every row.

So how about having already calculated values stored as variable?

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;



Also I've an idea!

Try this:

1) Create table users_geodata of type Memory (because real data will be in users table, let's use fastest engine for temporary table):

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) Schedule synchronization that does this:

REPLACE INTO users_geodata 
SELECT id, latitude, longitude FROM users

3) Run Your query:

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

Since InnoDB and MyISAM have to do equivalent amounts of work, I suspect the real problem is in caching. Check the values of these:

key_buffer_size
innodb_buffer_pool_size

and note how much RAM you have.

If you are using only InnoDB, check that the buffer_pool is about 70% of available RAM (for machines of 4GB or more). More details

The next step in speeding things up is to have a WHERE clause include a "bounding box", plus have INDEX(latitude) and INDEX(longitude) . (There is no advantage in using a composite index.)

For huge lat/lng tables .

Using "covering" index

Replace KEY(latitude) with KEY(latitude, longitude, uid) and KEY(longitude) with KEY(longitude, latitude, uid) . These will be "covering", hence somewhat faster, and possibly less contentious. (The Optimizer will pick between the two indexes based on statistics based on the values in the actual queries.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM