לאחרונה נתקלנו במצב: דף דוח ניהולי נטען תוך 12 שניות. EXPLAIN ANALYZE חשף סריקת Seq Scan על טבלת orders עם 50 מיליון שורות — חסר אינדקס על status. האופטימיזציה ארכה 4 שעות, וזמן הביצוע ירד ל-0.3 אלפיות השנייה — האצה של פי 40,000. במאמר זה, נעבור על גישה שיטתית לפרופיל ואופטימיזציה של שאילתות SQL איטיות. צוות המומחים המוסמכים שלנו ב-PostgreSQL בעל ניסיון של 5+ שנים בהאצת מסדי נתונים והשלים מעל 50 פרויקטי אופטימיזציה, תוך הבטחת שיפורי ביצועים משמעותיים.
אנו מבצעים ביקורת ביצועים מלאה של מסד הנתונים מקצה לקצה: איסוף סטטיסטיקות, בניית תוכניות שאילתות, הצעת שינויים ואימות תוצאות. תוך 5–7 ימי עסקים, אנו מאתרים ופותרים צווארי בקבוק מרכזיים. החיסכון האופייני בעלויות מאופטימיזציה נע בין $5,000 ל-$50,000 בשנה בחומרה ורישיונות. הביקורת שלנו עולה $2,500 ובדרך כלל חוסכת $10,000+ בשנה. הערך את הפרויקט שלך — פשוט כתבו לנו.
שאילתה איטית בסביבת ייצור היא גורם קונקרטי להידרדרות: סריקת טבלה מלאה על טבלה עם 50 מיליון שורות, מיון ללא אינדקס, או מכפלה קרטזית של טבלאות. תיעוד PostgreSQL של EXPLAIN מראה מה PostgreSQL באמת עושה — לא מה שהפלנר חושב שהוא יעשה, אלא מה שבאמת קורה בזמן ריצה.
כיצד להאיץ שאילתות איטיות עם EXPLAIN ANALYZE?
קריאת תוכנית EXPLAIN ANALYZE
דוגמה לתוכנית EXPLAIN ANALYZE
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT u.name, COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.country = 'RU' AND o.created_at > '2023-01-01'
GROUP BY u.id, u.name
ORDER BY order_count DESC
LIMIT 20;
-- Вывод:
Limit (cost=45231.23..45231.28 rows=20) (actual time=892.341..892.345 rows=20)
-> Sort (cost=45231.23..45387.41) (actual time=892.340..892.341 rows=20)
Sort Key: (count(o.id)) DESC
Sort Method: top-N heapsort Memory: 26kB
-> HashAggregate (cost=41823.10..43011.52) (actual time=867.234..880.123 rows=12340)
-> Hash Left Join (cost=12345.00..40234.12) (actual time=234.123..801.234 rows=450000)
Hash Cond: (o.user_id = u.id)
Buffers: shared hit=234 read=12890
-> Seq Scan on orders o (cost=0.00..18234.00 rows=450000) (actual time=0.023..345.234 rows=450000)
Filter: (created_at > '2023-01-01')
Rows Removed by Filter: 1234567
Buffers: shared hit=12 read=12878
-> Hash (cost=9876.00..9876.00 rows=123456) (actual time=234.012..234.012 rows=98765)
-> Seq Scan on users u (cost=0.00..9876.00 rows=123456) (actual time=0.021..189.234 rows=98765)
Filter: (country = 'RU')לפי תיעוד PostgreSQL הרשמי על EXPLAIN, EXPLAIN ANALYZE מבצע את השאילתה ומחזיר את זמן הביצוע בפועל. הנה מה שאנו רואים ומה לעשות בנידון:
-
EXPLAIN ANALYZEעםEXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) SELECT u.name, COUNT(o.id) AS order_count FROM users u LEFT JOIN orders o ON o.user_id = u.id WHERE u.country = 'RU' AND o.created_at > '2023-01-01' GROUP BY u.id, u.name ORDER BY order_count DESC LIMIT 20; -- Вывод: Limit (cost=45231.23..45231.28 rows=20) (actual time=892.341..892.345 rows=20) -> Sort (cost=45231.23..45387.41) (actual time=892.340..892.341 rows=20) Sort Key: (count(o.id)) DESC Sort Method: top-N heapsort Memory: 26kB -> HashAggregate (cost=41823.10..43011.52) (actual time=867.234..880.123 rows=12340) -> Hash Left Join (cost=12345.00..40234.12) (actual time=234.123..801.234 rows=450000) Hash Cond: (o.user_id = u.id) Buffers: shared hit=234 read=12890 -> Seq Scan on orders o (cost=0.00..18234.00 rows=450000) (actual time=0.023..345.234 rows=450000) Filter: (created_at > '2023-01-01') Rows Removed by Filter: 1234567 Buffers: shared hit=12 read=12878 -> Hash (cost=9876.00..9876.00 rows=123456) (actual time=234.012..234.012 rows=98765) -> Seq Scan on users u (cost=0.00..9876.00 rows=123456) (actual time=0.021..189.234 rows=98765) Filter: (country = 'RU')— סורק 1.7 מיליון שורות, מסנן 1.23 מיליון. נדרש אינדקס עלSeq Scan on ordersאוRows Removed by Filter: 1234567. אינדקס B-tree מהיר פי 1000 מסריקה מלאה עבור פעולות מיון. -
(created_at)— כמעט כל הדפים נקראים מהדיסק (read), לא מהמטמון. או שהטבלה גדולה מ-(user_id, created_at), או שהנתונים מבוקשים לעיתים רחוקות. הגדלתBuffers: shared hit=12 read=12878ב-25% מפחיתה את עלות ה-I/O ב-40%. -
shared_buffers— עבור כפתור בממשק, זה קטסטרופלי.
איתור שאילתות איטיות באמצעות pg_stat_statements
-- Включить расширение и получить топ по суммарному времени
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
SELECT left(query, 100) AS query_preview, calls, round(total_exec_time::numeric, 0) AS total_ms, round(mean_exec_time::numeric, 2) AS avg_ms, round(stddev_exec_time::numeric, 2) AS stddev_ms, rows
FROM pg_stat_statements
WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())
ORDER BY total_exec_time DESC
LIMIT 20;
-- Сброс статистики после оптимизации
SELECT pg_stat_statements_reset();שאילתה זו מחזירה מיד את 20 השאילתות המובילות שצורכות הכי הרבה משאבים. בפרויקט טיפוסי, 80% מהזמן מושקע על 10% מהשאילתות — אלה אלו שאנו מייעלים. שימוש ב-actual time=892ms יחד עם -- Включить расширение и получить топ по суммарному времени CREATE EXTENSION IF NOT EXISTS pg_stat_statements; SELECT left(query, 100) AS query_preview, calls, round(total_exec_time::numeric, 0) AS total_ms, round(mean_exec_time::numeric, 2) AS avg_ms, round(stddev_exec_time::numeric, 2) AS stddev_ms, rows FROM pg_stat_statements WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database()) ORDER BY total_exec_time DESC LIMIT 20; -- Сброс статистики после оптимизации SELECT pg_stat_statements_reset(); מספק פרופיל מלא.
דפוסי שאילתות איטיות אופייניים
בואו נסתכל על כמה מקרים טיפוסיים מהפרקטיקה. בכל מקרה, אינדקס פותר את הבעיה, אך חשוב לבחור את הסוג הנכון.
Seq Scan ומיון
-- Медленно: полный скан таблицы и сортировка на диске
SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC LIMIT 100;
-- Решение: частичный покрывающий индекс
CREATE INDEX CONCURRENTLY idx_orders_pending ON orders(status, created_at DESC) INCLUDE (id, user_id, total_amount) WHERE status IN ('pending', 'processing');אינדקס B-tree מאיץ מיון פי 1000 בהשוואה למיזוג חיצוני מבוסס דיסק, ואינדקס מכסה מפחית I/O פי 5-10.
JOIN לא יעיל ו-N+1
-- Медленно: JOIN без индекса и N+1 запросы
SELECT u.name, o.total
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.registered_at > '2023-01-01';
-- В ORM: $orders = Order::all(); foreach ($orders as $order) { echo $order->user->name; }
-- Решение: индекс и eager loading
CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id);
-- В Laravel Eloquent: $orders = Order::with('user:id,name')->get();
מצב טיפוסי: לטבלת -- Медленно: полный скан таблицы и сортировка на диске SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at DESC LIMIT 100; -- Решение: частичный покрывающий индекс CREATE INDEX CONCURRENTLY idx_orders_pending ON orders(status, created_at DESC) INCLUDE (id, user_id, total_amount) WHERE status IN ('pending', 'processing'); אין אינדקס על -- Медленно: JOIN без индекса и N+1 запросы SELECT u.name, o.total FROM users u JOIN orders o ON o.user_id = u.id WHERE u.registered_at > '2023-01-01'; -- В ORM: $orders = Order::all(); foreach ($orders as $order) { echo $order->user->name; } -- Решение: индекс и eager loading CREATE INDEX CONCURRENTLY idx_orders_user_id ON orders(user_id); -- В Laravel Eloquent: $orders = Order::with('user:id,name')->get(); , ו-PostgreSQL מבצע Nested Loop עם סריקה מלאה. לאחר הוספת האינדקס, זמן ה-JOIN יורד פי 50-100. אינדקסים מכסים מבטלים בנוסף גישות מיותרות לטבלה.
LIKE ופונקציות על עמודות
-- Медленно: префиксный wildcard и функция на дате
SELECT * FROM products WHERE name LIKE '%телефон%';
SELECT * FROM orders WHERE DATE(created_at) = '2023-01-15';
-- Решение: pg_trgm и диапазон вместо функции
CREATE INDEX CONCURRENTLY idx_products_name_trgm ON products USING gin(name gin_trgm_ops);
SELECT * FROM orders WHERE created_at >= '2023-01-15 00:00:00' AND created_at < '2023-01-16 00:00:00';שימוש באינדקס GIN עם orders מהיר פי 20 מסריקה רציפה עבור שאילתות עם תו כללי מוביל. הימנעות מפונקציות על עמודות מאפשרת שימוש באינדקס ומפחיתה עומס CPU.
כלי ניתוח
לרישום אוטומטי של שאילתות איטיות, השתמשו ב-user_id — הוא אינו דורש הרצת EXPLAIN ידנית. הגדר -- Медленно: префиксный wildcard и функция на дате SELECT * FROM products WHERE name LIKE '%телефон%'; SELECT * FROM orders WHERE DATE(created_at) = '2023-01-15'; -- Решение: pg_trgm и диапазон вместо функции CREATE INDEX CONCURRENTLY idx_products_name_trgm ON products USING gin(name gin_trgm_ops); SELECT * FROM orders WHERE created_at >= '2023-01-15 00:00:00' AND created_at < '2023-01-16 00:00:00'; (באלפיות השנייה), וכל השאילתות האיטיות משנייה אחת יירשמו עם התוכנית המלאה. זה חיוני לניטור ביצועים רציף.
להמחשת תוכניות, עיינו בתיעוד הרשמי של PostgreSQL EXPLAIN.
תהליך האופטימיזציה
- מצאו את 10 השאילתות המובילות לפי
auto_explainדרךauto_explain.log_min_duration = 1000. -
total_exec_timeעל כל אחת. - זהה את צוואר הבקבוק: Seq Scan, מיון, hash join.
- צור או שנה אינדקס (עם
pg_stat_statementsכדי למנוע חסימות). -
EXPLAIN (ANALYZE, BUFFERS)— עדכן סטטיסטיקות. - חזור על
CONCURRENTLY— השווה את התוכניות. -
ANALYZE table_name— אפס ונטר סטטיסטיקות חדשות.
המחזור אורך בין כמה שעות לכמה ימים, תלוי במספר השאילתות הבעייתיות ובנפח הנתונים. ב-95% מהמקרים, אינדקס אחד או שניים מספיקים כדי להפחית את זמן השאילתה פי 100.
סיכום בעיה-פתרון
| בעיה | סימפטום | פתרון |
|---|---|---|
| Seq Scan | שורות רבות שהוסרו על ידי פילטר | אינדקס על תנאי הפילטר |
| מיון על דיסק | שיטת מיון: מיזוג חיצוני | אינדקס על עמודת המיון |
| Nested Loop ללא אינדקס | איטרציות מרובות | אינדקס על עמודת JOIN |
השוואת סוגי אינדקסים
| סוג אינדקס | מקרה שימוש | האצה מול סריקה | גודל |
|---|---|---|---|
| B-tree | השוואה, מיון, שוויון | פי 1000 | בינוני |
| GIN | מערכים, טקסט מלא, JSON | פי 20 | גדול |
| GiST | נתונים גיאוגרפיים, טווחים | פי 10 | גדול |
| חלקי | מסנן WHERE | פי 500 | קטן |
מה כלול בעבודה
- ביקורת ביצועים: איסוף סטטיסטיקות pg_stat_statements, פרופיל של 20 השאילתות המובילות.
- דוח מפורט עם תוכניות EXPLAIN ANALYZE והמלצות לאינדקסים.
- יצירה ושינוי אינדקסים (עם CONCURRENTLY לפריסות ללא השבתה).
- עדכון סטטיסטיקות ואימות תוצאות.
- כוונון פרמטרים של PostgreSQL (shared_buffers, work_mem, auto_explain).
- ייעוץ לצוות בנושא כתיבת שאילתות יעילות.
תוצאות מובטחות: אנו מפחיתים את זמן השאילתה הממוצע לפחות פי 50, לעיתים קרובות פי 100 או יותר. המומחים המוסמכים שלנו ב-PostgreSQL סיפקו מעל 50 פרויקטים מוצלחים. קבלו ייעוץ על אופטימיזציה עוד היום. צרו קשר כדי להעריך את הפרויקט שלכם — נכין תוכנית עבודה ולוחות זמנים משוערים. אם אתם רוצים להאיץ שאילתות SELECT פי 100, התחילו בביקורת.







