क्यों?
कारण क्या यह है:
तेज़ क्वेरी:
-> Hash Left Join (cost=1378.60..2467.48 rows=15 width=79) (actual time=41.759..85.037 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* (...)
धीमी क्वेरी:
-> Hash Left Join (cost=1378.60..2467.48 rows=1 width=79) (actual time=35.084..80.209 rows=1129 loops=1) ... Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* unacc (...)
किसी अन्य वर्ण द्वारा खोज पैटर्न को विस्तारित करने से पोस्टग्रेज़ को अभी तक कम हिट मानने का कारण बनता है। (आमतौर पर, यह एक उचित अनुमान है।) पोस्टग्रेज़ में स्पष्ट रूप से सटीक पर्याप्त आँकड़े नहीं होते हैं (कोई नहीं, वास्तव में, पढ़ते रहें) उतनी ही हिट की अपेक्षा करें जो आपको वास्तव में मिलती हैं।
यह एक अलग क्वेरी योजना पर स्विच करने का कारण बनता है, जो वास्तविक . के लिए और भी कम इष्टतम है हिट की संख्या rows=1129
।
समाधान
वर्तमान पोस्टग्रेज को 9.5 मानते हुए क्योंकि इसे घोषित नहीं किया गया है।
स्थिति को सुधारने का एक तरीका अभिव्यक्ति अनुक्रमणिका . बनाना है विधेय में अभिव्यक्ति पर। यह पोस्टग्रेज़ को वास्तविक अभिव्यक्ति के लिए आंकड़े इकट्ठा करने के लिए बनाता है, जो क्वेरी में मदद कर सकता है भले ही इंडेक्स का उपयोग क्वेरी के लिए नहीं किया गया हो . अनुक्रमणिका के बिना, कोई आंकड़े नहीं . हैं बिल्कुल अभिव्यक्ति के लिए। और अगर सही किया जाए तो इंडेक्स का उपयोग क्वेरी के लिए किया जा सकता है, यह और भी बेहतर है। लेकिन कई समस्याएं हैं आपकी वर्तमान अभिव्यक्ति के साथ:
<स्ट्राइक> unaccent(TEXT(coalesce(p.abrev,'')||' ('||coalesce(p.prenome,'')||')')) ilike unaccent('%vicen%')
स्ट्राइक>
कुछ अनुमानों . के आधार पर इस अपडेट की गई क्वेरी पर विचार करें आपकी अज्ञात तालिका परिभाषाओं के बारे में:
SELECT e.id
, (SELECT count(*) FROM imgitem
WHERE tabid = e.id AND tab = 'esp') AS imgs -- count(*) is faster
, e.ano, e.mes, e.dia
, e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
, pl.pltag, e.inpa, e.det, d.ano anodet
, format('%s (%s)', p.abrev, p.prenome) AS determinador
, d.tax
, coalesce(v.val,v.valf) || ' ' || vu.unit AS altura
, coalesce(v1.val,v1.valf) || ' ' || vu1.unit AS dap
, d.fam, tf.nome família, d.gen, tg.nome AS gênero, d.sp
, ts.nome AS espécie, d.inf, e.loc, l.nome localidade, e.lat, e.lon
FROM pess p -- reorder!
JOIN det d ON d.detby = p.id -- INNER JOIN !
LEFT JOIN tax tf ON tf.oldfam = d.fam
LEFT JOIN tax tg ON tg.oldgen = d.gen
LEFT JOIN tax ts ON ts.oldsp = d.sp
LEFT JOIN tax ti ON ti.oldinf = d.inf -- unused, see @joop's comment
LEFT JOIN esp e ON e.det = d.id
LEFT JOIN loc l ON l.id = e.loc
LEFT JOIN var v ON v.esp = e.id AND v.key = 265
LEFT JOIN varunit vu ON vu.id = v.unit
LEFT JOIN var v1 ON v1.esp = e.id AND v1.key = 264
LEFT JOIN varunit vu1 ON vu1.id = v1.unit
LEFT JOIN pl ON pl.id = e.pl
WHERE f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%') OR
f_unaccent(p.prenome) ILIKE f_unaccent('%' || 'vicenti' || '%');
प्रमुख बिंदु
क्यों f_unaccent()
? क्योंकि unaccent()
अनुक्रमित नहीं किया जा सकता। इसे पढ़ें:
मैंने निम्नलिखित (अनुशंसित!) बहु-स्तंभ कार्यात्मक ट्रिग्राम GIN अनुक्रमणिका की अनुमति देने के लिए वहां उल्लिखित फ़ंक्शन का उपयोग किया है :
CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);
यदि आप ट्रिग्राम इंडेक्स से परिचित नहीं हैं, तो इसे पहले पढ़ें:
और संभवत::
Postgres का नवीनतम संस्करण (वर्तमान में 9.5) चलाना सुनिश्चित करें। GIN इंडेक्स में काफी सुधार हुआ है। और आपकी रुचि pg_trgm 1.2 में सुधारों में होगी, जो आगामी Postgres 9.6 के साथ रिलीज़ होने वाली है:
तैयार बयान मापदंडों के साथ प्रश्नों को निष्पादित करने का एक सामान्य तरीका है (विशेषकर उपयोगकर्ता इनपुट से पाठ के साथ)। पोस्टग्रेज को एक ऐसी योजना ढूंढनी होगी जो किसी दिए गए पैरामीटर के लिए सबसे अच्छा काम करे। वाइल्डकार्ड को स्थिरांक के रूप में जोड़ें इस तरह खोज शब्द के लिए:
f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')
('vicenti'
एक पैरामीटर के साथ बदल दिया जाएगा।) तो पोस्टग्रेज जानता है कि हम एक ऐसे पैटर्न से निपट रहे हैं जो न तो बाएं और न ही दाएं लंगर है - जो विभिन्न रणनीतियों की अनुमति देगा। अधिक विवरण के साथ संबंधित उत्तर:
या हो सकता है कि प्रत्येक खोज शब्द (संभवतः किसी फ़ंक्शन में गतिशील SQL का उपयोग करके) के लिए क्वेरी की पुन:योजना बनाएं। लेकिन सुनिश्चित करें कि नियोजन समय किसी भी संभावित प्रदर्शन लाभ को नहीं खा रहा है।
WHERE
pess
. में कॉलम पर कंडीशन <स्ट्राइक> LEFT JOIN
के विपरीत है स्ट्राइक> . Postgres को इसे INNER JOIN
. में बदलने के लिए मजबूर किया जाता है . क्या बुरा है ज्वाइन ट्री में देर से आता है। और चूंकि Postgres आपके जॉइन को फिर से व्यवस्थित नहीं कर सकता (नीचे देखें), यह बहुत महंगा हो सकता है। तालिका को पहले . पर ले जाएं FROM
. में स्थिति पंक्तियों को जल्दी खत्म करने के लिए खंड। निम्नलिखित LEFT JOIN
s परिभाषा के अनुसार किसी भी पंक्ति को समाप्त नहीं करते हैं। लेकिन कई तालिकाओं के साथ उन जोड़ों को स्थानांतरित करना महत्वपूर्ण है जो गुणा . कर सकते हैं अंत तक पंक्तियाँ।
आप 13 टेबल में शामिल हो रहे हैं, उनमें से 12 LEFT JOIN
. के साथ हैं जो 12!
. छोड़ता है संभव संयोजन - या 11! * 2!
अगर हम एक LEFT JOIN
लेते हैं ध्यान में रखते हुए कि वास्तव में एक INNER JOIN
. है . वह भी है पोस्टग्रेज़ के लिए कई सर्वोत्तम क्वेरी योजना के लिए सभी संभावित क्रमपरिवर्तन का मूल्यांकन करने के लिए। join_collapse_limit
के बारे में पढ़ें :
- PostgreSQL में कार्डिनैलिटी अनुमान त्रुटि दिखाने के लिए नमूना क्वेरी
- SQL INNER WHERE सिंटैक्स के बराबर कई तालिकाओं में शामिल हों
join_collapse_limit
. के लिए डिफ़ॉल्ट सेटिंग है 8 , जिसका अर्थ है कि Postgres आपके FROM
. में तालिकाओं को पुन:व्यवस्थित करने का प्रयास नहीं करेगा खंड और तालिकाओं का क्रम प्रासंगिक . है ।
इसका एक तरीका यह होगा कि प्रदर्शन-महत्वपूर्ण भाग को सीटीई
जैसे @joop ने टिप्पणी की
. join_collapse_limit
सेट न करें कई सम्मिलित तालिकाओं को शामिल करने वाली क्वेरी योजना के लिए बहुत अधिक या समय खराब हो जाएगा।
आपकी सम्मिलित तिथि . के बारे में नाम data
:
<स्ट्राइक> cast(cast(e.ano as varchar(4))||'-'||right('0'||cast(e.mes as varchar(2)),2)||'-'|| right('0'||cast(e.dia as varchar(2)),2) as varchar(10)) as data
स्ट्राइक>
मानना आप साल, महीने और दिन के लिए तीन संख्यात्मक स्तंभों से निर्माण करते हैं, जिन्हें परिभाषित किया गया है NOT NULL
, इसके बजाय इसका उपयोग करें:
e.ano::text || to_char(e.mes2, 'FM"-"00')
|| to_char(e.dia, 'FM"-"00') AS data
FM
के बारे में टेम्पलेट पैटर्न संशोधक:
लेकिन वास्तव में, आपको दिनांक को डेटा प्रकार date
शुरू करने के लिए।
सरल भी किया गया:
format('%s (%s)', p.abrev, p.prenome) AS determinador
क्वेरी को तेज़ नहीं करेगा, लेकिन यह बहुत साफ है। देखें format()
कोड>
।
सबसे पहले अंतिम, प्रदर्शन अनुकूलन के लिए सभी सामान्य सलाह लागू होता है:
यदि आप यह सब ठीक कर लेते हैं, तो आपको सभी . के लिए बहुत तेज़ क्वेरी दिखाई देंगी पैटर्न।