সেটআপ
লোকেদের অনুসরণ এবং তুলনা করা আরও সহজ করার জন্য আমি @ জ্যাকের সেটআপটি তৈরি করছি। পোস্টগ্রিসএসকিউএল 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 এমবি সূচক index
আমি বেশিরভাগ আংশিক সূচকগুলি তৈরি করি (lset, frequency DESC)
। দ্বিতীয় কলামটি শুধুমাত্র বিশেষ ক্ষেত্রে সহায়তা করে। তবে জড়িত উভয় কলামই প্রকারভেদযুক্ত integer
, পোস্টগ্রিজ এসকিউএল-এ ম্যাক্সালাইগনের সাথে সংমিশ্রণে ডেটা প্রান্তিককরণের সুনির্দিষ্ট কারণে , দ্বিতীয় কলাম সূচকটিকে আরও বড় করে না। এটি কোনও ব্যয়ের জন্য খুব ছোট জয়।
আংশিক সূচকগুলির জন্য এটি করার কোনও অর্থ নেই যা কেবলমাত্র একটি একক ফ্রিকোয়েন্সি বিস্তৃত। এগুলি এখন চলছে (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
।
আমরা পদক্ষেপগুলি লুপ করার সাথে সাথে একটি পৃথক ক্যোয়ারী পরিকল্পনা সুবিধাভোগী হতে পারে। স্ট্যাটিক এসকিউএল এর জন্য ক্যোয়ারী পরিকল্পনাটি একবার উত্পন্ন হয় এবং তারপরে পুনরায় ব্যবহার করা হয় - যা কিছু ওভারহেড সংরক্ষণ করতে পারে। তবে এক্ষেত্রে ক্যোয়ারীটি সহজ এবং মানগুলি খুব আলাদা। ডায়নামিক এসকিউএল একটি বড় জয় হবে।
LIMIT
প্রতিটি প্রশ্নের পদক্ষেপের জন্য গতিশীল ।
এটি একাধিক উপায়ে সহায়তা করে: প্রথমত, সারিগুলি কেবলমাত্র প্রয়োজনীয় হিসাবে আনা হয়। গতিশীল এসকিউএল এর সাথে একত্রে এটি শুরু করতে বিভিন্ন ক্যোয়ারী পরিকল্পনা তৈরি করতে পারে। দ্বিতীয়: LIMIT
উদ্বৃত্ত ছাঁটাই করতে ফাংশন কলটিতে অতিরিক্ত প্রয়োজন নেই ।
উচ্চতার চিহ্ন
সেটআপ
আমি চারটি উদাহরণ বেছে নিয়েছি এবং প্রত্যেকটির সাথে তিনটি পৃথক পরীক্ষা চালিয়েছি। উষ্ণ ক্যাশের সাথে তুলনা করতে আমি পাঁচটি সেরা নিয়েছি:
ফর্মের কাঁচা এসকিউএল কোয়েরি:
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
পারফরম্যান্সে সহায়তা করতে পারে। মিষ্টি স্পট খুঁজে পেতে পরীক্ষা। এখানে উপস্থাপিত সরঞ্জামগুলির সাথে এটি পরীক্ষা করা সহজ হওয়া উচিত।