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.)
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.