1

I have the following database structure:

CREATE TABLE IF NOT EXISTS `business` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `address` varchar(255) NOT NULL,
  `city` varchar(255) NOT NULL,
  `state` varchar(255) NOT NULL,
  `postal` int(11) NOT NULL,
  `country` varchar(255) NOT NULL,
  `lat` float NOT NULL,
  `lng` float NOT NULL,
  `name` varchar(255) NOT NULL,
  `phone` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `website` varchar(255) NOT NULL,
  `userID` bigint(20) NOT NULL,
  `url` varchar(255) NOT NULL,
  `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `clicks` bigint(20) NOT NULL,
  `oHours` varchar(255) NOT NULL,
  `featured` tinyint(1) NOT NULL,
  `imageThumb` varchar(255) NOT NULL DEFAULT 'default.jpg',
  `imageOrig` varchar(255) NOT NULL DEFAULT 'default.jpg',
  `flag` tinyint(1) NOT NULL,
  `display` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=589846 ;

This table (business) has 507,736 records

CREATE TABLE IF NOT EXISTS `businesscat` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `bizID` bigint(20) NOT NULL,
  `catID` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=589863 ;

This table (businesscat) has 519,825 records

CREATE TABLE IF NOT EXISTS `category` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `url` varchar(255) NOT NULL,
  `icon` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;

This table (category) has only 5 records

So, my problem is I'm trying to get 100 records with calculation of nearest business using the ff:

SELECT business.name
, business.lng
, business.lat
, business.address
, business.city
, business.state
, business.postal
, business.phone
, business.url
, business.imageThumb
, businesscat.catID
, category.icon
, (((acos(sin((".$lat."*pi()/180)) 
  * sin((business.lat*pi()/180))
  + cos((".$lat."*pi()/180)) 
  * cos((business.lat*pi()/180)) 
  * cos(((".$lng."-business.lng)*pi()/180))))
  * 180/pi())*60*1.1515) AS distance
FROM business 
INNER JOIN businesscat ON businesscat.bizID=business.id 
INNER JOIN category ON category.id=businesscat.catID
ORDER BY distance LIMIT 100 

Any idea to make it faster?

rene
  • 41,474
  • 78
  • 114
  • 152
hodl
  • 1,420
  • 12
  • 21
  • I assume all runs fine if you leave out the Order by distance? – rene Dec 26 '11 at 12:28
  • 1
    Have you defined indexes for businesscat.bizID and businesscat.catID? Also, you might be able to optimize your businesscat table by dropping the id and declaring bizID and catID as the primary key, though I doubt that this will improve your query. – Mike Nakis Dec 26 '11 at 12:32
  • I changed BIGINT to INT is that fine? rene you're correct. It loads faster when I removed the ORDER BY distance. – hodl Dec 26 '11 at 13:11
  • MikeNakis has a point. and you need two indexes - otherwise it will traverse all the row in all thre tables – MikeyKennethR Dec 26 '11 at 13:29
  • Yeah, I did. It works fine now. I add back the ORDER BY distance, works fine. Thanks guys! – hodl Dec 26 '11 at 13:33
  • We are glad to be of help. Please do not forget to 'accept' the correct answer. – Mike Nakis Dec 26 '11 at 13:39

2 Answers2

2

I just read in the MySQL manual that subqueries can contain ORDER BY and LIMIT. So, my suggestion would be as follows:

Put your distance calculation in a separate subquery together with the ORDER BY and LIMIT clauses. Then, put your joins in an enclosing (outer) query. This way the operations of your joins will not be executed for hundreds of thousands of businesses which are outside your area of interest to begin with.

Also make sure you have defined indexes for businesscat.bizID and businesscat.catID.

EDIT: if that does not make your query fast enough for your purposes, then try the following:

Before entering the query, calculate the 'min' and 'max' longitude and latitude (100 meters to the north, west, east and south of your $lng and $lat.) Then, use these to pre-filter your businesses in an inner query as follows: WHERE business.lng >= $min_lng AND business.lng <= $max_lng AND business.lat >= $min_lat AND business.lat <= $max_lat. Then, in an enclosing query calculate the distance and re-filter by it. And of course this can be even further optimized by defining indexes on business.lng and business.lat.

Mike Nakis
  • 56,297
  • 11
  • 110
  • 142
0

Do you know how much data you can store in bigint? It'll suffice for the whole universe. Smallint or maybe mediumint is good.

All of the fields have varchar(255)! do you really need that much data?

You can cache mySQL query execution plan, do you use that?

Your tables' storage engines are InnoDB so let me ask you a more important question:

Do you use innodb_file_per_table setting?

index every field that you use in ORDER BY or in JOINS:

  • distance is not indexed
  • UserID is not indexed

EDIT

Are you really sure about that, I recommend you to double check the mySQL manual. I'm pretty sure about that, and that's good to know that I checked it now and varchar(255) certainly differ from varchar(20).

I think you confused INT(20) => INT and Varchar(255) => varchar(20)

Alireza
  • 6,497
  • 13
  • 59
  • 132