כוונון שאילתות SQL באמצעות AI

כדי לשפר את ביצועי השאילתות במסד הנתונים של AlloyDB ל-PostgreSQL, אתם יכולים לשנות את שאילתות ה-SQL באמצעות תובנות מבוססות-AI. הפונקציה ai.tune_sql, שמבוססת על מודל שפה גדול (LLM), מנתחת את תוכניות הביצוע של השאילתות כדי לזהות צווארי בקבוק מורכבים ולספק המלצות שאפשר ליישם כדי לבצע אופטימיזציה.

כשמפעילים את הפונקציה ai.tune_sql, היא מנתחת את השאילתה ומספקת תובנות והמלצות לגבי השאילתה בתחומים הבאים:

  • סיכום השאילתה: ניתוח צווארי בקבוק בביצועי השאילתה.
  • שכתוב שאילתות: הצעות לשיטות אופטימיזציה לשכתוב השאילתה המקורית לשאילתה מקבילה ויעילה יותר.
  • המלצות לאינדקסים: המלצות לסוגי אינדקסים כמו אינדקסים של ביטויים, אינדקסים מסוג Generalized Inverted Index‏ (GIN) ואינדקסים מסוג Scalable Nearest Neighbors‏ (ScaNN).
  • המלצות לאיסוף נתונים סטטיסטיים: המלצות לאיסוף נתונים סטטיסטיים של טבלאות ANALYZE ושל כמה עמודות, על סמך תוכנית הביצוע של השאילתה.
  • כוונון דגלים של Grand Unified Configuration ‏ (GUC): המלצה על ערכי GUC חדשים על ידי ניתוח תוכנית הביצוע ומידע על המערכת, כמו שימוש בזיכרון ובמאגר נתונים זמני וגודל.
  • ‫Analyze and vacuum: המלצה להריץ את ANALYZE ו-VACUUM בטבלאות, על סמך היסטוריית הניתוח והוואקום ומספר ה-tuples החיים והמתים.

מגבלות

לכוונון שאילתות SQL יש את המגבלות הבאות:

  • שאילתות מוכנות ושאילתות עם פרמטרים: בשאילתות מוכנות ושאילתות עם פרמטרים, ai.tune_sql לא מספק המלצות לשכתוב שאילתות, אבל עדיין מספק המלצות אחרות לכוונון. כדי לקבל המלצות לשכתוב שאילתה שמכילה פלייסהולדרים של פרמטרים, כמו $1 או $2 שהועתקו מ-Query Insights או מ-pg_stat_statements, צריך להחליף את הפלייסהולדרים בערכים ליטרליים מייצגים לפני שמריצים את ai.tune_sql.
  • ‫הצהרות SELECT לקריאה בלבד רק כש-explain_analyze הוא TRUE: רק הצהרות SELECT בודדות לקריאה בלבד נתמכות כש-explain_analyze מוגדר כ-TRUE. אם מריצים את ai.tune_sql כשהערך של explain_analyze מוגדר ל-TRUE בהצהרות אחרות, כמו INSERT,‏ UPDATE,‏ DELETE,‏ SELECT INTO או SELECT FOR UPDATE, מוחזרת הודעת שגיאה.

לפני שמתחילים

מוודאים שיש לכם את הפרטים הבאים:

הפעלת כוונון SQL

רושמים LLM ומפעילים את הדגלים הנדרשים לניהול מסד הנתונים.

  1. יוצרים נקודת קצה (endpoint) של מודל Gemini לפונקציה ai.tune_sql, על ידי רישום של נקודת קצה גלובלית או אזורית של המודל באזור של המכונה:

    • כדי לרשום נקודת קצה של מודל גלובלי כשהמודל לא זמין באזור של המכונה, לדוגמה gemini-3.5-flash ב-locations/global, מריצים את ההצהרה הבאה:

      CALL google_ml.create_model(
        model_id => 'gemini-3.5-flash-global',
        model_request_url => 'https://aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/global/publishers/google/models/gemini-3.5-flash:generateContent',
        model_qualified_name => 'gemini-3.5-flash',
        model_provider => 'google',
        model_type => 'llm',
        model_auth_type => 'alloydb_service_agent_iam');
      

      מחליפים את PROJECT_ID במזהה הפרויקט שבו המודל זמין.

    • כדי לרשום נקודת קצה של מודל אזורי באזור של המכונה, למשל gemini-2.5-pro ב-locations/REGION_ID, מריצים את ההצהרה הבאה:

      CALL google_ml.create_model(
        model_id => 'gemini-2.5-pro',
        model_request_url => 'https://REGION_ID-aiplatform.googleapis.com/v1/projects/PROJECT_ID/locations/REGION_ID/publishers/google/models/gemini-2.5-pro:generateContent',
        model_qualified_name => 'gemini-2.5-pro',
        model_provider => 'google',
        model_type => 'llm',
        model_auth_type => 'alloydb_service_agent_iam');
      

      מחליפים את מה שכתוב בשדות הבאים:

      • ‫REGION_ID: האזור של מכונת AlloyDB, לדוגמה us-central1.
      • ‫PROJECT_ID: מזהה הפרויקט שבו המודל זמין.
  2. מגדירים את מזהה המודל להמלצות לכוונון שאילתות SQL בסשן הנוכחי של מסד הנתונים. מציינים את הערך של model_id שרשמתם בשלב הקודם, למשל 'gemini-3.5-flash-global' או 'gemini-2.5-pro':

    SET google_db_advisor.llm_model_id = 'gemini-3.5-flash-global';
    
  3. כדי להפעיל את כוונון שאילתות ה-SQL בסשן הנוכחי של מסד הנתונים, מגדירים את הדגל google_db_advisor.enable_ai_tune_sql:

    SET google_db_advisor.enable_ai_tune_sql = on;
    

    הפקודה SET חלה רק על הסשן הנוכחי של מסד הנתונים. כדי לשמר את ההגדרות האלה בסשנים הבאים, צריך להריץ את הפקודה ALTER DATABASE DATABASE_NAME SET FLAG_NAME = VALUE; לשני הדגלים, או להגדיר את הדגלים לניהול מסד נתונים במכונה.

כוונון ידני של שאילתות SQL

כדי לכוונן ידנית שאילתות SQL, מריצים את הפונקציה ai.tune_sql באמצעות הצהרת SELECT.

  1. מחברים לקוח psql למכונה של AlloyDB או פותחים את AlloyDB Studio.

    אם השתמשתם ב-SET כדי להגדיר את google_db_advisor.llm_model_id ואת google_db_advisor.enable_ai_tune_sql, הריצו את ההצהרות האלה של SET באותו סשן של מסד הנתונים לפני הקריאה ל-ai.tune_sql.

  2. כדי לכוונן שאילתות SQL, מריצים את הפונקציה ai.tune_sql. הפונקציה הזו מאחזרת את הסכימה שקשורה לשאילתה ושולחת אותה, יחד עם השאילתה, ל-LLM לצורך ניתוח הביצועים:

    SELECT ai.tune_sql(query => 'SQL_QUERY', explain_analyze => TRUE);
    

    מחליפים את SQL_QUERY בשאילתת ה-SQL שרוצים לנתח.

    כברירת מחדל, פרמטר ההפעלה (explain_analyze) מוגדר לערך TRUE. כשהערך הוא TRUE, הפונקציה מריצה את EXPLAIN ANALYZE כדי להריץ את השאילתה ולתעד את תוכנית הביצוע. הגישה הזו מספקת עצות מדויקות ושימושיות יותר לכוונון השאילתות. אם מגדירים את explain_analyze ל-FALSE, הפונקציה מריצה רק את EXPLAIN כדי ליצור תוכנית בלי להריץ את השאילתה.

הצגת תוכניות לביצוע שאילתות עם המלצות לשיפור הביצועים

בפלט מוצגת קודם התוכנית הרגילה לביצוע השאילתה, ואחריה ההמלצות המפורטות שנוצרו על ידי AI. ההמלצות כוללות סיכום של השאילתה, הצעות לשכתוב והמלצות לאיסוף נתונים סטטיסטיים, לכוונון דגלי GUC ולביצוע פעולות ואקום. הפונקציה ai.tune_sql יכולה ליצור המלצות תוך שתיים עד ארבע דקות.

בדוגמה הבאה מוצג הפלט של ai.tune_sql לשאילתת anti-join:

SELECT ai.tune_sql(
  'SELECT *
   FROM store_sales
   WHERE ss_customer_sk NOT IN (
     SELECT sr_customer_sk
     FROM store_returns
     WHERE sr_item_sk = ss_item_sk)');

                     tune_sql
--------------------------------------------------
 NOTE
 --------------------------------------------------
 Advices generated with the assistance of an LLM. Independent verification of these advices is required due to the potential for inaccuracies.
 --------------------------------------------------
 ORIGINAL QUERY PLAN
 --------------------------------------------------
 Seq Scan on store_sales  (cost=0.00..111292742.52 rows=1440384 width=115) (actual time=0.092..103093.207 rows=1306312.00 loops=1)
   Filter: (NOT (ANY (ss_customer_sk = (SubPlan 1).col1)))
   Rows Removed by Filter: 1574198
   Buffers: shared hit=72987539
   SubPlan 1
     ->  Index Scan using idx_sr_item_sk on store_returns  (cost=0.42..76.74 rows=18 width=4) (actual time=0.005..0.034 rows=19.38 loops=2880510)
           Index Cond: (sr_item_sk = store_sales.ss_item_sk)
           Index Searches: 2880510
           Buffers: shared hit=72933670
 Planning Time: 0.257 ms
 --------------------------------------------------
 QUERY SUMMARY
 --------------------------------------------------
 The original query plan shows a Sequential Scan on store_sales with a correlated SubPlan for the NOT IN condition. For each of the 1.44 million rows in store_sales, it executes an Index Scan on store_returns, resulting in over 2.88 million index searches. This nested loop execution is the primary bottleneck, taking over 103 seconds. Converting the correlated NOT IN subquery to an anti-join (NOT EXISTS) will allow the optimizer to use a Hash Anti Join, processing both tables in bulk and significantly reducing execution time.
 --------------------------------------------------
 AI SUGGESTED QUERY 1
 --------------------------------------------------
 SELECT * FROM store_sales WHERE NOT EXISTS (SELECT 1 FROM store_returns WHERE store_returns.sr_item_sk = store_sales.ss_item_sk AND (store_returns.sr_customer_sk = store_sales.ss_customer_sk OR store_returns.sr_customer_sk IS NULL OR store_sales.ss_customer_sk IS NULL));
 Reason: The original query uses a NOT IN subquery which is executed as a correlated subquery (SubPlan), leading to millions of index scans. Rewriting to NOT EXISTS allows the optimizer to use a Hash Anti Join, which is much more efficient for large datasets. The OR IS NULL conditions ensure strict semantic equivalence with NOT IN when dealing with NULL values.
 --------------------------------------------------
 REWRITTEN QUERY PLAN
 --------------------------------------------------
 Gather  (cost=8541.96..139088.40 rows=154194 width=115)
   Workers Planned: 2
   ->  Parallel Hash Anti Join  (cost=7541.96..122669.00 rows=64248 width=115)
         Hash Cond: (store_sales.ss_item_sk = store_returns.sr_item_sk)
         Join Filter: ((store_returns.sr_customer_sk = store_sales.ss_customer_sk) OR (store_returns.sr_customer_sk IS NULL) OR (store_sales.ss_customer_sk IS NULL))
         ->  Parallel Seq Scan on store_sales  (cost=0.00..65872.20 rows=1200320 width=115)
         ->  Parallel Hash  (cost=6044.76..6044.76 rows=119776 width=8)
               ->  Parallel Seq Scan on store_returns  (cost=0.00..6044.76 rows=119776 width=8)
 --------------------------------------------------
 AI SUGGESTED INDEX 1
 --------------------------------------------------
 CREATE INDEX idx_sr_item_customer ON store_returns (sr_item_sk, sr_customer_sk);
 Reason: A composite index on (sr_item_sk, sr_customer_sk) allows index-only scans for the subquery or early aggregation, reducing table access and speeding up the anti-join condition evaluation.
 Note: Indexes are suggested solely based on the given query, but they don't consider the overall system workload.
           The suggested indexes improve the performance of the original query.
 --------------------------------------------------
 GUC FLAGS TUNING
 --------------------------------------------------
 SET work_mem = '64MB'; The current work_mem of 4MB is too small for a Hash Anti Join on tables with hundreds of thousands of rows. Increasing it to 64MB allows the hash table for store_returns to fit entirely in memory, preventing costly disk spills during the join.

 Execution Time: 103167.475 ms
(50 rows)

המאמרים הבאים