query-ის წინ ჩასვი EXPLAIN (ANALYZE, BUFFERS), გაუშვი და შედეგი ქვემოდან ზემოთ წაიკითხე. სამ რამეს ეძებ: node-ს, სადაც დროის უმეტესი ნაწილი ნამდვილად იხარჯება, node-ს, რომლის სავარაუდო რიგების რაოდენობა რეალურისგან სიდიდის რიგით განსხვავდება, და დიდ ცხრილზე მიმდევრობით scan-ს, რომელიც მხოლოდ რამდენიმე რიგს აბრუნებს. პატარა ან საშუალო მონაცემთა ბაზაზე თითქმის ყველა ნელი query ამ სამიდან ერთ-ერთია, ხოლო გამოსწორება ინდექსია, ახალი სტატისტიკა ან გადაწერილი WHERE პირობა. პოსტის დანარჩენი ნაწილი იმაზეა, როგორ გაარკვიო, რომელს უყურებ.
plan საიდუმლო დოკუმენტი არ არის. ეს node-ების ხეა, სადაც თითოეული თავის შვილებისგან იღებს რიგებს და მშობელს გადასცემს, ხოლო planner-ის ვარაუდი და executor-ის რეალობა გვერდიგვერდაა დაბეჭდილი. როცა იცი, რას ნიშნავს ექვსი-შვიდი ტიპის node და რომელი რიცხვები უნდა შეადარო, plan-ის წაკითხვას დაახლოებით ოცდაათი წამი სჭირდება.
EXPLAIN, EXPLAIN ANALYZE და პარამეტრები, რომლებიც ღირს#
მარტო EXPLAIN აჩვენებს plan-ს, რომელიც planner-მა აირჩია, query-ის გაშვების გარეშე. ის მყისიერი და უსაფრთხოა და მხოლოდ შეფასებებს გაძლევს. EXPLAIN ANALYZE query-ს მართლა ასრულებს და იმას, რაც რეალურად მოხდა, იმის გვერდით ბეჭდავს, რაც იყო ნავარაუდევი. ეს განსხვავება მთელი აზრია, ამიტომ გამოიყენე ANALYZE, თუ საპირისპიროს მიზეზი არ გაქვს.
ორი გაფრთხილება, სანამ გააკეთებ. EXPLAIN ANALYZE UPDATE-ზე, DELETE-ზე ან INSERT-ზე ჩანაწერს ასრულებს. თუ არ გინდა, შეფუთე:
BEGIN;EXPLAIN (ANALYZE, BUFFERS)DELETE FROM sessions WHERE expires_at < now() - interval '30 days';ROLLBACK;და ANALYZE დროის გაზომვის ზედმეტ დანახარჯს ამატებს. უმეტეს მანქანაზე ეს რამდენიმე პროცენტია; ნელი clock source-ის მქონე სისტემაზე მოკლე query-ის გაზომილი დრო შეიძლება გაორმაგდეს. თუ ციფრები შეუძლებლად გამოიყურება, გაუშვი ხელახლა TIMING OFF-ით, რომელიც რიგების რაოდენობას ინახავს და თითოეული node-ის დროს აგდებს.
პარამეტრები, რომლებიც თავიანთ ადგილს იმსახურებს:
BUFFERS- რამდენი 8 kB ბლოკი წაიკითხა თითოეულმა node-მა shared buffers-იდან (shared hit), დისკიდან ან ოპერაციული სისტემის ქეშიდან (shared read), და რამდენი ჩაწერა (shared written). ეს ყველაზე ახლოა ფიზიკური I/O-ს მთვლელთან, რომელსაც მიიღებ, და გაზიარებულ მანქანაზე კედლის საათის დროზე გაცილებით სტაბილურია. PostgreSQL 18-იდან ისANALYZE-თან ნაგულისხმევად შედის; ადრეულ ვერსიებზე მას ითხოვ.VERBOSE- ბეჭდავს გამომავალი სვეტების სიას და სქემით კვალიფიცირებულ სახელებს. გამოსადეგია plan-ებზე, სადაც რამდენიმე join და განმეორებადი სვეტების სახელებია.SETTINGS- ბეჭდავს ნებისმიერ planner პარამეტრს, რომელიც ნაგულისხმევზე არ არის. ერთხელ დამატებად ღირს, როცა plan ერთ სერვერზე იმავე plan-ისგან სხვაზე განსხვავდება.WAL- write-ahead log-ის ჩანაწერები და ბაიტები, რომლებიც გენერირდა. აზრი აქვს მხოლოდ ჩამწერ ბრძანებასთან.FORMAT JSON- მანქანით წაკითხვადი გამოტანა, რასაც ვებზე plan-ის ვიზუალიზატორები მოიხმარენ.GENERIC_PLAN(PostgreSQL 16 და უფრო ახალი) - გაძლევს საშუალებას,EXPLAINგაუკეთო ბრძანებას, რომელიც$1placeholder-ებს შეიცავს მნიშვნელობების მიწოდების გარეშე, ასე ამოწმებ, რას აგზავნის სინამდვილეში prepared statement ან ORM.
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, SETTINGS)SELECT ...;გაუშვი query ორჯერ და მეორე plan წაიკითხე. პირველი გაშვება ბლოკების დისკიდან ქეშში წაკითხვას იხდის, და ცივ ქეშზე მორგება იშვიათად გინდა, თუ თავად სიცივე არ არის ნამდვილი საჩივარი.
როგორ წავიკითხოთ ხე#
ყოველი ხაზი, რომელიც ->-ით იწყება, node-ია. შეწევა ჩადგმას ნიშნავს: node-ის შვილები მის ქვეშ არის შეწეული და შესრულება ყველაზე ღრმა node-დან გარეთ მიდის. წაიკითხე ყველაზე ღრმა შეწევიდან ზევით და რიგებს მიჰყვები.
node-ის ხაზი ასე გამოიყურება:
-> Index Scan using orders_pkey on orders (cost=0.43..8.45 rows=1 width=36) (actual time=0.021..0.023 rows=1 loops=1)ექვსი რიცხვი, და თითოეული რაღაც კონკრეტულს ნიშნავს:
cost=0.43..8.45- სავარაუდო საწყისი და საერთო ღირებულება თვითნებურ ერთეულებში, სადაც 1.0 დაახლოებით ერთი მიმდევრობითი გვერდის წაკითხვის ღირებულებაა. პირველი რიცხვი ისაა, რაც საჭიროა პირველი რიგის დასაბრუნებლად, სწორედ ამიტომ აქვს sort-ს დიდი საწყისი ღირებულება და index scan-ს თითქმის არავითარი. რიცხვებს მნიშვნელობა მხოლოდ ერთმანეთთან შედარებით აქვს.rows=1- ამ node-ის მიერ გამოსაშვები რიგების სავარაუდო რაოდენობა, თითო შესრულებაზე.width=36- რიგის სავარაუდო საშუალო სიგანე ბაიტებში. გამოსადეგია, როცა plan გაცილებით მეტ მონაცემს გადააქვს, ვიდრე ელოდი.actual time=0.021..0.023- რეალური მილიწამები პირველ და ბოლო რიგამდე, loop-ებზე გასაშუალოებული.loops=1- რამდენჯერ გაეშვა ეს node. ეს რიცხვია, რომელსაც ხალხი ივიწყებს: nested loop-ის შიდა მხარესactual timeდაrowsთითო loop-ზეა. node, რომელიც აჩვენებსactual time=0.4..0.5 rows=3 loops=9000-ს, დაახლოებით 4.5 წამი დაჯდა და 27 000 რიგი გამოიდო, და არა ნახევარი მილიწამი და სამი რიგი.
დროები შვილების ჩათვლით არის, ამიტომ node-ის საკუთარი ღირებულება მისი actual time მინუს ყველაფრის დროა, რაც მის ქვეშაა. ბოლო ორი ხაზი, Planning Time და Execution Time, ცალკეა: planning time, რომელიც საერთოს დიდი წილია, ჩვეულებრივ ნიშნავს ცხრილს ასობით partition-ით ან უზარმაზარ რაოდენობა join-ებს.
node-ის ქვეშ დამატებით ხაზებში ცხოვრობს დეტალი. Filter Rows Removed by Filter-ით, Index Cond, Heap Fetches, Sort Method, Buckets და Buffers ყველა რაღაცას გეუბნება, რასაც სათაურის რიცხვები არა.
node-ები, რომლებსაც რეალურად ნახავ#
| Node | რას აკეთებს | როდის არის პრობლემა |
|---|---|---|
Seq Scan | კითხულობს ცხრილის ყველა რიგს | დიდი ცხრილი, პატარა შედეგი, შერჩევითი ფილტრი |
Index Scan | ინდექსზე დადის, შესაბამის რიგებს იღებს | იშვიათად, თუ ათასობითჯერ არ ციკლდება |
Index Only Scan | პასუხობს მხოლოდ ინდექსიდან | როცა Heap Fetches მაღალია |
Bitmap Heap Scan | აგროვებს რიგების მდებარეობას, შემდეგ გვერდებს თანმიმდევრობით კითხულობს | Recheck Cond lossy ბლოკებით |
Nested Loop | თითო გარე რიგზე შიდა მხარეს ამოწმებს | გარე რიგების რაოდენობა გაცილებით მეტია შეფასებულზე |
Hash Join | ერთი მხარის hash-ს აგებს, მეორეთი ამოწმებს | Batches 1-ზე მეტი ნიშნავს, რომ დისკზე გადავიდა |
Merge Join | აერთიანებს ორ დალაგებულ შესატანს | როცა მის მკვებავი sort-ები არის რეალური ღირებულება |
Sort | რიგებს ალაგებს | Sort Method: external merge Disk: |
HashAggregate | რიგებს მეხსიერებაში აჯგუფებს | ცუდი შეფასებისას დისკზე გადადის |
Gather | აგროვებს რიგებს პარალელური worker-ებიდან | დაგეგმილზე ნაკლები worker გაეშვა |
Memoize | ქეშავს შიდა შედეგებს nested loop-ში | დაბალი hit rate მეხსიერებას ფლანგავს |
მიმდევრობითი scan ავტომატურად ცუდი არ არის. 500-რიგიანი საძიებო ცხრილის თავიდან ბოლომდე წაკითხვა ნებისმიერ ინდექსზე იაფია, და planner ამას იცის. მიმდევრობითი scan პრობლემად მაშინ იქცევა, როცა ცხრილი დიდია, ფილტრი შერჩევითია და Rows Removed by Filter ექვსნიშნა რიცხვია.
ორი ხაზი, რომლის მყისიერად ამოცნობა ღირს. Sort Method: external merge Disk: 48312kB ნიშნავს, რომ sort work_mem-ში არ ჩატია და დროებით ფაილებში გადავიდა, რაც ჩვეულებრივ ყველაზე დიდი მოგებაა, რაც გაქვს: work_mem-ის გაზრდა იმ სესიისთვის ან იმ query-სთვის მას Sort Method: quicksort Memory: 51000kB-ად აქცევს. და Buckets: 65536 Batches: 8 Memory Usage: 3841kB hash join-ზე ნიშნავს, რომ hash ცხრილი იმავე მიზეზით მონაცემებზე რვა გავლად დაიყო. PostgreSQL-ის ტიუნინგი პატარა სერვერებისთვის ამბობს, რა უნდა იყოს ეს პარამეტრები 1-დან 8 GB-მდე მანქანაზე, და რატომ არის work_mem-ის გლობალურად გაზრდა მეხსიერების ამოწურვის გზა.
ნელი query, გამოსწორებული#
აი plan 2.4 მილიონი შეკვეთის ცხრილიდან, რომელიც პასუხობს „ერთი მომხმარებლის ბოლო ოცი შეკვეთა".
EXPLAIN (ANALYZE, BUFFERS)SELECT id, created_at, totalFROM ordersWHERE customer_id = 4821 AND created_at >= now() - interval '90 days'ORDER BY created_at DESCLIMIT 20;Limit (cost=48231.44..48231.49 rows=20 width=20) (actual time=812.334..812.339 rows=20 loops=1) Buffers: shared hit=1204 read=41988 -> Sort (cost=48231.44..48232.07 rows=252 width=20) (actual time=812.332..812.334 rows=20 loops=1) Sort Key: created_at DESC Sort Method: top-N heapsort Memory: 27kB -> Seq Scan on orders (cost=0.00..48224.72 rows=252 width=20) (actual time=0.412..811.903 rows=238 loops=1) Filter: ((customer_id = 4821) AND (created_at >= (now() - '90 days'::interval))) Rows Removed by Filter: 2399762 Buffers: shared hit=1204 read=41988Planning Time: 0.214 msExecution Time: 812.381 msყველაფერი, რაც გჭირდება, იქ არის. scan-მა 43 192 ბლოკი წაიკითხა, დაახლოებით 337 MB, რომ 238 რიგი დაებრუნებინა და 2.4 მილიონი გადაეყარა. 252 რიგის შეფასება რეალურ 238-თან ახლოს იყო, ამიტომ სტატისტიკა კარგადაა. უბრალოდ არ არსებობს ინდექსი, რომელიც ფილტრს შეესაბამება.
CREATE INDEX CONCURRENTLY orders_customer_created_idx ON orders (customer_id, created_at DESC);Limit (cost=0.43..38.72 rows=20 width=20) (actual time=0.041..0.088 rows=20 loops=1) Buffers: shared hit=23 -> Index Scan using orders_customer_created_idx on orders (cost=0.43..482.90 rows=252 width=20) (actual time=0.039..0.084 rows=20 loops=1) Index Cond: ((customer_id = 4821) AND (created_at >= (now() - '90 days'::interval))) Buffers: shared hit=23Planning Time: 0.302 msExecution Time: 0.114 msორმოცდასამი ათასის ნაცვლად ოცდასამი ბლოკი, და Sort node სრულიად გაქრა: რადგან ინდექსი created_at-ს ყოველი customer_id-ის შიგნით კლებადობით ინახავს, რიგები იმ თანმიმდევრობით მოდის, როგორც query ითხოვდა, და LIMIT ოცის შემდეგ ჩერდება. სვეტების რიგი ამ ინდექსში თვითნებური არ არის და DESC-იც არა. PostgreSQL-ის ინდექსები ახსნილი გადის, რატომ მიდის ტოლობის სვეტები პირველი და დიაპაზონისა ბოლო.
ხუთი რამ, რაც plan-ს ანელებს#
ცუდი რიგების შეფასება. შეადარე rows= actual ... rows=-ს ყოველ node-ზე. ორჯერადი სხვაობა არაფერია. ასჯერადი ნიშნავს, რომ planner-მა თავისი სტრატეგია ცუდ ინფორმაციაზე აირჩია, და მის ზემოთ მდგარი node ალბათ nested loop-ია, რომელიც hash join უნდა ყოფილიყო. მიზეზები: ცხრილი bulk load-ის შემდეგ არ გაანალიზებულა, default_statistics_target-ის 100 ძალიან მსხვილია გადახრილი სვეტისთვის, ან ორი სვეტი კორელირებს და planner მათ სელექტიურობებს ისე ამრავლებს, თითქოს დამოუკიდებელი იყოს. გამოსწორებები, ძალისხმევის მიხედვით:
ANALYZE orders;ALTER TABLE orders ALTER COLUMN status SET STATISTICS 1000;CREATE STATISTICS orders_city_region (dependencies, ndistinct) ON city, region FROM orders;ANALYZE orders;ინდექსი აკლია ან გამოუსადეგარია. ინდექსი არსებობს, მაგრამ query მას ვერ იყენებს: ფუნქცია სვეტზე (WHERE lower(email) = ...-ს lower(email)-ზე გამოსახულების ინდექსი სჭირდება), ტიპის შეუსაბამობა, რომელიც cast-ს აიძულებს, წამყვანი wildcard LIKE-ში, ან OR ორ სვეტზე, რომელსაც ერთი ინდექსი ვერ ფარავს.
დისკზე დალაგება ან hash. ზემოთ განვიხილეთ. ეძებე Disk: plan-ის ნებისმიერ ადგილას.
ძალიან ბევრი loop. nested loop 40 000 გავლით 0.2 ms-იანი index scan-ისა რვა წამია, რომელსაც არცერთი node არ აღიარებს. გაამრავლე actual time loops-ზე, სანამ დაიჯერებ, რომ node იაფია.
სამუშაო, რომელიც query-ს არ სჭირდება. სამის ნაცვლად ყველა სვეტის SELECT, ORDER BY LIMIT-ის გარეშე, DISTINCT, რომელიც მაგრავს გამრავლებულ join-ს, COUNT მთელ ცხრილზე ყოველ გვერდის ჩატვირთვაზე. ყველაზე იაფი query ის არის, რომელსაც შლი.
მკვდარი რიგები მეექვსე მიზეზია და კარგად იმალება: ცხრილი, რომელიც გაბერილია, რადგან autovacuum ვერ ასწრებს, გაცილებით დიდი ცხრილივით იკითხება, და Index Only Scan ათასობით Heap Fetches-ით იგივე ამბავია მეორე მხრიდან. Postgres-ის vacuum და bloat წასაკითხია, თუ query გაუარესდა მონაცემების ზრდის გარეშე.
ნელი query-ების პოვნა პირველ რიგში#
plan-ის კითხვა ვარაუდობს, რომ იცი, რომელი query წაიკითხო. სამი ხელსაწყო, დაყენების სირთულის ზრდის მიხედვით:
pg_stat_activityახლანდელი მომენტისთვის.SELECT pid, now() - query_start AS runtime, state, left(query, 80) FROM pg_stat_activity WHERE state <> 'idle' ORDER BY runtime DESC;აჩვენებს, რა გაშვებულია და რამდენი ხანია.pg_cancel_backend(pid)თავაზიანად აჩერებს,pg_terminate_backend(pid)- უხეშად.log_min_duration_statementლოგისთვის. დააყენე500-ზე და ყოველი ბრძანება, რომელიც ნახევარ წამზე დიდხანს გრძელდება, სერვერის ლოგში ჩაიწერება თავისი ხანგრძლივობითა და პარამეტრებით. არაფერი ჯდება, როცა არაფერია ნელი.pg_stat_statementsკანონზომიერებისთვის. სწორედ ეს ცვლის, როგორ მუშაობ. ისshared_preload_libraries-ში უნდა იყოს ჩამოთვლილი და რესტარტს საჭიროებს, შემდეგCREATE EXTENSION pg_stat_statements;.
SELECT calls, round(total_exec_time::numeric, 1) AS total_ms, round(mean_exec_time::numeric, 2) AS mean_ms, rows, left(query, 70) AS queryFROM pg_stat_statementsORDER BY total_exec_time DESCLIMIT 15;დაალაგე total_exec_time-ით და არა mean_exec_time-ით. query, რომელიც 4 ms გრძელდება და დღეში ორ მილიონჯერ გაეშვება, უფრო მეტი გიჯდება, ვიდრე ღამის ანგარიში, რომელიც ცხრა წამი გრძელდება, და ჩვეულებრივ უფრო მარტივი გასასწორებელია. pg_stat_statements_reset() მთვლელებს ასუფთავებს, რომ ფანჯარა გაზომო.
მეოთხე ვარიანტი, auto_explain, ლოგავს ნებისმიერი ბრძანების სრულ plan-ს, რომელიც ზღურბლს აღემატება, რაც იჭერს plan-ს, რომელიც მხოლოდ დილის 3 საათზე, გარკვეული პარამეტრებით ცუდდება. მისი ჩატვირთვა ყოველ query-ზე ცოტას ჯდება, ამიტომ ჩართე რაღაც კონკრეტულის დასაჭერად და ისევ გამორთე.
ოთხივეს superuser ან სათანადოდ პრივილეგირებული როლი სჭირდება, პლუს postgresql.conf-ის რედაქტირებისა და რესტარტის შესაძლებლობა. RE:NODE-ზე PostgreSQL ხაზი გაძლევს ამ სერვერისთვის გენერირებულ superuser პაროლს, ფაილ მენეჯერს და SFTP-ს კონფიგურაციის ფაილებისთვის და კონსოლს რესტარტისთვის, ასე რომ shared_preload_libraries პარამეტრია, რომლის შეცვლაც მართლა შეგიძლია და არა მხარდაჭერის ბილეთი. პანელის გზამკვლევები ამბობს, სად ჩნდება ეს მონაცემები.
რას არ გეუბნება plan#
plan აღწერს სამუშაოს, რომელიც სერვერმა ამ ბრძანებაზე გააკეთა. ის არაფერს ამბობს:
- lock-ის ლოდინზე. query, რომელიც
ALTER TABLE-ის უკან დგას, სრულიად ჯანსაღ plan-ს აჩვენებს და ოთხმოცდაათ წამს გრძელდება. შეამოწმეpg_locks,pg_stat_activity-თან შეერთებული, ანwait_event_typeსვეტი. - კავშირის დანახარჯზე. ახალი PostgreSQL კავშირის გახსნა პროცესს fork-ავს და რამდენიმე მილიწამსა და რამდენიმე მეგაბაიტს ჯდება. აპლიკაცია, რომელიც თითო მოთხოვნაზე ერთს ხსნის, კავშირზე უფრო მეტ დროს ხარჯავს, ვიდრე query-ზე. ეს pooler-ის საქმეა და არა planner-ის - იხილე კავშირების pool-ები და ლიმიტები.
- ქსელის round trip-ებზე. ორასი პატარა query ციკლში, თითოეული სწრაფი, N+1 პრობლემაა, და ყოველი ORM მას შემთხვევით აწარმოებს. ყველა plan იდეალურად გამოიყურება.
- კლიენტის მხარის დროზე. 400 000 რიგის აპლიკაციაში წამოღება, რომელსაც მხოლოდ რაოდენობა სჭირდებოდა, ნელია ადგილას, რომელსაც
EXPLAINვერ ხედავს. - დანარჩენ მანქანაზე. plan, რომელიც 40 000 ბლოკს კითხულობს, უქმ სერვერზე კარგია და საშინელი, როცა ექვსი სხვა კავშირი იგივეს აკეთებს. პანელის მეხსიერების, CPU-სა და დისკის გრაფიკები არის შემოწმება, რომ plan და მანქანა თანხმდება.
კიდევ ერთი პატიოსანი ლიმიტი: EXPLAIN ANALYZE-ის გარეშე აჩვენებს, რას აპირებს planner მიმდინარე სტატისტიკითა და მიმდინარე პარამეტრებით, და prepared statement-ებს ხუთი შესრულების შემდეგ generic plan-ზე გადასვლა შეუძლიათ, ამიტომ ის, რასაც შენი driver უშვებს, ყოველთვის არ არის ის, რაც ხელით გამოსცადე. EXPLAIN (GENERIC_PLAN) PostgreSQL 16-ზე და უფრო ახალზე სწორედ ამ ხარვეზის გამო არსებობს.
FAQ#
რატომ უგულებელყოფს Postgres ჩემს ინდექსს?
ჩვეულებრივ იმიტომ, რომ სჯერა, query ცხრილის დიდ ნაწილს აბრუნებს, და ამ შემთხვევაში მიმდევრობითი scan მართლა იაფია. ჯერ შეადარე შეფასება რეალობას. თუ შეფასება სწორია და მაინც index scan-ს ელოდები, მიზეზი ხშირად random_page_cost-ია, რომლის ნაგულისხმევია 4 და მბრუნავ დისკებს ვარაუდობს. NVMe-ზე 1.1 აპარატურას ასახავს და planner-ს მთლიანად index scan-ებისკენ სწევს.
Seq Scan ყოველთვის ცუდია?
არა. პატარა ცხრილზე ის ყველაზე სწრაფი ვარიანტია, და planner მას განზრახ აირჩევს. ცუდია, როცა ცხრილი დიდია და ფილტრი წაკითხულის უმეტესობას აგდებს, რასაც Rows Removed by Filter ხაზი ზუსტად გეუბნება.
რა განსხვავებაა cost-სა და actual time-ს შორის?
Cost არის ერთეულების გარეშე შეფასება, რომელსაც planner კანდიდატი plan-ების შესადარებლად იყენებს, ისე დაფიქსირებული, რომ ერთი მიმდევრობითი გვერდის წაკითხვა 1.0 ღირს. Actual time არის შესრულებისას გაზომილი მილიწამები. ერთს მეორედ ვერ გადაიყვან და არც უნდა სცადო - ერთადერთი გამოსადეგი შედარება შეფასებულ და რეალურ რიგების რაოდენობას შორისაა.
უნდა დავამატო თუ არა ინდექსი ყველა სვეტზე?
არა. ყოველი ინდექსი უნდა განახლდეს ინდექსირებული სვეტების ყოველ insert-ზე, update-სა და delete-ზე, დისკის ადგილს იკავებს და vacuum-ს სამუშაოს უმატებს. სამი კარგად შერჩეული კომპოზიტური ინდექსი ჩვეულებრივ თხუთმეტ ერთსვეტიანს სჯობს. შეხედე pg_stat_user_indexes-ს idx_scan = 0-ზე, რომ იპოვო ინდექსები, რომლებიც ბოლო რესტარტიდან არავის გამოუყენებია.
ჩემი query psql-ში სწრაფია და აპლიკაციიდან ნელა. რატომ?
ყველაზე ხშირად აპლიკაცია იმავე query-ს არ უშვებს: სხვა პარამეტრები, generic plan, გარშემო მოცული ტრანზაქცია განსხვავებული isolation level-ით, ან ORM, რომელიც LIMIT-სა და ORDER BY-ს ამატებს, რომელიც შენ არ დაგიწერია. ჩართე log_min_duration_statement, დაიჭირე ზუსტი ბრძანება, რომელიც სერვერმა მიიღო, და მას გაუკეთე explain.
რამდენად ხშირად უნდა გავუშვა ANALYZE ხელით?
Autovacuum ჩვეულებრივ შემთხვევას ამუშავებს. გაუშვი ხელით bulk load-ის, restore-ისა და მიგრაციის შემდეგ, რომელიც სვეტის განაწილებას ცვლის, რადგან სანამ ის არ გაეშვება, planner სტატისტიკით მუშაობს, რომელიც ცხრილს აღწერს, რომელიც აღარ არსებობს. dump სტატისტიკას თან არ ატარებს, სწორედ ამიტომ არის ახლად აღდგენილი მონაცემთა ბაზა ხშირად იდუმალად ნელი - pg_dump და pg_restore ამ ამბის დანარჩენს შეიცავს.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.