在db中找到25个最接近db的地方是SLOW

时间:2013-09-19 17:33:41

标签: android database performance search greendao

我有大约12 000个条目的数据库。每个条目都给出了纬度,经度和空距离。我需要做的是从当前GPS位置找到25个最近的条目。我的ORM是greenDao

有2个问题: 我不知道我和条目之间的距离,我无法将所有条目加载到RAM,因为当我这样做时,堆上升到70MB并且应用程序在OutOfMemoryException崩溃(所以我需要使用延迟加载)。

我试过这种方法:

  1. 获取给定表的迭代器
  2. 加载条目,计算它与当前位置的距离,将条目保存到ArrayList缓冲区(我将每1000个条目刷新缓冲区回到db(它只是updateInTx(...))然后清理它)
  3. 重复第2点,直到iterator.hasNext();
  4. 从具有limit(25).orderAsc()
  5. 的条目中查询
  6. 结果
  7. 这是有效的,但从第1-3点开始它非常慢(在Nexus 7上大约需要25秒)。休息大约需要1.5秒。

    每次用户启动应用或请求数据刷新时,我都必须这样做。 任何想法如何更好地解决它?

    由于

    编辑: 这是计算距离的函数,因此在SQL中很难做到这一点:(

    double getDistance(GPSCoords myPos, Place place) {
        double dlong = (place.getLongitude() - myPos.getLongitude()) * d2r;
        double dlat = (place.getLatitude() - myPos.getLatitude()) * d2r;
        double a = Math.pow(Math.sin(dlat / 2.0), 2) + Math.cos(myPos.getLatitude() * d2r)
                * Math.cos(place.getLatitude() * d2r) * Math.pow(Math.sin(dlong / 2.0), 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        double d = 6367 * c;
    
        return d;
    }
    

4 个答案:

答案 0 :(得分:2)

您应该能够让SQL在数据库中完成工作:

select ((x - ?)*(x - ?) + (y - ?)*(y - ?)) as distsq from entries 
order by dist limit 20

不幸的是,sqlite不提供取幂,因此需要重复的术语。

如果这还不够快,另一种方法是使边界框查询以您的位置为中心,通过二分查找调整边界框的大小,直到您有30个或更多条目。每个x和y维度的索引都会加速这些索引。

编辑由于OP说地球曲率很重要,因此边界框技术可能是我们使用未扩展sqlite获得的最佳方法。这是一个提出的算法:

Let P be the current position
Let Slat = lat0 be the bounding box latitude half-size initialized with a "best guess"
Let Slon = lon0 be the bounding box longitude half-size initialized with a "best guess"
// NB the best guesses should cover an approximately square area on the ground
loop
  Let W = P.lon - Slon, E = P.lon + Slon, N = P.lat + Slat, S = P.lat - Slat
  C = select count(*) from entries
      where W <= lon and lon <= E and S <= lat and lat <= N
  if C indicates the result is too big (e.g. for memory or read time), 
    Slat = 0.5 * Slat
    Slon = 0.5 * Slon
  else
    Let R be the result of the same query for * instead of count(*)
    Let D be the geometric distance from P to the nearest point on bounding box
    Compute r.dist for all r in R (in memory)
    Sort R by dist (in memory)
    Throw away the tail elements of R where r.dist > D 
       // Can't use these because points outside bounding box might be closer!
    If at least 20 remaining R elements, 
      return top 20
    else
      Slat = 2 * Slat
      Slon = 2 * Slon
    end if
  end if
end loop    

注意你需要lat和lon的索引。在这种情况下,我不知道SQLite查询优化器有多好。一个好的优化器将根据过去查询中累积的统计信息选择lat或lon索引,使用它来快速查找该维度的边界框范围内的所有点,然后扫描此结果以获得最终结果。如果优化器不那么聪明,您只想索引可能产生最小初始结果的维度:在平均情况下,这是具有最大几何范围(覆盖距离)的维度。

The r* tree index将使边界框查询更快,但至少通过Jelly Bean,您必须提供自己的SQLite实例,并包含此扩展。也许以后的Android版本包含它?我不知道。

此外,如果您在应用程序中包含自定义SQLite,则将距离(带曲率)功能添加为扩展名非常容易。

答案 1 :(得分:0)

有许多使用不同风格的SQL进行距离计算的例子。从数据库加载每一行并计算它的距离,然后排序和获取最接近的行将从前后到数据库变慢。在SQL中进行计算并且仅检索所需的计算将会更加高效。

答案 2 :(得分:0)

您可以尝试将距离计算移动到sql db。 你也可以放一些更聪明的代码,这些代码会运行距离计算,直到他找到25个位置,它们与当前位置的距离小于x(你选择)。或者甚至少于25个项目(也许你只需要7个填充屏幕)并且当用户已经在应用程序中时继续在后台进行计算。 这将是一个更好的用户体验。

答案 3 :(得分:0)

我不明白为什么你觉得你需要懒得加载你的参赛作品。只有12k条目,70MB的堆数听起来很可疑。您是否只是为了计算距离而抓住整行?尝试抓住您需要的列:

  • 纬度
  • 经度
  • 主键

假设每个字节为8个字节,即24 * 12000个字节,或大约280个千字节。给它一些只是Java的开销空间,但你仍然在寻找非常可管理的东西。

然后你可以在代码中进行计算,然后让它为每个最近的点吐出主键。第二个查询可以抓住那些25(这次是整行),你就完成了!