MySQL并解决“n + 1”模式(超过1M记录)

时间:2014-12-14 22:46:34

标签: mysql sql join geolocation

我正在运行此查询 - 使用ActiveRecord(Ruby On Rails):

@hotels = Hotel.includes(:services).within(distance, origin: city).where('reception = ?', 0).order("distance ASC")

半径为100英里,此查询持续约40秒。表hotels中约有300,000条记录,services中有1,100,000条记录。在我看来,问题是“n + 1”问题。我想改进搜索,因为等待40-50秒来获取结果不是非常用户友好。

我试图用原始MySQL重写ActiveRecord查询,如下所示:

SELECT hotels.*,
    69.0 * HAVERSINE(hotels.latitude,hotels.longitude, #{lat},#{lng}) AS distance
FROM hotels
WHERE hotels.latitude BETWEEN #{lat} - (#{distance} / 69.0)
    AND #{lat} + (#{distance} / 69.0)
    AND hotels.longitude BETWEEN #{lng} - (#{distance} / (69.0 * COS(RADIANS(#{lat}))))
    AND #{lng} + (#{distance} / (69.0 * COS(RADIANS(#{lat})))) 
    AND hotels.reception = 0
ORDER BY distance

当我运行此查询时,应用程序正在加载数据甚至更多时间 - 确切地说是400秒。我认为这是因为“n + 1”事情 - 在此查询中,没有涉及服务表中的数据。我试图将此表添加到查询中,如下所示:

SELECT hotels.*,
    69.0 * HAVERSINE(hotels.latitude,hotels.longitude, #{lat},#{lng}) AS distance
FROM hotels
JOIN hotel_services ON hotel_services.hotel_id = hotels.id
JOIN services ON hotels.id = hotel_services.service_id
WHERE hotels.latitude BETWEEN #{lat} - (#{distance} / 69.0)
    AND #{lat} + (#{distance} / 69.0)
    AND hotels.longitude BETWEEN #{lng} - (#{distance} / (69.0 * COS(RADIANS(#{lat}))))
    AND #{lng} + (#{distance} / (69.0 * COS(RADIANS(#{lat})))) 
    AND hotels.reception = 0
ORDER BY distance

但它似乎是一样的 - 仍有相同的 n + 1 问题。

在MySQL中是如何处理这个问题的?在单个查询中加载所有内容可能是最有效的,但是有办法吗?

谢谢你们。

修改

从ActiveRecord生成SQL(它生成2个查询):

  SELECT (ACOS(least(1,COS(0.5943236044502174)*COS(-2.0637416211957023)*COS(RADIANS(hotels.latitude))*COS(RADIANS(hotels.longitude))+ COS(0.5943236044502174)*SIN(-2.0637416211957023)*COS(RADIANS(hotels.latitude))*SIN(RADIANS(hotels.longitude))+
     SIN(0.5943236044502174)*SIN(RADIANS(hotels.latitude))))*3963.1899999999996)
  AS distance,hotels.* 
  FROM `hotels` 
  WHERE (((ACOS(least(1,COS(0.5943236044502174)*COS(-2.0637416211957023)*COS(RADIANS(hotels.latitude))*COS(RADIANS(hotels.longitude))+     COS(0.5943236044502174)*SIN(-2.0637416211957023)*COS(RADIANS(hotels.latitude))*SIN(RADIANS(hotels.longitude))+
    SIN(0.5943236044502174)*SIN(RADIANS(hotels.latitude))))*3963.1899999999996)
     <= 100.0)) 
  AND (reception = 0) ORDER BY distance ASC

SELECT `hotel_services`.* FROM `hotel_services` WHERE `hotel_services`.`hotel_id` IN (237440, 237441, another IDs)

EDIT2: 为什么你加入hotel_services和服务?你似乎没有使用它们。

这是因为模型:

class Hotel < ActiveRecord::Base 
  has_many :hotel_services, dependent: :destroy
  has_many :services, through: :hotel_services
end

0 个答案:

没有答案