ติดตั้ง
ฉันกำลังสร้างการตั้งค่าของ @ 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
ในการเรียกใช้ฟังก์ชันเพื่อตัดแต่งส่วนเกิน
เกณฑ์มาตรฐาน
ติดตั้ง
ฉันเลือกสี่ตัวอย่างและวิ่งทดสอบที่แตกต่างกันสามแบบ ฉันเปรียบเทียบห้าอย่างดีที่สุดกับแคชที่อบอุ่น:
แบบสอบถาม SQL ดิบของแบบฟอร์ม:
SELECT *
FROM lexikon
WHERE lset >= 20000
AND lset <= 30000
ORDER BY frequency DESC
LIMIT 5;
เหมือนกันหลังจากสร้างดัชนีนี้
CREATE INDEX ON lexikon(lset);
ต้องการพื้นที่ว่างเหมือนกับดัชนีบางส่วนของฉันทั้งหมด:
SELECT pg_size_pretty(pg_total_relation_size('lexikon')) -- 93 MB
ฟังก์ชั่น
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
ข้อสรุป
เป็นที่คาดหวังได้รับประโยชน์จากฟังก์ชั่นที่เติบโตขึ้นกับช่วงใหญ่และขนาดเล็ก lset
LIMIT
กับช่วงที่มีขนาดเล็กมากของlset
แบบสอบถามดิบร่วมกับดัชนีที่เป็นจริงได้เร็วขึ้น คุณจะต้องการทดสอบและอาจสาขา: แบบสอบถามดิบสำหรับช่วงเล็ก ๆ ของlset
การเรียกใช้ฟังก์ชั่นอื่น คุณสามารถสร้างสิ่งนั้นลงในฟังก์ชั่นเพื่อ "ดีที่สุดของทั้งสองโลก" - นั่นคือสิ่งที่ฉันจะทำ
ขั้นตอนเพิ่มเติมlex_freq
อาจช่วยประสิทธิภาพได้ทั้งนี้ขึ้นอยู่กับการกระจายข้อมูลและแบบสอบถามทั่วไป ทดสอบเพื่อหาจุดหวาน ด้วยเครื่องมือที่นำเสนอที่นี่มันควรจะง่ายต่อการทดสอบ