Postgresql sqldb中优化动态排序查询性能的设计模式

Postgresql sqldb中优化动态排序查询性能的设计模式,postgresql,performance,database-design,similarity,Postgresql,Performance,Database Design,Similarity,我有一个应用程序,它有一个相当大的活动数据集(比如cars),大约有200万行活动数据。每个“汽车”都有许多属性(列),如价格、里程、年份、品牌、型号、燃油类型等 现在,在我的web应用程序中的每辆车的/show页面上,我需要生成前10辆最“相似”的车的列表。由于我从不“知道”一辆车是一种非常普通还是非常罕见的车(在实际执行db查询之前),我设计了一种模式,在“相似的车”查询中我几乎不做任何过滤(where-子句)。相反,我根据视图中当前汽车的数据,结合CASE WHEN-语句,执行了大量ORD

我有一个应用程序,它有一个相当大的活动数据集(比如cars),大约有200万行活动数据。每个“汽车”都有许多属性(列),如价格、里程、年份、品牌、型号、燃油类型等

现在,在我的web应用程序中的每辆车的/show页面上,我需要生成前10辆最“相似”的车的列表。由于我从不“知道”一辆车是一种非常普通还是非常罕见的车(在实际执行db查询之前),我设计了一种模式,在“相似的车”查询中我几乎不做任何过滤(
where
-子句)。相反,我根据视图中当前汽车的数据,结合
CASE WHEN
-语句,执行了大量
ORDER BY
-子句。假设一位用户看到一辆福特福克斯,2010年,30.000公里,汽油,12490欧元,来自杜塞尔多夫附近的汽车。然后我会这样做:

SELECT "cars".*
  FROM de."cars" 
  WHERE ("cars"."id" != 24352543) 
    AND "cars"."sales_state" = 'onsale' 
    AND (cars.is_disabled IS NOT TRUE) 
    ORDER BY
      CASE WHEN ABS(cars.price - 12490) < cars.price * 0.2 THEN 1 WHEN ABS(cars.price - 12490) < cars.price * 0.4 THEN 2 WHEN ABS(cars.price - 12490) < cars.price * 0.6 THEN 3 ELSE 4 END, 
      CASE WHEN fuel_type = 'Gasoline' THEN 0 ELSE 1 END, 
      ABS(cars.price - 12490), 
      CASE WHEN ST_Distance( ST_GeographyFromText( 'SRID=4326;POINT(' || cars.longitude || ' ' || cars.latitude || ')' ), ST_GeographyFromText('SRID=4326;POINT(12.172130 48.162990)') ) <= 30000 THEN 1 WHEN ST_Distance( ST_GeographyFromText( 'SRID=4326;POINT(' || cars.longitude || ' ' || cars.latitude || ')' ), ST_GeographyFromText('SRID=4326;POINT(12.172130 48.162990)') ) <= 100000 THEN 2 ELSE 3 END, 
      ABS(cars.year - 2010), 
      ABS(cars.km - 30000)
    LIMIT 10
选择“汽车”*
来自“汽车”
其中(“汽车”。“id”!=24352543)
和“汽车”,“销售州”=“销售”
和(cars.is_disabled不正确)
订购人
当ABS(cars.price-12490)当ST|u Distance(ST|u GeographyFromText('SRID=4326;POINT('cars.longitude | |')'、'ST|u GeographyFromText')、ST|u GeographyFromText('SRID=4326;POINT(12.17213048.162990))时,您无法准确地获得那么快的结果,因为您必须对所有查询结果执行top-N排序,即使您启动
work||mem
,也会很慢

ORDER BY
子句不能按原样进行索引

如果您的查询更灵活一些,也许您可以尝试以下方式:

CREATE INDEX ON de.cars (price)
  WHERE sales_state = 'onsale' AND is_disabled IS NOT TRUE;
第一个问题:

WITH priced_cars AS (
  SELECT SELECT cars.*
    FROM de.cars
    WHERE (cars.id != 24352543)
      AND cars.sales_state = 'onsale'
      AND (cars.is_disabled IS NOT TRUE)
      AND cars.price BETWEEN 12490*5/6 AND 12490*5/4
)
SELECT * FROM priced_cars
ORDER BY
  CASE WHEN fuel_type = 'Gasoline' THEN 0 ELSE 1 END, 
  ABS(price - 12490), 
  CASE
    WHEN ST_Distance( ST_GeographyFromText( 'SRID=4326;POINT(' || longitude || ' ' || latitude || ')' ), ST_GeographyFromText('SRID=4326;POINT(12.172130 48.162990)') ) <= 30000
    THEN 1
    WHEN ST_Distance( ST_GeographyFromText( 'SRID=4326;POINT(' || longitude || ' ' || latitude || ')' ), ST_GeographyFromText('SRID=4326;POINT(12.172130 48.162990)') ) <= 100000
    THEN 2
    ELSE 3
  END, 
  ABS(year - 2010), 
  ABS(km - 30000)
LIMIT 10;
这将只对应于第一列为1的汽车,但它可以很快,因为它可以使用索引

如果你找到10辆那样的车,你就完了

否则,使用
WHERE
条件对
price
运行第二个查询,该条件将对应于
price
上的下一个最佳标准,这同样可以使用相同的索引,但速度会慢一些

继续这样做,直到你有10辆车为止(最后一次查询没有关于
price
的条件,并且会像以前一样慢)


如果您必须运行四个这样的查询,这将是一个损失,因为您在前三个查询中找不到10辆车,但在另一种情况下可能会更快。

对于可能的sql复杂性和转移(许多不同的模式)以及您提到的计时(250毫秒)我应该强制sql遵循一个尽可能简单有效的“计划”,一次分解一个过滤器

我在一个循环中处理(每次)随机的过滤器集,根据我判断更重要的过滤器,选择PKs,然后在其他循环中加入PKs

这样,您就有机会在所有随机过滤器集中获得最佳时间,而且您可能很快就知道0结果

更多详细信息示例: 首先,你要把注意力集中在你搜索的项目上,我相信是car.id。因此,您需要一组随机过滤器上的Car.id值。假设您有20个可能的过滤器。每个过滤器都会产生一组car.id值。有些过滤器可能直接在car.id所在的表上工作。其他一些可能需要连接到1-2或3个表。但是,所有过滤器一起可能总共需要10-15个连接。最少的表加入更好的机会得到一个好的计划

假设您有3个筛选器,筛选器2、筛选器7和筛选器14。例如,将12个表和过滤器与这3个过滤器连接可能有效,也可能无效。如果是的话,另一种组合就不会了。所以我的建议是(伪代码):

(可选)您可以指定过滤器的处理顺序。
如果您知道在一组5-6个过滤器中,99%的搜索使用了其中至少一个过滤器,那么首先对它们进行排序将导致在前5个selects max处将car.id值缩小到0-NORY的范围,这将是一个有趣的问题,您可以知道查询是否总是这样,或者查询的某些部分是否可能会发生更改。关键是这些子句是相同的,但其内容取决于所使用的单个页面/汽车。例如,价格,如果是汽油或柴油,lat/long等。因此,查询的结构始终相同,但年份、里程和燃油类型等变量每次都不同。明白了吗?谢谢你的回复,这几乎是我能想到的唯一方法。我会等一等,看看她身上是否出现了其他有趣的东西:-)嗨,听起来很有趣。你能再详细一点吗。我不确定我是否完全理解你建议的模式?我通过扩展上面的答案做到了这一点,希望ti能有所帮助
procedure/table function get carids as
for each optional filter 1 to 20
 if filter is set
  select car.id from car (possible joins) where filter=filter.value and car.id 
  in (previous car.id found)
  if count(car.id)=0 end and return no results
 end if
end for
return car.id collected