为什么向索引中添加排序列并不能使其更高效?

Why is adding sorted columns to an index not making it more efficient?

我有这样的查询:

SELECT  "cars"."id" FROM de."cars" 
WHERE "cars"."sales_state" = 'onsale'
  AND (cars.is_disabled IS NOT TRUE) 
  AND (cars.price >= 35920) 
  AND (cars.price <= 659880) 
  AND ("cars"."featuring_score" IS NOT NULL) 
ORDER BY
   CASE WHEN cars.featuring_score < 'C' THEN 1
        WHEN cars.featuring_score = 'C' THEN 2
        WHEN cars.featuring_score > 'C' THEN 3
        ELSE 4
   END, 
   CASE WHEN cars.au_rating >= 3 THEN 1
        WHEN cars.au_rating = 0  THEN 2
        WHEN cars.au_rating = 2  THEN 3
        WHEN cars.au_rating = 1  THEN 4
        ELSE 6
   END, 
   CASE cars.brand
        WHEN 'Audi' THEN 1
        WHEN 'Alpina' THEN 2
        WHEN 'Artega' THEN 3
        WHEN 'BMW' THEN 4
        WHEN 'Maybach' THEN 5
        ELSE 6
   END ASC, 
   CASE WHEN ABS(cars.price - 347900) < cars.price * 0.2 THEN 1
        WHEN ABS(cars.price - 347900) < cars.price * 0.4 THEN 2
        WHEN ABS(cars.price - 347900) < cars.price * 0.6 THEN 3
        ELSE 4
   END, 
   CASE WHEN images_count = 0 OR images_count IS NULL THEN 1
        ELSE 0
   END,
   ABS(cars.price - 347900) 
LIMIT 61;

我正在尝试创建一个索引来加快此查询的执行速度。我感到困惑的是为什么以下两个索引同样快,并且具有相同的执行查询计划:

CREATE INDEX CONCURRENTLY IF NOT EXISTS index_cars_v2
        ON de.cars USING btree
        (
          (price)
        )
        WHERE sales_state::text = 'onsale'::text 
           AND is_disabled IS NOT TRUE 
           AND featuring_score IS NOT NULL

CREATE INDEX CONCURRENTLY IF NOT EXISTS index_cars_v2
        ON de.cars USING btree
        (
          (CASE WHEN cars.featuring_score < 'C' THEN 1 WHEN cars.featuring_score = 'C' THEN 2 WHEN cars.featuring_score > 'C' THEN 3 ELSE 4 END),
          (CASE WHEN cars.au_rating >= 3 THEN 1 WHEN cars.au_rating = 0 THEN 2 WHEN cars.au_rating = 2 THEN 3 WHEN cars.au_rating = 1 THEN 4 ELSE 6 END),
          (price)
        )
        WHERE sales_state::text = 'onsale'::text 
           AND is_disabled IS NOT TRUE 
           AND featuring_score IS NOT NULL

两者都生成类似这样的查询计划:

"Limit  (cost=326.63..326.78 rows=61 width=28) (actual time=218.501..218.508 rows=61 loops=1)"
"  Output: id, (CASE WHEN ((featuring_score)::text < 'C'::text) THEN 1 WHEN ((featuring_score)::text = 'C'::text) THEN 2 WHEN ((featuring_score)::text > 'C'::text) THEN 3 ELSE 4 END), (CASE WHEN (au_rating >= 3) THEN 1 WHEN (au_rating = 0) THEN 2 WHEN (au_rating = 2) THEN 3 WHEN (au_rating = 1) THEN 4 ELSE 6 END), (CASE brand WHEN 'Audi'::text THEN 1 WHEN 'Alpina'::text THEN 2 WHEN 'Artega'::text THEN 3 WHEN 'BMW'::text THEN 4 WHEN 'Maybach'::text THEN 5 ELSE 6 END), (CASE WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.2)) THEN 1 WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.4)) THEN 2 WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.6)) THEN 3 ELSE 4 END), (CASE WHEN ((images_count = 0) OR (images_count IS NULL)) THEN 1 ELSE 0 END), (abs((price - 347900)))"
"  Buffers: shared hit=60813"
"  ->  Sort  (cost=326.63..327.35 rows=286 width=28) (actual time=218.499..218.501 rows=61 loops=1)"
"        Output: id, (CASE WHEN ((featuring_score)::text < 'C'::text) THEN 1 WHEN ((featuring_score)::text = 'C'::text) THEN 2 WHEN ((featuring_score)::text > 'C'::text) THEN 3 ELSE 4 END), (CASE WHEN (au_rating >= 3) THEN 1 WHEN (au_rating = 0) THEN 2 WHEN (au_rating = 2) THEN 3 WHEN (au_rating = 1) THEN 4 ELSE 6 END), (CASE brand WHEN 'Audi'::text THEN 1 WHEN 'Alpina'::text THEN 2 WHEN 'Artega'::text THEN 3 WHEN 'BMW'::text THEN 4 WHEN 'Maybach'::text THEN 5 ELSE 6 END), (CASE WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.2)) THEN 1 WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.4)) THEN 2 WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.6)) THEN 3 ELSE 4 END), (CASE WHEN ((images_count = 0) OR (images_count IS NULL)) THEN 1 ELSE 0 END), (abs((price - 347900)))"
"        Sort Key: (CASE WHEN ((cars.featuring_score)::text < 'C'::text) THEN 1 WHEN ((cars.featuring_score)::text = 'C'::text) THEN 2 WHEN ((cars.featuring_score)::text > 'C'::text) THEN 3 ELSE 4 END), (CASE WHEN (cars.au_rating >= 3) THEN 1 WHEN (cars.au_rating = 0) THEN 2 WHEN (cars.au_rating = 2) THEN 3 WHEN (cars.au_rating = 1) THEN 4 ELSE 6 END), (CASE cars.brand WHEN 'Audi'::text THEN 1 WHEN 'Alpina'::text THEN 2 WHEN 'Artega'::text THEN 3 WHEN 'BMW'::text THEN 4 WHEN 'Maybach'::text THEN 5 ELSE 6 END), (CASE WHEN ((abs((cars.price - 347900)))::numeric < ((cars.price)::numeric * 0.2)) THEN 1 WHEN ((abs((cars.price - 347900)))::numeric < ((cars.price)::numeric * 0.4)) THEN 2 WHEN ((abs((cars.price - 347900)))::numeric < ((cars.price)::numeric * 0.6)) THEN 3 ELSE 4 END), (CASE WHEN ((cars.images_count = 0) OR (cars.images_count IS NULL)) THEN 1 ELSE 0 END), (abs((cars.price - 347900)))"
"        Sort Method: top-N heapsort  Memory: 33kB"
"        Buffers: shared hit=60813"
"        ->  Index Scan using index_cars_v2 on de.cars  (cost=0.42..316.72 rows=286 width=28) (actual time=0.075..205.774 rows=60655 loops=1)"
"              Output: id, CASE WHEN ((featuring_score)::text < 'C'::text) THEN 1 WHEN ((featuring_score)::text = 'C'::text) THEN 2 WHEN ((featuring_score)::text > 'C'::text) THEN 3 ELSE 4 END, CASE WHEN (au_rating >= 3) THEN 1 WHEN (au_rating = 0) THEN 2 WHEN (au_rating = 2) THEN 3 WHEN (au_rating = 1) THEN 4 ELSE 6 END, CASE brand WHEN 'Audi'::text THEN 1 WHEN 'Alpina'::text THEN 2 WHEN 'Artega'::text THEN 3 WHEN 'BMW'::text THEN 4 WHEN 'Maybach'::text THEN 5 ELSE 6 END, CASE WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.2)) THEN 1 WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.4)) THEN 2 WHEN ((abs((price - 347900)))::numeric < ((price)::numeric * 0.6)) THEN 3 ELSE 4 END, CASE WHEN ((images_count = 0) OR (images_count IS NULL)) THEN 1 ELSE 0 END, abs((price - 347900))"
"              Index Cond: ((cars.price >= 35920) AND (cars.price <= 659880))"
"              Buffers: shared hit=60813"
"Planning Time: 1.118 ms"
"Execution Time: 218.589 ms"

我希望将 featuring_scoreau_rating 上的函数式 CASE WHEN 语句添加到索引中以产生更好的性能,因为索引会根据第一个排序标准,但是它完全没有区别。我的假设是错误的还是我在索引定义中做错了什么。

PS。我也尝试过在索引中使用 price 最后,这在查询计划中没有任何区别 - 实际上我也感到困惑...

PPS。我是 运行 PostgreSQL 11

索引必须包含 all ORDER BY 条目,以便可以用来避免排序:

CREATE INDEX CONCURRENTLY IF NOT EXISTS index_cars_v2
ON de.cars (
   (CASE WHEN cars.featuring_score < 'C' THEN 1
         WHEN cars.featuring_score = 'C' THEN 2
         WHEN cars.featuring_score > 'C' THEN 3
         ELSE 4
    END),
   (CASE WHEN cars.au_rating >= 3 THEN 1
         WHEN cars.au_rating = 0  THEN 2
         WHEN cars.au_rating = 2  THEN 3
         WHEN cars.au_rating = 1  THEN 4
         ELSE 6
    END),
   (CASE cars.brand
         WHEN 'Audi' THEN 1
         WHEN 'Alpina' THEN 2
         WHEN 'Artega' THEN 3
         WHEN 'BMW' THEN 4
         WHEN 'Maybach' THEN 5
         ELSE 6
    END),
   (CASE WHEN ABS(cars.price - 347900) < cars.price * 0.2 THEN 1
         WHEN ABS(cars.price - 347900) < cars.price * 0.4 THEN 2
         WHEN ABS(cars.price - 347900) < cars.price * 0.6 THEN 3
         ELSE 4
    END),
   (CASE WHEN images_count = 0 OR images_count IS NULL THEN 1
         ELSE 0
    END),
   (ABS(cars.price - 347900))
WHERE sales_state = 'onsale' 
   AND is_disabled IS NOT TRUE 
   AND featuring_score IS NOT NULL;