सेट अप
मैं @ जैक के सेटअप का निर्माण कर रहा हूं ताकि लोगों के लिए अनुसरण करना और तुलना करना आसान हो सके। 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
गतिशील एसक्यूएल में आगे उपयोग किया जा रहा है, आपको इस तालिका को सुरक्षित बनाना होगा । यदि आप एक उपयुक्त वर्तमान के search_path
बारे में सुनिश्चित नहीं हो सकते हैं, तो हमेशा स्कीमा को योग्य public
बनाएं और (और किसी भी अविश्वसनीय भूमिका) से विशेषाधिकारों को रद्द करें :
REVOKE ALL ON public.lex_freq FROM public;
GRANT SELECT ON public.lex_freq TO public;
तालिका lex_freq
में तीन उद्देश्य हैं:
- स्वचालित रूप से आवश्यक आंशिक अनुक्रम बनाएँ ।
- पुनरावृत्त समारोह के लिए कदम प्रदान करें।
- ट्यूनिंग के लिए मेटा जानकारी।
इंडेक्स
यह 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
50 एमबी टेबल के लिए अब तक केवल 21 एमबी इंडेक्स।
मैं अधिकांश आंशिक अनुक्रम बनाता हूं (lset, frequency DESC)
। दूसरा कॉलम केवल विशेष मामलों में मदद करता है। लेकिन चूंकि दोनों शामिल कॉलम टाइप के हैं integer
, इसलिए PostgreSQL में MAXALIGN के साथ संयोजन में डेटा संरेखण की बारीकियों के कारण , दूसरा कॉलम इंडेक्स को कोई बड़ा नहीं बनाता है। यह शायद ही किसी भी कीमत के लिए एक छोटी जीत है।
ऐसा करने का कोई मतलब नहीं है कि आंशिक अनुक्रमित के लिए जो केवल एक आवृत्ति को फैलाते हैं। वे बस पर हैं (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;
समारोह
समारोह कुछ हद तक @ जैक के समाधान की शैली के समान है:
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;
मुख्य अंतर:
के साथ गतिशील एसक्यूएलRETURN QUERY EXECUTE
।
जैसा कि हम चरणों के माध्यम से लूप करते हैं, एक अलग क्वेरी प्लान लाभार्थी हो सकता है। स्टेटिक एसक्यूएल के लिए क्वेरी प्लान एक बार जनरेट किया जाता है और फिर से उपयोग किया जाता है - जो कुछ ओवरहेड को बचा सकता है। लेकिन इस मामले में क्वेरी सरल है और मान बहुत अलग हैं। डायनेमिक SQL एक बड़ी जीत होगी।
LIMIT
हर क्वेरी चरण के लिए गतिशील ।
यह कई तरीकों से मदद करता है: सबसे पहले, पंक्तियों को केवल आवश्यकतानुसार लाया जाता है। डायनेमिक एसक्यूएल के साथ संयोजन में यह भी शुरू करने के लिए विभिन्न क्वेरी प्लान उत्पन्न कर सकता है। दूसरा: 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 एमएस
2: कुल रनटाइम: 36.458 एमएस
3: कुल रनटाइम: 0.330 एमएस
SELECT * FROM f_search(60000, 65000, 100);
1: कुल रनटाइम: 294.819 एमएस
2: कुल रनटाइम: 18.915 एमएस
3: कुल रनटाइम: 1.414 एमएस
SELECT * FROM f_search(10000, 70000, 100);
1: कुल रनटाइम: 426.831 एमएस
2: कुल रनटाइम: 217.874 एमएस
3: कुल रनटाइम: 1.611 एमएस
SELECT * FROM f_search(1, 1000000, 5);
1: कुल रनटाइम: 2458.205 एमएस
2: कुल रनटाइम: 2458.205 एमएस - बड़ी रेंज के लिसेट के लिए, सीक स्कैन इंडेक्स की तुलना में तेज है।
3: कुल रनटाइम: 0.266 एमएस
निष्कर्ष
जैसा कि अपेक्षित था, फ़ंक्शन का लाभ बड़े lset
और छोटे रेंज के साथ बढ़ता है LIMIT
।
साथ की बहुत छोटी पर्वतमालाlset
, सूचकांक के साथ संयोजन में कच्चे क्वेरी वास्तव में है तेजी से । आप परीक्षण करना चाहते हैं और शायद शाखा: छोटी श्रृंखला के लिए कच्ची क्वेरी lset
, अन्यथा फ़ंक्शन कॉल कर सकते हैं। तुम भी एक "दोनों दुनिया के सर्वश्रेष्ठ" के लिए समारोह में निर्माण कर सकते हैं - कि मैं क्या करूँगा।
आपके डेटा वितरण और विशिष्ट प्रश्नों के आधार पर, lex_freq
प्रदर्शन में अधिक कदम मदद कर सकते हैं। मीठी जगह खोजने के लिए परीक्षण करें। यहां प्रस्तुत टूल के साथ, परीक्षण करना आसान होना चाहिए।