我如何优化这些范围

时间:2019-05-15 17:49:53

标签: ruby-on-rails postgresql optimization activerecord

我们在一个模型中有多个作用域。 随着数据库的增长,作用域变得无法使用。

使用范围与这种类型的查询是问题开始的地方: where(“ devices.id IN(?)”,ETC

这是我们的一些范围:

scope :reserved, -> { joins("INNER JOIN reservations ON (devices.ipaddress = reservations.ip)") }
scope :in_dhcp_range, -> {
    joins(
      "INNER JOIN dhcp_ranges ON (devices.ipaddress >= dhcp_ranges.start_ip AND devices.ipaddress <= dhcp_ranges.end_ip)"
    ).distinct
  }
scope :alternate_aws, -> {
    joins("INNER JOIN awssubnets ON (devices.ipaddress <<= awssubnets.cidr)").distinct
  }

scope :dhcp_full_calculation, -> {
    where("devices.id IN (?)",
      (Device.in_dhcp_range.select("devices.id") + Device.alternate_aws.select("devices.id")).uniq - Device.reserved)
  }

当数据库只有50,000条记录时,这些作用域起作用。但是,超过25万条记录,我们在以下范围内遇到性能问题:dhcp_full_calculation

使用Postgres数据库的Rails 5.2.3,ruby 2.6.3

1 个答案:

答案 0 :(得分:0)

因此,总体性能问题与数据库外部发生的计算操作有关,例如

(Device.in_dhcp_range.select("devices.id") + 
 Device.alternate_aws.select("devices.id")).uniq 
   - Device.reserved)

这将执行3个查询,然后在Array上使用Array(或类似ActiveRecord::Relation)方法以返回要选择的Device的列表。这将导致大量计算以及查询中非常大的IN子句(在某些时候,该子句将超过分配给SQL语句的字符数)

相反,您应该使用子查询将所有这些工作放在数据库本身上,从而像这样构建where子句

scope :dhcp_full_calculation, -> {
  where.not(id: Device.reserved.select(:id) ).where(
    id: Device.select(:id).where(id: 
         Device.in_dhcp_range.select(:id) 
       ).or( 
         Device.select(:id).where( 
           id: Device.alternate_aws.select(:id) 
         ) 
       ) 
  )
}

这将生成类似于以下内容的查询:

 SELECT 
   devices.*
 FROM 
   devices
 WHERE 
   devices.id NOT IN (
     SELECT devices.id FROM [YOUR RESERVED QUERY])
   AND  devices.id IN ( 
     SELECT devices.id 
     FROM devices
     WHERE 
       devices.id IN ( SELECT devices.id FROM [YOUR IN DHCP RANGE QUERY])
       OR devices.id IN ( SELECT devices.id FROM [YOUR ALTERNATE AWS QUERY])
     )