Tối ưu hóa SQL bằng AI: Sử dụng LLM để giải mã Execution Plan

AI tutorial - IT technology blog
AI tutorial - IT technology blog

Bối cảnh & Lý do: Cơn ác mộng PagerDuty lúc 2 giờ sáng

Đó là lúc 2:14 sáng thứ Ba tuần trước khi thông báo cảnh báo hiện lên điện thoại của tôi. CPU của database production bị treo ở mức 99%, và thời gian phản hồi API tăng vọt lên 12 giây. Sau khi xem nhanh danh sách tiến trình, tôi đã tìm ra thủ phạm: một truy vấn lồng nhau khổng lồ trên bảng orders có 45 triệu hàng. Một lập trình viên — có lẽ là tôi của ba tháng trước — đã đẩy nó lên mà không có index phù hợp.

Trong một kịch bản thông thường, tôi sẽ chạy EXPLAIN ANALYZE và nhìn chằm chằm vào một execution plan định dạng JSON dài 500 dòng. Tôi sẽ mất 20 phút để cố gắng hình dung chính xác nơi Sequential Scan đang gây ra nút thắt cổ chai. Nhưng khi bạn thiếu ngủ, sai sót của con người là không thể tránh khỏi. Đây là lúc các Mô hình Ngôn ngữ Lớn (LLM) biến một quy trình thủ công tẻ nhạt thành một quy trình làm việc tinh gọn.

Các bộ tối ưu hóa tiêu chuẩn trong PostgreSQL hoặc MySQL rất giỏi trong việc chọn con đường tốt nhất giữa các index hiện có. Tuy nhiên, chúng hiếm khi gợi ý những index mà lẽ ra bạn nên tạo ngay từ đầu. Bằng cách nạp các execution plan có cấu trúc vào một LLM, chúng ta có thể nhận được các đề xuất ngay lập tức, giàu ngữ cảnh cho các index còn thiếu. Tôi đã triển khai phương pháp này trong môi trường production. Nó giúp giảm chi phí truy vấn ổn định ở mức 85% chỉ trong vài phút thay vì hàng giờ.

Cài đặt: Thiết lập Trợ lý Database AI của bạn

Việc bắt đầu không yêu cầu một bộ công cụ doanh nghiệp cồng kềnh. Chúng ta sẽ sử dụng một script Python nhẹ để kết nối database của bạn với một LLM như GPT-4o hoặc Claude 3.5 Sonnet. Các mô hình này có khả năng phân tích cấu trúc phân cấp của một execution plan JSON một cách đáng ngạc nhiên.

1. Thiết lập môi trường

Bắt đầu bằng cách tạo một môi trường ảo. Việc này giúp giữ cho thiết lập cục bộ của bạn sạch sẽ và đảm bảo bạn có đúng phiên bản của driver OpenAI và Postgres.

# Tạo và kích hoạt virtualenv
python3 -m venv sql-ai-env
source sql-ai-env/bin/activate

# Cài đặt các thư viện phụ thuộc
pip install openai psycopg2-binary python-dotenv

2. Truy cập Database

User database của bạn phải có quyền chạy EXPLAIN. Mặc dù hướng dẫn này sử dụng PostgreSQL, logic này có thể áp dụng hoàn hảo cho MySQL hoặc SQL Server. Bạn cũng sẽ cần một API key tiêu chuẩn từ nhà cung cấp LLM mà bạn chọn.

Cấu hình: Nạp Plan cho AI

Bước đột phá thực sự xảy ra khi bạn ngừng gửi các truy vấn thô cho AI. Nếu bạn chỉ hỏi: “Làm cách nào để tối ưu hóa cái này?”, bạn sẽ nhận được lời khuyên chung chung. Nhưng nếu bạn cung cấp Execution Plan, AI sẽ thấy được sự khó khăn bên trong của engine. Nó xác định chính xác khi nào một Hash Join tiêu tốn quá nhiều bộ nhớ hoặc khi nào một Parallel Seq Scan quét qua một bảng dữ liệu khổng lồ.

1. Script tối ưu hóa

Xây dựng một script tên là optimize.py. Script này xử lý công việc nặng nhọc là trích xuất plan và định dạng nó cho mô hình.

import os
import psycopg2
import json
from openai import OpenAI
from dotenv import load_dotenv

load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def get_execution_plan(query):
    conn = psycopg2.connect(os.getenv("DATABASE_URL"))
    cur = conn.cursor()
    # Chúng ta sử dụng FORMAT JSON để cung cấp dữ liệu có cấu trúc cho AI
    cur.execute(f"EXPLAIN (FORMAT JSON, ANALYZE) {query}")
    plan = cur.fetchone()[0]
    cur.close()
    conn.close()
    return plan

def get_ai_suggestion(query, plan):
    prompt = f"""
    Bạn là một Chuyên gia Quản trị Cơ sở dữ liệu (Senior DBA).
    Hãy phân tích truy vấn SQL này và Execution Plan của PostgreSQL kèm theo.
    Xác định các nút thắt cổ chai như Seq Scan hoặc các node có chi phí cao.
    
    Hãy cung cấp:
    1. Lệnh CREATE INDEX chính xác cần thiết.
    2. Các cách viết lại truy vấn cụ thể để cải thiện hiệu suất.
    
    Truy vấn:
    {query}
    
    Execution Plan:
    {json.dumps(plan, indent=2)}
    """
    
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

# Ví dụ sử dụng
slow_query = "SELECT * FROM orders WHERE status = 'pending' AND created_at > '2023-01-01'"
plan = get_execution_plan(slow_query)
suggestion = get_ai_suggestion(slow_query, plan)
print(suggestion)

2. Tinh chỉnh System Prompt

Kết quả chất lượng cao phụ thuộc vào các ràng buộc cụ thể. Tôi thấy rằng việc hướng dẫn AI “ưu tiên các cột có độ chọn lọc cao (high-cardinality)” hoặc “ưu tiên covering index” sẽ ngăn chặn các gợi ý sai lệch (hallucination). Hãy yêu cầu mô hình chỉ gợi ý index dựa trên các cột xuất hiện trong các mệnh đề WHERE, JOIN, hoặc GROUP BY.

Xác minh & Giám sát: Kiểm chứng kết quả

Việc chạy mù quáng các mã SQL do AI tạo ra trên production là một công thức dẫn đến thảm họa. Quy trình làm việc của tôi luôn bao gồm một bước xác minh bắt buộc trên môi trường staging, nơi chứa một phần dữ liệu đại diện từ production.

1. Kiểm tra “Trước và Sau”

Đo lường hiệu suất trước khi bạn áp dụng bất kỳ thay đổi nào. Ghi lại thời gian thực thi và chỉ số “Total Cost” từ plan. Khi index đã hoạt động, hãy chạy lại EXPLAIN ANALYZE. Bạn muốn thấy plan chuyển từ Seq Scan sang Index Scan. Trong một thử nghiệm gần đây, việc này đã giảm chi phí node từ 145.000 xuống chỉ còn 120.

-- Index do AI đề xuất
CREATE INDEX idx_orders_status_created_at ON orders(status, created_at);

-- Xác minh sự cải thiện
EXPLAIN ANALYZE SELECT * FROM orders WHERE status = 'pending' AND created_at > '2023-01-01';

2. Quản lý việc phình to Index (Index Bloat)

Hãy nhớ rằng index không hề miễn phí. Mỗi index mới đều làm tăng chi phí cho các thao tác INSERTUPDATE. Sau khi AI giúp bạn dập tắt đám cháy tức thời, hãy giám sát việc sử dụng index trong một tuần. Sử dụng truy vấn sau để kiểm tra xem index mới của bạn có thực sự hiệu quả hay không:

SELECT 
    relname AS table_name, 
    indexrelname AS index_name, 
    idx_scan AS times_used
FROM pg_stat_user_indexes 
WHERE indexrelname = 'idx_orders_status_created_at';

Nếu idx_scan vẫn bằng 0 sau vài ngày chạy traffic, hãy xóa nó đi. AI có thể đã quá hăng hái, hoặc các mẫu truy vấn của ứng dụng đã thay đổi.

3. Tự động hóa Pipeline

Thiết lập hiện tại của chúng tôi tích hợp trực tiếp việc này vào pipeline CI/CD. Khi một lập trình viên gửi một PR chứa một truy vấn phức tạp mới, một GitHub Action sẽ chạy EXPLAIN trên một database dev đã được làm sạch dữ liệu nhạy cảm. Sau đó, LLM sẽ bình luận vào PR các gợi ý tối ưu hóa. Điều này ngăn chặn các sự cố lúc 2 giờ sáng ngay từ trước khi chúng được đưa lên production.

Chuyển từ phân tích plan thủ công sang chẩn đoán có sự hỗ trợ của AI giúp giảm thời gian xử lý từ hàng giờ xuống còn hàng giây. Nó cho phép các nhóm kỹ sư tập trung vào kiến trúc cấp cao thay vì phải nheo mắt nhìn vào các sơ đồ cây của các vòng lặp lồng nhau.

Share: