sql - 添加索引以加速 Geocoder 附近搜索

标签 sql ruby-on-rails ruby postgresql

在我的 Rails 应用程序中,我具有允许查找最接近当前登录用户的用户的功能。我为此使用 Geocoder gem。在用户模型中,我有这样的范围:

   scope :close_to, -> (user:, distance:) {
    where.not(id: user.id)
    .near([user.latitude, user.longitude], distance)
  }

这非常有效,但对于大量用户来说速度很慢。当我调用此范围时,它会生成以下 sql 查询:

SELECT users.*, 6371.0 * 2 * ASIN(SQRT(POWER(SIN((48.471645 - users.latitude) * PI() / 180 / 2), 2) + COS(48.471645 * PI() / 180) * COS(users.latitude * PI() / 180) * POWER(SIN((-83.102801 - users.longitude) * PI() / 180 / 2), 2))) AS distance, MOD(CAST((ATAN2( ((users.longitude - -83.102801) / 57.2957795), ((users.latitude - 48.471645) / 57.2957795)) * 57.2957795) + 360 AS decimal), 360) AS bearing FROM "users" WHERE ("users"."id" != 43362) AND (users.latitude BETWEEN 39.4784289408127 AND 57.46486105918731 AND users.longitude BETWEEN -96.6674214298497 AND -69.5381805701503 AND (6371.0 * 2 * ASIN(SQRT(POWER(SIN((48.471645 - users.latitude) * PI() / 180 / 2), 2) + COS(48.471645 * PI() / 180) * COS(users.latitude * PI() / 180) * POWER(SIN((-83.102801 - users.longitude) * PI() / 180 / 2), 2)))) BETWEEN 0.0 AND 1000) ORDER BY distance ASC;

我正在尝试为此创建索引,但它们不起作用。我正在尝试以下组合:

1.
    add_index :users, [:id, :latitude]
    add_index :users, [:id, :longitude]

2.  add_index :users, [:id, :latitude, :longitude]

3.  add_index :users, [:latitude]
    add_index :users, [:longitude]

4. add_index :users, [:id, :latitude]

我应该如何添加索引来加速这个查询?

编辑:我忘了补充一点,我的纬度和经度列是小数。

此查询的 ANALYZE 返回类似的内容:

 Sort  (cost=7141.66..7142.14 rows=191 width=327) (actual time=575.995..585.543 rows=36598 loops=1)
   Sort Key: ((12742::double precision * asin(sqrt((power(sin((((((48.471645 - latitude))::double precision * 3.14159265358979::double precision) / 180::double precision) / 2::double precision)), 2::double precision) + ((0.662990616338754::double precision * cos((((latitude)::double precision * 3.14159265358979::double precision) / 180::double precision))) * power(sin(((((((-83.102801) - longitude))::double precision * 3.14159265358979::double precision) / 180::double precision) / 2::double precision)), 2::double precision)))))))
   Sort Method: external merge  Disk: 4672kB
   ->  Seq Scan on users  (cost=0.00..7134.43 rows=191 width=327) (actual time=0.381..517.615 rows=36598 loops=1)
         Filter: ((id <> 43362) AND (latitude >= 39.4784289408127) AND (latitude <= 57.46486105918731) AND (longitude >= (-96.6674214298497)) AND (longitude <= (-69.5381805701503)) AND ((12742::double precision * asin(sqrt((power(sin((((((48.471645 - latitude))::double precision * 3.14159265358979::double precision) / 180::double precision) / 2::double precision)), 2::double precision) + ((0.662990616338754::double precision * cos((((latitude)::double precision * 3.14159265358979::double precision) / 180::double precision))) * power(sin(((((((-83.102801) - longitude))::double precision * 3.14159265358979::double precision) / 180::double precision) / 2::double precision)), 2::double precision)))))) >= 0::double precision) AND ((12742::double precision * asin(sqrt((power(sin((((((48.471645 - latitude))::double precision * 3.14159265358979::double precision) / 180::double precision) / 2::double precision)), 2::double precision) + ((0.662990616338754::double precision * cos((((latitude)::double precision * 3.14159265358979::double precision) / 180::double precision))) * power(sin(((((((-83.102801) - longitude))::double precision * 3.14159265358979::double precision) / 180::double precision) / 2::double precision)), 2::double precision)))))) <= 1000::double precision))
         Rows Removed by Filter: 6756
 Planning time: 1.041 ms
 Execution time: 587.695 ms
(8 rows)

编辑 2:

我注意到 postgresql 使用我的

add_index :users, [:latitude, :longitude]

仅当我输入小距离时。近10公里内的用户。

最佳答案

减速很可能是由数学运算引起的,而不是由获取表数据引起的。您的部分标准不是针对记录字段,而是针对其他记录的数学运算结果,因此它变成了 O(N2)。

Postgres之所以不使用索引而是选择Seq scan,是因为它决定了大部分的表记录在查询的时候必须要抓取。当要获取表中的大部分记录时,索引可能不会带来太大好处。

要加快速度,您应该考虑使用空间索引和基于附近的搜索 PostGis或者,使用 Geo Distance Query 的 Elasticsearch .

关于sql - 添加索引以加速 Geocoder 附近搜索,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38058200/

相关文章:

ruby-on-rails - 在本地 Mac 上运行多个 Rails 应用程序

ruby - 选择和收集之间的区别

ruby - 重启 Sidekiq

mysql - 测试我使用的是 MySQL 语法吗?

ruby-on-rails - SaaS 应用程序是如何组织的?

mysql - 为什么在 SQL 匹配中使用 'LIKE' 不算数?

ruby-on-rails - 仅包含存在的 Assets

ruby - 循环不会在检查条件时终止

mysql - 两个选择 SQL 语句的结果划分

sql - SQL Server 2014 中类似于 DATEDIFF_BIG 的自定义代码