ดัชนีเชิงพื้นที่สามารถช่วยแบบสอบถามแบบ "ช่วง - เรียงตาม - ขีด จำกัด "


29

ถามคำถามนี้โดยเฉพาะสำหรับ Postgres เนื่องจากมี supoort ที่ดีสำหรับดัชนี R-tree / spatial

เรามีตารางต่อไปนี้พร้อมโครงสร้างแบบต้นไม้ (ชุดแบบซ้อน) ของคำและความถี่:

lexikon
-------
_id   integer  PRIMARY KEY
word  text
frequency integer
lset  integer  UNIQUE KEY
rset  integer  UNIQUE KEY

และแบบสอบถาม:

SELECT word
FROM lexikon
WHERE lset BETWEEN @Low AND @High
ORDER BY frequency DESC
LIMIT @N

ฉันคิดว่าดัชนีการครอบคลุม(lset, frequency, word)จะมีประโยชน์ แต่ฉันรู้สึกว่ามันอาจทำงานได้ไม่ดีหากมีlsetค่ามากเกินไปใน(@High, @Low)ช่วง

ดัชนีแบบง่าย(frequency DESC)อาจมีเพียงพอในบางครั้งเมื่อการค้นหาที่ใช้ดัชนีนั้นให้ผลก่อน@Nแถวที่ตรงกับเงื่อนไขของช่วง

แต่ดูเหมือนว่าประสิทธิภาพขึ้นอยู่กับค่าพารามิเตอร์มาก

มีวิธีที่จะทำให้มันทำงานได้เร็วหรือไม่โดยไม่คำนึงว่าช่วง(@Low, @High)นั้นจะกว้างหรือแคบและไม่ว่าคำบนความถี่จะโชคดีในช่วงที่เลือก (แคบ) หรือไม่?

ดัชนี R-tree / spatial จะช่วยได้หรือไม่?

การเพิ่มดัชนีการเขียนแบบสอบถามการออกแบบตารางใหม่ไม่มีข้อ จำกัด


3
ดัชนีครอบคลุมจะแนะนำด้วย 9.2 (ตอนนี้เบต้า), btw คน PostgreSQL พูดสแกนดัชนีเท่านั้น ดูคำตอบที่เกี่ยวข้องนี้: dba.stackexchange.com/a/7541/3684และหน้า PostgreSQL Wiki
Erwin Brandstetter

คำถามสองข้อ: (1) คุณคาดหวังรูปแบบการใช้งานประเภทใด มีการอ่านเป็นส่วนใหญ่หรือมีการปรับปรุงบ่อยครั้ง (โดยเฉพาะอย่างยิ่งตัวแปรชุดซ้อน) (2) มีการเชื่อมต่อใด ๆ ระหว่างตัวแปรจำนวนเต็มชุด lset และ rset และคำตัวแปรข้อความหรือไม่?
jp

@jug: อ่านเป็นส่วนใหญ่ ไม่มีการเชื่อมต่อระหว่างและlset,rset word
ypercubeᵀᴹ

3
หากคุณมีการอัปเดตมากมายรูปแบบชุดแบบซ้อนจะเป็นตัวเลือกที่ไม่ดีเกี่ยวกับประสิทธิภาพ (หากคุณมีสิทธิ์เข้าถึงหนังสือ "The art of SQL" ให้ดูที่บทเกี่ยวกับตัวแบบลำดับขั้น) แต่อย่างไรก็ตามปัญหาหลักของคุณก็คล้ายคลึงกับการหาค่าสูงสุด / ค่าสูงสุด (ของตัวแปรอิสระ) ในช่วงเวลาซึ่งมันยากที่จะออกแบบวิธีการจัดทำดัชนี สำหรับความรู้ของฉันการจับคู่ที่ใกล้ที่สุดกับดัชนีที่คุณต้องการคือโมดูล knngist แต่คุณจะต้องปรับเปลี่ยนให้เหมาะสมกับความต้องการของคุณ ดัชนีเชิงพื้นที่ไม่น่าจะมีประโยชน์
jp

คำตอบ:


30

คุณอาจได้รับประสิทธิภาพที่ดีขึ้นโดยค้นหาครั้งแรกในแถวที่มีความถี่สูงกว่า สิ่งนี้สามารถทำได้โดยการ 'บด' ความถี่แล้วก้าวผ่านขั้นตอนเหล่านั้นตัวอย่างเช่น:

- ทดสอบและlexikonจำลองข้อมูล:

begin;
set role dba;
create role stack;
grant stack to dba;
create schema authorization stack;
set role stack;
--
create table lexikon( _id serial, 
                      word text, 
                      frequency integer, 
                      lset integer, 
                      width_granule integer);
--
insert into lexikon(word, frequency, lset) 
select word, (1000000/row_number() over(order by random()))::integer as frequency, lset
from (select 'word'||generate_series(1,1000000) word, generate_series(1,1000000) lset) z;
--
update lexikon set width_granule=ln(frequency)::integer;
--
create index on lexikon(width_granule, lset);
create index on lexikon(lset);
-- the second index is not used with the function but is added to make the timings 'fair'

granule การวิเคราะห์ (ส่วนใหญ่สำหรับข้อมูลและการปรับแต่ง):

create table granule as 
select width_granule, count(*) as freq, 
       min(frequency) as granule_start, max(frequency) as granule_end 
from lexikon group by width_granule;
--
select * from granule order by 1;
/*
 width_granule |  freq  | granule_start | granule_end
---------------+--------+---------------+-------------
             0 | 500000 |             1 |           1
             1 | 300000 |             2 |           4
             2 | 123077 |             5 |          12
             3 |  47512 |            13 |          33
             4 |  18422 |            34 |          90
             5 |   6908 |            91 |         244
             6 |   2580 |           245 |         665
             7 |    949 |           666 |        1808
             8 |    349 |          1811 |        4901
             9 |    129 |          4926 |       13333
            10 |     47 |         13513 |       35714
            11 |     17 |         37037 |       90909
            12 |      7 |        100000 |      250000
            13 |      2 |        333333 |      500000
            14 |      1 |       1000000 |     1000000
*/
alter table granule drop column freq;
--

ฟังก์ชั่นสำหรับการสแกนความถี่สูงก่อน:

create function f(p_lset_low in integer, p_lset_high in integer, p_limit in integer)
       returns setof lexikon language plpgsql set search_path to 'stack' as $$
declare
  m integer;
  n integer := 0;
  r record;
begin 
  for r in (select width_granule from granule order by width_granule desc) loop
    return query( select * 
                  from lexikon 
                  where width_granule=r.width_granule 
                        and lset>=p_lset_low and lset<=p_lset_high );
    get diagnostics m = row_count;
    n = n+m;
    exit when n>=p_limit;
  end loop;
end;$$;

ผลลัพธ์ (ควรกำหนดเวลาด้วยเกลือนิดหน่อย แต่การสืบค้นแต่ละครั้งจะถูกเรียกใช้สองครั้งเพื่อตอบโต้การแคช)

ใช้งานฟังก์ชั่นที่เราเขียนก่อน:

\timing on
--
select * from f(20000, 30000, 5) order by frequency desc limit 5;
/*
 _id |   word    | frequency | lset  | width_granule
-----+-----------+-----------+-------+---------------
 141 | word23237 |      7092 | 23237 |             9
 246 | word25112 |      4065 | 25112 |             8
 275 | word23825 |      3636 | 23825 |             8
 409 | word28660 |      2444 | 28660 |             8
 418 | word29923 |      2392 | 29923 |             8
Time: 80.452 ms
*/
select * from f(20000, 30000, 5) order by frequency desc limit 5;
/*
 _id |   word    | frequency | lset  | width_granule
-----+-----------+-----------+-------+---------------
 141 | word23237 |      7092 | 23237 |             9
 246 | word25112 |      4065 | 25112 |             8
 275 | word23825 |      3636 | 23825 |             8
 409 | word28660 |      2444 | 28660 |             8
 418 | word29923 |      2392 | 29923 |             8
Time: 0.510 ms
*/

แล้วด้วยการสแกนดัชนีอย่างง่าย:

select * from lexikon where lset between 20000 and 30000 order by frequency desc limit 5;
/*
 _id |   word    | frequency | lset  | width_granule
-----+-----------+-----------+-------+---------------
 141 | word23237 |      7092 | 23237 |             9
 246 | word25112 |      4065 | 25112 |             8
 275 | word23825 |      3636 | 23825 |             8
 409 | word28660 |      2444 | 28660 |             8
 418 | word29923 |      2392 | 29923 |             8
Time: 218.897 ms
*/
select * from lexikon where lset between 20000 and 30000 order by frequency desc limit 5;
/*
 _id |   word    | frequency | lset  | width_granule
-----+-----------+-----------+-------+---------------
 141 | word23237 |      7092 | 23237 |             9
 246 | word25112 |      4065 | 25112 |             8
 275 | word23825 |      3636 | 23825 |             8
 409 | word28660 |      2444 | 28660 |             8
 418 | word29923 |      2392 | 29923 |             8
Time: 51.250 ms
*/
\timing off
--
rollback;

คุณอาจต้องการเปลี่ยนแปลงจำนวนของแกรนูลและฟังก์ชันที่ใช้ในการวางแถวลงในข้อมูลจริงของคุณ การแจกแจงความถี่ที่แท้จริงเป็นกุญแจสำคัญที่นี่เช่นเดียวกับค่าที่คาดไว้สำหรับส่วนlimitคำสั่งและขนาดของlsetช่วงที่ต้องการ


ทำไมจึงมีช่องว่างเริ่มต้นจากwidth_granule=8ระหว่างgranulae_startและgranulae_endจากระดับก่อนหน้า?
vyegorov

@vyegorov เพราะไม่มีค่าใด ๆ 1809 และ 1810 นี่เป็นข้อมูลที่สร้างแบบสุ่มดังนั้น YMMV :)
Jack Douglas

อืมดูเหมือนว่ามันจะไม่มีส่วนเกี่ยวข้องกับการสุ่ม แต่ด้วยวิธีการที่frequencyถูกสร้างขึ้น: ช่องว่างขนาดใหญ่ระหว่าง 1e6 / 2 และ 1e6 / 3 จำนวนแถวที่สูงขึ้นจะกลายเป็นช่องว่างที่เล็กลง อย่างไรก็ตามขอบคุณสำหรับแนวทางที่ยอดเยี่ยมนี้ !!
vyegorov

@vyegorov ขอโทษใช่คุณพูดถูก อย่าลืมดูการปรับปรุงของ Erwinsหากคุณยังไม่ได้ทำ!
แจ็คดักลาส

23

ติดตั้ง

ฉันกำลังสร้างการตั้งค่าของ @ Jackเพื่อให้ผู้คนติดตามและเปรียบเทียบได้ง่ายขึ้น ทดสอบกับPostgreSQL 9.1.4

CREATE TABLE lexikon (
   lex_id    serial PRIMARY KEY
 , word      text
 , frequency int NOT NULL  -- we'd need to do more if NULL was allowed
 , lset      int
);

INSERT INTO lexikon(word, frequency, lset) 
SELECT 'w' || g  -- shorter with just 'w'
     , (1000000 / row_number() OVER (ORDER BY random()))::int
     , g
FROM   generate_series(1,1000000) g

จากที่นี่ไปฉันใช้เส้นทางอื่น:

ANALYZE lexikon;

ตารางเสริม

โซลูชันนี้ไม่ได้เพิ่มคอลัมน์ลงในตารางเดิม แต่เพียงต้องการตัวช่วยเล็ก ๆ ฉันวางมันลงในสคีมาpublicใช้สคีมาที่คุณเลือก

CREATE TABLE public.lex_freq AS
WITH x AS (
   SELECT DISTINCT ON (f.row_min)
          f.row_min, c.row_ct, c.frequency
   FROM  (
      SELECT frequency, sum(count(*)) OVER (ORDER BY frequency DESC) AS row_ct
      FROM   lexikon
      GROUP  BY 1
      ) c
   JOIN  (                                   -- list of steps in recursive search
      VALUES (400),(1600),(6400),(25000),(100000),(200000),(400000),(600000),(800000)
      ) f(row_min) ON c.row_ct >= f.row_min  -- match next greater number
   ORDER  BY f.row_min, c.row_ct, c.frequency DESC
   )
, y AS (   
   SELECT DISTINCT ON (frequency)
          row_min, row_ct, frequency AS freq_min
        , lag(frequency) OVER (ORDER BY row_min) AS freq_max
   FROM   x
   ORDER  BY frequency, row_min
   -- if one frequency spans multiple ranges, pick the lowest row_min
   )
SELECT row_min, row_ct, freq_min
     , CASE freq_min <= freq_max
         WHEN TRUE  THEN 'frequency >= ' || freq_min || ' AND frequency < ' || freq_max
         WHEN FALSE THEN 'frequency  = ' || freq_min
         ELSE            'frequency >= ' || freq_min
       END AS cond
FROM   y
ORDER  BY row_min;

ตารางมีลักษณะดังนี้:

row_min | row_ct  | freq_min | cond
--------+---------+----------+-------------
400     | 400     | 2500     | frequency >= 2500
1600    | 1600    | 625      | frequency >= 625 AND frequency < 2500
6400    | 6410    | 156      | frequency >= 156 AND frequency < 625
25000   | 25000   | 40       | frequency >= 40 AND frequency < 156
100000  | 100000  | 10       | frequency >= 10 AND frequency < 40
200000  | 200000  | 5        | frequency >= 5 AND frequency < 10
400000  | 500000  | 2        | frequency >= 2 AND frequency < 5
600000  | 1000000 | 1        | frequency  = 1

เนื่องจากคอลัมน์condจะถูกนำไปใช้ใน SQL แบบไดนามิกยิ่งขึ้นคุณต้องทำตารางนี้เชื่อถือได้ สคีมามีคุณสมบัติตามตารางเสมอหากคุณไม่แน่ใจเกี่ยวกับกระแสที่เหมาะสมsearch_pathและยกเลิกสิทธิ์การเขียนจากpublic(และบทบาทที่ไม่น่าเชื่อถืออื่น ๆ ):

REVOKE ALL ON public.lex_freq FROM public;
GRANT SELECT ON public.lex_freq TO public;

ตารางlex_freqทำหน้าที่สามประการ:

  • สร้างดัชนีบางส่วนที่ต้องการโดยอัตโนมัติ
  • จัดเตรียมขั้นตอนสำหรับฟังก์ชันวนซ้ำ
  • ข้อมูล Meta สำหรับการจูน

ดัชนี

DOคำสั่งนี้สร้างดัชนีที่จำเป็นทั้งหมด :

DO
$$
DECLARE
   _cond text;
BEGIN
   FOR _cond IN
      SELECT cond FROM public.lex_freq
   LOOP
      IF _cond LIKE 'frequency =%' THEN
         EXECUTE 'CREATE INDEX ON lexikon(lset) WHERE ' || _cond;
      ELSE
         EXECUTE 'CREATE INDEX ON lexikon(lset, frequency DESC) WHERE ' || _cond;
      END IF;
   END LOOP;
END
$$

ดัชนีบางส่วนทั้งหมดเหล่านี้รวมกันขยายตารางหนึ่งครั้ง มีขนาดใกล้เคียงกับดัชนีพื้นฐานหนึ่งรายการในตารางทั้งหมด:

SELECT pg_size_pretty(pg_relation_size('lexikon'));       -- 50 MB
SELECT pg_size_pretty(pg_total_relation_size('lexikon')); -- 71 MB

จนถึงขณะนี้มีเพียง 21 MB ของดัชนีสำหรับตาราง 50 MB

ฉันสร้างดัชนีบางส่วนส่วน(lset, frequency DESC)ใหญ่ คอลัมน์ที่สองช่วยในกรณีพิเศษเท่านั้น แต่เนื่องจากคอลัมน์ที่เกี่ยวข้องทั้งสองประเภทintegerเป็นเพราะการจัดตำแหน่งข้อมูลเฉพาะร่วมกับ MAXALIGNใน PostgreSQL คอลัมน์ที่สองจึงไม่ทำให้ดัชนีใหญ่ขึ้น มันเป็นชัยชนะเล็กน้อยสำหรับค่าใช้จ่ายใด ๆ

ไม่มีจุดในการทำเช่นนั้นสำหรับดัชนีบางส่วนที่ครอบคลุมเพียงความถี่เดียว (lset)เหล่านี้เป็นเพียงบน ดัชนีที่สร้างมีลักษณะเช่นนี้:

CREATE INDEX ON lexikon(lset, frequency DESC) WHERE frequency >= 2500;
CREATE INDEX ON lexikon(lset, frequency DESC) WHERE frequency >= 625 AND frequency < 2500;
-- ...
CREATE INDEX ON lexikon(lset, frequency DESC) WHERE frequency >= 2 AND frequency < 5;
CREATE INDEX ON lexikon(lset) WHERE freqency = 1;

ฟังก์ชัน

ฟังก์ชั่นนี้ค่อนข้างมีสไตล์คล้ายกับโซลูชันของ @ Jack:

CREATE OR REPLACE FUNCTION f_search(_lset_min int, _lset_max int, _limit int)
  RETURNS SETOF lexikon
$func$
DECLARE
   _n      int;
   _rest   int := _limit;   -- init with _limit param
   _cond   text;
BEGIN 
   FOR _cond IN
      SELECT l.cond FROM public.lex_freq l ORDER BY l.row_min
   LOOP    
      --  RAISE NOTICE '_cond: %, _limit: %', _cond, _rest; -- for debugging
      RETURN QUERY EXECUTE '
         SELECT * 
         FROM   public.lexikon 
         WHERE  ' || _cond || '
         AND    lset >= $1
         AND    lset <= $2
         ORDER  BY frequency DESC
         LIMIT  $3'
      USING  _lset_min, _lset_max, _rest;

      GET DIAGNOSTICS _n = ROW_COUNT;
      _rest := _rest - _n;
      EXIT WHEN _rest < 1;
   END LOOP;
END
$func$ LANGUAGE plpgsql STABLE;

ความแตกต่างที่สำคัญ:

  • SQL แบบไดนามิกRETURN QUERY EXECUTEด้วย
    เมื่อเราวนไปตามขั้นตอนต่าง ๆ แผนแบบสอบถามอาจเป็นประโยชน์ แผนแบบสอบถามสำหรับ SQL แบบคงที่จะถูกสร้างขึ้นหนึ่งครั้งจากนั้นนำมาใช้ซ้ำซึ่งสามารถประหยัดค่าใช้จ่ายได้ แต่ในกรณีนี้การสืบค้นนั้นง่ายและค่าต่างกันมาก Dynamic SQL จะเป็นผู้ชนะครั้งใหญ่

  • แบบไดนามิกLIMITสำหรับทุกขั้นตอนการสืบค้น
    วิธีนี้ช่วยได้หลายวิธี: อย่างแรกแถวจะดึงข้อมูลได้ตามต้องการเท่านั้น เมื่อใช้ร่วมกับไดนามิก SQL สิ่งนี้อาจสร้างแผนการคิวรีที่แตกต่างกันเพื่อเริ่มต้น ประการที่สอง: ไม่จำเป็นต้องมีเพิ่มเติมLIMITในการเรียกใช้ฟังก์ชันเพื่อตัดแต่งส่วนเกิน

เกณฑ์มาตรฐาน

ติดตั้ง

ฉันเลือกสี่ตัวอย่างและวิ่งทดสอบที่แตกต่างกันสามแบบ ฉันเปรียบเทียบห้าอย่างดีที่สุดกับแคชที่อบอุ่น:

  1. แบบสอบถาม SQL ดิบของแบบฟอร์ม:

    SELECT * 
    FROM   lexikon 
    WHERE  lset >= 20000
    AND    lset <= 30000
    ORDER  BY frequency DESC
    LIMIT  5;
  2. เหมือนกันหลังจากสร้างดัชนีนี้

    CREATE INDEX ON lexikon(lset);

    ต้องการพื้นที่ว่างเหมือนกับดัชนีบางส่วนของฉันทั้งหมด:

    SELECT pg_size_pretty(pg_total_relation_size('lexikon')) -- 93 MB
  3. ฟังก์ชั่น

    SELECT * FROM f_search(20000, 30000, 5);

ผล

SELECT * FROM f_search(20000, 30000, 5);

1: รันไทม์ทั้งหมด: 315.458 ms
2: รวมรันไทม์: 36.458 ms
3: รวมรันไทม์: 0.330 ms

SELECT * FROM f_search(60000, 65000, 100);

1: รันไทม์ทั้งหมด: 294.819 ms
2: รวมรันไทม์: 18.915 ms
3: รวมทั้งหมด: 1.414 ms

SELECT * FROM f_search(10000, 70000, 100);

1: รันไทม์ทั้งหมด: 426.831 ms
2: รวมรันไทม์: 217.874 ms
3: รวมรันไทม์: 1.611 ms

SELECT * FROM f_search(1, 1000000, 5);

1: รันไทม์ทั้งหมด: 2458.205 ms
2: รันไทม์ทั้งหมด: 2458.205 ms - สำหรับช่วง lset ขนาดใหญ่การสแกน seq นั้นเร็วกว่าดัชนี
3: รันไทม์ทั้งหมด: 0.266 ms

ข้อสรุป

เป็นที่คาดหวังได้รับประโยชน์จากฟังก์ชั่นที่เติบโตขึ้นกับช่วงใหญ่และขนาดเล็ก lsetLIMIT

กับช่วงที่มีขนาดเล็กมากของlsetแบบสอบถามดิบร่วมกับดัชนีที่เป็นจริงได้เร็วขึ้น คุณจะต้องการทดสอบและอาจสาขา: แบบสอบถามดิบสำหรับช่วงเล็ก ๆ ของlsetการเรียกใช้ฟังก์ชั่นอื่น คุณสามารถสร้างสิ่งนั้นลงในฟังก์ชั่นเพื่อ "ดีที่สุดของทั้งสองโลก" - นั่นคือสิ่งที่ฉันจะทำ

ขั้นตอนเพิ่มเติมlex_freqอาจช่วยประสิทธิภาพได้ทั้งนี้ขึ้นอยู่กับการกระจายข้อมูลและแบบสอบถามทั่วไป ทดสอบเพื่อหาจุดหวาน ด้วยเครื่องมือที่นำเสนอที่นี่มันควรจะง่ายต่อการทดสอบ


1

ฉันไม่เห็นเหตุผลใด ๆ ที่จะรวมคอลัมน์คำในดัชนี ดังนั้นดัชนีนี้

CREATE INDEX lexikon_lset_frequency ON lexicon (lset, frequency DESC)

จะทำให้ข้อความค้นหาของคุณทำงานได้อย่างรวดเร็ว

UPD

ขณะนี้ไม่มีวิธีสร้างดัชนีครอบคลุมใน PostgreSQL มีการอภิปรายเกี่ยวกับคุณสมบัตินี้ในรายชื่อผู้รับจดหมายของ PostgreSQL http://archives.postgresql.org/pgsql-performance/2012-06/msg00114.php


1
มันถูกรวมไว้เพื่อให้ดัชนี "ครอบคลุม"
ypercubeᵀᴹ

แต่ถ้าไม่ค้นหาคำนั้นในโครงสร้างการตัดสินใจคิวรีคุณแน่ใจหรือไม่ว่าดัชนีการครอบคลุมกำลังช่วยที่นี่
jcolebrand

ตกลงฉันเห็นแล้ว ขณะนี้ไม่มีวิธีสร้างดัชนีครอบคลุมใน PostgreSQL มีการอภิปรายเกี่ยวกับคุณลักษณะนี้ในรายการทางไปรษณีย์ได้archives.postgresql.org/pgsql-performance/2012-06/msg00114.php
grayhemp

เกี่ยวกับ "การครอบคลุมดัชนี" ใน PostgreSQL ดูความคิดเห็นของ Erwin Brandstetter ต่อคำถาม
jp

1

ใช้ดัชนี GIST

มีวิธีที่จะทำให้มันทำงานได้เร็วหรือไม่โดยไม่คำนึงว่าช่วง (@Low, @High) กว้างหรือแคบและไม่ว่าคำความถี่สูงสุดจะโชคดีในช่วงที่เลือก (แคบ) หรือไม่?

มันขึ้นอยู่กับสิ่งที่คุณหมายถึงเมื่อคุณอย่างรวดเร็ว: ORDER freq DESCคุณเห็นได้ชัดต้องไปทุกแถวในช่วงเพราะการค้นหาของคุณเป็น เนื่องจากผู้วางแผนคิวรีนั้นครอบคลุมเรื่องนี้อยู่แล้วหากฉันเข้าใจคำถาม

ที่นี่เราสร้างตารางที่มี 10k แถว (5::int,random()::double precision)

CREATE EXTENSION IF NOT EXISTS btree_gin;
CREATE TABLE t AS
  SELECT 5::int AS foo, random() AS bar
  FROM generate_series(1,1e4) AS gs(x);

เราจัดทำดัชนีมัน

CREATE INDEX ON t USING gist (foo, bar);
ANALYZE t;

เราสอบถามมัน

EXPLAIN ANALYZE
SELECT *
FROM t
WHERE foo BETWEEN 1 AND 6
ORDER BY bar DESC
FETCH FIRST ROW ONLY;

Seq Scan on tที่เราได้รับ นี่เป็นเพียงเพราะการประมาณค่าการเลือกของเราทำให้ pg สรุปการเข้าถึง heap นั้นเร็วกว่าการสแกนดัชนีและตรวจสอบซ้ำ ดังนั้นเราจึงทำให้มันฉ่ำมากขึ้นโดยการแทรกแถวที่มากกว่า 1,000,000 แถว(42::int,random()::double precision)ไม่ตรงกับ "ช่วง" ของเรา

INSERT INTO t(foo,bar)
SELECT 42::int, x
FROM generate_series(1,1e6) AS gs(x);

VACUUM ANALYZE t;

แล้วเราก็ถามใหม่

EXPLAIN ANALYZE
SELECT *
FROM t
WHERE foo BETWEEN 1 AND 6
ORDER BY bar DESC
FETCH FIRST ROW ONLY;

คุณสามารถดูที่นี่เราเสร็จสิ้นใน 4.6 MS กับดัชนีเฉพาะสแกน ,

                                                                 QUERY PLAN                                                                  
---------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=617.64..617.64 rows=1 width=12) (actual time=4.652..4.652 rows=1 loops=1)
   ->  Sort  (cost=617.64..642.97 rows=10134 width=12) (actual time=4.651..4.651 rows=1 loops=1)
         Sort Key: bar DESC
         Sort Method: top-N heapsort  Memory: 25kB
         ->  Index Only Scan using t_foo_bar_idx on t  (cost=0.29..566.97 rows=10134 width=12) (actual time=0.123..3.623 rows=10000 loops=1)
               Index Cond: ((foo >= 1) AND (foo <= 6))
               Heap Fetches: 0
 Planning time: 0.144 ms
 Execution time: 4.678 ms
(9 rows)

การขยายช่วงเพื่อรวมตารางทั้งหมดสร้างการสแกนอีกครั้งหนึ่งทางตรรกะและการเติบโตด้วยอีกหนึ่งพันล้านแถวจะเป็นการสร้างดัชนีการสแกนอีกครั้ง

ดังนั้นโดยสรุป

  • มันจะทำงานได้อย่างรวดเร็วสำหรับปริมาณข้อมูล
  • Fast นั้นสัมพันธ์กับทางเลือกอื่นหากช่วงนั้นไม่ได้เลือกเพียงพอการสแกนตามลำดับอาจเร็วเท่าที่คุณจะหาได้
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.