Modern database-driven applications live and die by their query performance. Slow queries can lead to frustrated users, cascading system failures, and inflated cloud bills. While experienced database administrators (DBAs) are useful, their time is often limited. This guide will demonstrate how we can use Artificial Intelligence, specifically large language models (LLMs), to assist in analyzing PostgreSQL query execution plans and suggest optimizations. We’ll walk through a practical workflow, from capturing a query plan to interpreting AI-generated advice, helping us identify bottlenecks and improve performance more efficiently.
Prerequisites
Before we dive in, ensure you have the following set up:
- Python 3.8+: Our automation scripts will be written in Python.
- OpenAI API Key: We’ll be using the OpenAI API for AI capabilities. You can obtain one from the OpenAI platform. Ensure it’s set as an environment variable named
OPENAI_API_KEY. - PostgreSQL Database: A running PostgreSQL instance with some sample data is necessary. We’ll use a hypothetical e-commerce database for our examples.
- Python Libraries: Install the necessary libraries using pip:
pip install openai psycopg2-binary
```
(`psycopg2-binary` is often easier to install than `psycopg2` directly, as it includes pre-compiled binaries.)
* **Basic SQL Knowledge:** Familiarity with SQL and understanding of concepts like `JOIN`s, `WHERE` clauses, and the general idea of indexes will be beneficial.
## Step-by-step sections
### Step 1: Prepare Your Environment and Database Connection
First, we set up our Python script to interact with both the OpenAI API and our PostgreSQL database. We'll use `psycopg2` for database connectivity and the `openai` library for AI interactions.
Create a Python file (e.g., `optimize_query.py`) and add the following basic structure:
```python
import os
import psycopg2
import json
from openai import OpenAI
# Ensure your OpenAI API key is set as an environment variable
# If not, uncomment and set it directly (not recommended for production)
# os.environ["OPENAI_API_KEY"] = "sk-YOUR_OPENAI_API_KEY_HERE"
# Initialize OpenAI client
client = OpenAI()
def get_db_connection():
"""Establishes and returns a PostgreSQL database connection."""
try:
conn = psycopg2.connect(
host="localhost",
database="your_database_name", # <--- REPLACE THIS
user="your_username", # <--- REPLACE THIS
password="your_password" # <--- REPLACE THIS
)
print("Successfully connected to the database.")
return conn
except psycopg2.Error as e:
print(f"Error connecting to database: {e}")
return None
def execute_query(conn, query, fetch_one=False, fetch_all=False):
"""Executes a SQL query and returns results if any."""
try:
with conn.[cursor](/reviews/cursor-ai-review-2026-is-it-worth-switching-from-vs-code/)() as cur:
cur.execute(query)
if fetch_one:
return cur.fetchone()
if fetch_all:
return cur.fetchall()
conn.commit() # Commit changes for DDL/DML
return True # Indicate success for non-fetching queries
except psycopg2.Error as e:
print(f"Error executing query: {e}")
conn.rollback() # Rollback on error
return False # Indicate failure
def get_explain_plan(conn, query):
"""Executes EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) for a given query."""
explain_query = f"EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) {query}"
print(f"\nExecuting EXPLAIN ANALYZE for the query...")
result = execute_query(conn, explain_query, fetch_all=True)
if result:
# EXPLAIN (FORMAT JSON) returns a single row with a single column, which is a JSON array
return json.dumps(result[0][0], indent=2)
return None
def get_ai_suggestions(original_query, explain_plan_json):
"""Sends the query and explain plan to OpenAI for optimization suggestions."""
prompt = f"""
We are analyzing a PostgreSQL query and its execution plan to find optimization opportunities.
Please review the following SQL query and its EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) output.
Suggest specific, actionable improvements, including potential index recommendations, query rewrites, or configuration changes.
Explain *why* each suggestion is beneficial, referencing parts of the EXPLAIN plan (e.g., "Seq Scan on products", "Cost: X..Y") if possible.
Be concise and focus on practical steps.
---
Original SQL Query:
{original_query}
---
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) Output:
{explain_plan_json}
---
Optimization Suggestions:
"""
print("\nSending query and EXPLAIN plan to AI for suggestions...")
try:
response = client.chat.completions.create(
model="gpt-3.5-turbo", # Consider "gpt-4" for more nuanced suggestions, but higher cost
messages=[
{"role": "system", "content": "You are a helpful assistant specialized in PostgreSQL query optimization."},
{"role": "user", "content": prompt}
],
temperature=0.7, # A bit of creativity, but still factual
max_tokens=1000
)
return response.choices[0].message.content
except Exception as e:
print(f"Error getting AI suggestions: {e}")
return "Could not get AI suggestions."
if __name__ == "__main__":
conn = get_db_connection()
if not conn:
exit()
# ... rest of the steps will go here ...
conn.close()
print("Database connection closed.")
Action: Replace your_database_name, your_username, and your_password in get_db_connection() with your actual database credentials.
Step 2: Define a Problematic Query
Let’s consider a common scenario in an e-commerce application: finding high-value customers who purchased items from a specific category within a certain timeframe. This query often involves multiple joins and potentially large tables, making it a good candidate for optimization.
Assume we have the following tables:
customers:customer_id(PK),customer_nameorders:order_id(PK),customer_id(FK),order_dateorder_items:item_id(PK),order_id(FK),product_id(FK),quantityproducts:product_id(PK),product_name,category,price
Add this query to the if __name__ == "__main__": block in your script:
# ... inside if __name__ == "__main__": block ...
problematic_query = """
SELECT
c.customer_id,
c.customer_name,
SUM(oi.quantity * p.price) AS total_spent
FROM
customers c
JOIN
orders o ON c.customer_id = o.customer_id
JOIN
order_items oi ON o.order_id = oi.order_id
JOIN
products p ON oi.product_id = p.product_id
WHERE
p.category = 'Electronics' AND o.order_date >= '2023-01-01'
GROUP BY
c.customer_id, c.customer_name
HAVING
SUM(oi.quantity * p.price) > 1000
ORDER BY
total_spent DESC
LIMIT 10;
"""
print("\n--- Original Query ---")
print(problematic_query)
Action: Ensure your database has these tables populated with some data to generate a realistic EXPLAIN ANALYZE output. You can use a tool like pg_dump to create sample data or generate it programmatically.
Step 3: Obtain the Query Execution Plan
The EXPLAIN ANALYZE command in PostgreSQL provides detailed information about how the database executes a query, including actual execution times, rows processed, and buffer usage. Adding FORMAT JSON makes the output machine-readable, which is ideal for our AI assistant.
Our get_explain_plan function already handles this. Add the call to it:
# ... inside if __name__ == "__main__": block ...
explain_plan = get_explain_plan(conn, problematic_query)
if explain_plan:
print("\n--- EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) Output ---")
print(explain_plan)
else:
print("Failed to get EXPLAIN plan.")
conn.close()
exit()
Action: Run the script now (python optimize_query.py). You should see the query printed, followed by a large JSON output representing the EXPLAIN ANALYZE plan. Review the plan briefly; look for “Seq Scan” (sequential scans) on large tables, high “Cost” values, and significant “Actual Time” discrepancies.
Step 4: use AI for Optimization Suggestions
Now for the core AI interaction. We’ll feed the original query and its EXPLAIN ANALYZE JSON output to our get_ai_suggestions function, which then calls the OpenAI API.
Add the following to your script:
# ... inside if __name__ == "__main__": block ...
ai_suggestions = get_ai_suggestions(problematic_query, explain_plan)
print("\n--- AI Optimization Suggestions ---")
print(ai_suggestions)
Action: Run the script again. After printing the EXPLAIN plan, it will send the data to OpenAI and print the AI’s suggestions.
Example AI output might look something like this:
--- AI Optimization Suggestions ---
Based on the EXPLAIN plan, here are some optimization suggestions:
1. **Index Recommendation: `products.category`**:
* **Why**: The `WHERE p.category = 'Electronics'` clause likely results in a "Seq Scan on products" if no index exists. An index on `products.category` would allow for a much faster "Bitmap Index Scan" or "Index Scan", drastically reducing the time spent filtering products.
* **Action**: `CREATE INDEX idx_products_category ON products (category);`
2. **Index Recommendation: `orders.order_date`**:
* **Why**: Similarly, `o.order_date >= '2023-01-01'` might also cause a sequential scan on the `orders` table. An index would speed up filtering orders by date.
* **Action**: `CREATE INDEX idx_orders_order_date ON orders (order_date);`
3. **Composite Index for Joins (Optional but beneficial):**
* **Why**: If the joins involving `customer_id` (customers to orders), `order_id` (orders to order_items), and `product_id` (order_items to products) are consistently slow, ensure you have indexes on the foreign key columns. For example, `orders.customer_id` and `order_items.order_id` and `order_items.product_id`.
* **Action**: `CREATE INDEX idx_orders_customer_id ON orders (customer_id);`
`CREATE INDEX idx_order_items_order_id ON order_items (order_id);`
`CREATE INDEX idx_order_items_product_id ON order_items (product_id);`
4. **Consider Materialized View for Frequent Queries:**
* **Why**: If this exact query (or a very similar one) is run frequently and the underlying data doesn't change too often, a materialized view could pre-compute the results, offering near-instantaneous retrieval at the cost of periodic refreshes.
* **Action**: `CREATE MATERIALIZED VIEW top_electronics_customers AS SELECT ... (original query) ...;`
`REFRESH MATERIALIZED VIEW top_electronics_customers;`
Review the `EXPLAIN` plan's `Actual Time` and `Rows Removed by Filter` for the `Seq Scan` operations to confirm where the most time is being spent before creating indexes.
Step 5: Evaluate and Apply AI Suggestions
The AI provides suggestions, but it’s crucial to understand them and verify their applicability. AI can sometimes “hallucinate” or provide generic advice that isn’t optimal for your specific schema or data distribution.
- Review: Read each suggestion carefully. Does it make sense in the context of your query and schema? Does it directly address a high-cost operation identified in the
EXPLAINplan? - Prioritize: Start with the simplest and most impactful suggestions, typically index creation.
- Test: Crucially, never apply changes directly to a production database without thorough testing. Use a staging or development environment.
Let’s assume the AI suggested creating an index on products.category. We can apply this using our execute_query function.
# ... inside if __name__ == "__main__": block ...
# Example of applying an AI suggestion: Create an index
print("\nAttempting to apply a suggested index...")
create_index_query = "CREATE INDEX IF NOT EXISTS idx_products_category ON products (category);"
if execute_query(conn, create_index_query):
print("Index 'idx_products_category' created (or already exists).")
else:
print("Failed to create index.")
# Re-run EXPLAIN ANALYZE with the potentially optimized schema
print("\n--- Re-running EXPLAIN ANALYZE after applying index ---")
optimized_explain_plan = get_explain_plan(conn, problematic_query)
if optimized_explain_plan:
print(optimized_explain_plan)
else:
print("Failed to get optimized EXPLAIN plan.")
Action: Add the index creation and re-EXPLAIN steps to your script. Run it. Compare the EXPLAIN ANALYZE output before and after the index creation. You should see a significant change in the execution plan, likely replacing a “Seq Scan” with an “Index Scan” or “Bitmap Index Scan” and reducing the “Actual Time” for that operation.
Step 6: Iterate and Refine
Query optimization is often an iterative process.
- Analyze the new plan: After applying one set of optimizations, the bottleneck might shift to another part of the query. Feed the new
EXPLAIN ANALYZEplan back into the AI to get further suggestions. - Consider Query Rewrites: Sometimes, indexes aren’t enough, and the query itself needs to be rewritten. The AI might suggest using Common Table Expressions (CTEs), subqueries, or different join types. Always test these changes thoroughly.
- Database Configuration: Less common for direct query optimization, but sometimes global database parameters (like
work_memorshared_buffers) can impact performance. The AI might hint at these if it detects resource constraints.
Step 7: Measure Real-World Impact
While EXPLAIN ANALYZE provides estimates and actual execution times, the true measure of optimization is its impact on your application’s performance.
- Benchmark: Use tools like
pgbenchor custom load testing scripts to simulate real-world traffic against your optimized queries. - Monitoring: After deploying changes, monitor your database’s performance metrics (CPU usage, I/O, latency, query times) to ensure the optimization holds up under load and doesn’t introduce new issues.
Common Issues
- AI Hallucinations / Incorrect Suggestions: LLMs can sometimes generate plausible-sounding but incorrect or suboptimal advice. Always critically evaluate suggestions. AI is an assistant, not a replacement for a DBA’s expertise.
- API Rate Limits and Costs: Frequent API calls, especially with larger models like GPT-4, can quickly consume your API budget or hit rate limits. Start with smaller models (
gpt-3.5-turbo) and optimize your prompts to be concise. - Database Connectivity Errors: Ensure your database credentials, host, and port are correct. Check firewall rules if the database is not on
localhost. - Misinterpreting
EXPLAINPlans: While the AI helps, understanding the basics ofEXPLAINoutput (e.g., node types, costs, rows, actual time) will help you validate AI suggestions and provide better context in your prompts. - Sensitive Data Exposure: Never send sensitive production data (actual rows, user IDs, PII) to public AI models. Anonymize queries and plans, or consider using privately hosted LLMs for extremely sensitive environments.
- Mutable Data Issues:
EXPLAIN ANALYZEexecutes the query. If your query includes DML (INSERT, UPDATE, DELETE), it will modify your data. Always useEXPLAIN ANALYZEwithSELECTqueries or in a transaction that you roll back.
Next Steps
After mastering this basic workflow, consider exploring these advanced topics:
- Automated Query Review in CI/CD: Integrate this AI-powered analysis into your continuous integration pipeline. Automatically run
EXPLAIN ANALYZEon new or modified queries and have the AI flag potential performance regressions before they hit production. - Fine-tuning AI Models: For highly specific database schemas or common query patterns, fine-tuning a smaller LLM with your own optimized query examples and
EXPLAINplans could yield more accurate and tailored suggestions. - Local LLMs: For maximum data privacy and control, explore running open-source LLMs (e.g., Llama 2, Mistral) locally or on your private cloud infrastructure using frameworks like Ollama or Hugging Face Transformers.
- Advanced PostgreSQL Optimization: Dive deeper into PostgreSQL’s internal workings:
- Statistics: Understand
ANALYZEandVACUUMand how they impact the query planner. - Partitioning: For very large tables, partitioning can drastically improve query performance.
- Connection Pooling: Optimize application-database interaction with tools like PgBouncer.
- Building Custom Tools: Develop a more sophisticated tool around this workflow, perhaps with a web UI, to manage queries, store historical
EXPLAINplans, and track optimization progress.
By integrating AI into our database optimization efforts, we can accelerate the identification of performance bottlenecks, free up valuable DBA time, and build more solid and efficient data-driven applications. Remember, AI is a powerful assistant, but human oversight and critical thinking remain important.
Recommended Reading
Deepen your skills with these highly-rated books. Links go to Amazon — as an affiliate, we may earn a small commission at no extra cost to you.
- SQL Performance Explained by Markus Winand
- High Performance MySQL by Schwartz et al.
- Learning SQL by Alan Beaulieu