Bài 19: Đọc Hiểu Advanced Execution Plan – JSON Format Và Visualization

"Execution Plan không chỉ là văn bản – hãy khám phá sức mạnh của JSON và công cụ trực quan hóa!"


1. Vấn Đề: Query Phức Tạp Với Execution Plan Khó Hiểu

Scenario:
Bảng transactions (50 triệu dòng) với cấu trúc:

  • transaction_id (PK)

  • user_id

  • amount

  • transaction_date

Query:

SELECT  
  user_id,  
  SUM(amount) AS total_amount,  
  COUNT(*) AS transaction_count  
FROM transactions  
WHERE transaction_date BETWEEN '2023-01-01' AND '2023-12-31'  
GROUP BY user_id  
HAVING SUM(amount) > 1000;

Kết quả: Thời gian thực thi ~45 giây, Execution Plan dạng text khó phân tích.


2. Phân Tích Execution Plan Dạng JSON

Bước 1: Chạy EXPLAIN (ANALYZE, FORMAT JSON) (PostgreSQL)
EXPLAIN (ANALYZE, FORMAT JSON)  
SELECT ...;

Kết quả:

[  
  {  
    "Plan": {  
      "Node Type": "Aggregate",  
      "Strategy": "Sorted",  
      "Partial Mode": "Finalize",  
      "Parallel Aware": false,  
      "Startup Cost": 50000.00,  
      "Total Cost": 70000.00,  
      "Plan Rows": 100000,  
      "Plan Width": 20,  
      "Actual Startup Time": 1000.123,  
      "Actual Total Time": 45000.456,  
      "Actual Rows": 50000,  
      "Actual Loops": 1,  
      "Plans": [  
        {  
          "Node Type": "Sort",  
          "Parent Relationship": "Outer",  
          "Parallel Aware": false,  
          "Startup Cost": 30000.00,  
          "Total Cost": 35000.00,  
          "Plan Rows": 1000000,  
          "Plan Width": 12,  
          "Actual Startup Time": 500.123,  
          "Actual Total Time": 30000.456,  
          "Actual Rows": 1000000,  
          "Actual Loops": 1,  
          "Sort Key": ["user_id"],  
          "Sort Method": "external merge",  
          "Sort Space Used": 500000,  
          "Sort Space Type": "Disk",  
          "Plans": [  
            {  
              "Node Type": "Seq Scan",  
              "Parent Relationship": "Outer",  
              "Parallel Aware": false,  
              "Relation Name": "transactions",  
              "Alias": "transactions",  
              "Startup Cost": 0.00,  
              "Total Cost": 20000.00,  
              "Plan Rows": 1000000,  
              "Plan Width": 12,  
              "Actual Startup Time": 0.012,  
              "Actual Total Time": 10000.123,  
              "Actual Rows": 1000000,  
              "Actual Loops": 1,  
              "Filter": "(transaction_date >= '2023-01-01' 
AND transaction_date <= '2023-12-31')",  
              "Rows Removed by Filter": 4000000  
            }  
          ]  
        }  
      ]  
    }  
  }  
]
Bước 2: Giải Mã JSON Plan
  • Node Type: Loại hoạt động (Aggregate, Sort, Seq Scan).

  • Cost: Chi phí ước lượng (Startup Cost, Total Cost).

  • Actual Time: Thời gian thực thi (Startup, Total).

  • Sort Method: Cách sắp xếp (external merge trên Disk).

  • Filter: Điều kiện lọc dữ liệu.

Phát Hiện Vấn Đề:
  1. Full Scan trên transactions: Tốn 10 giây để quét 50 triệu dòng.

  2. Sort trên Disk: Tốn 30 giây để sắp xếp 1 triệu dòng.

  3. Aggregate: Tốn 5 giây để tổng hợp dữ liệu.


3. Tối Ưu: Sử Dụng Index và Parallel Query

Bước 1: Tạo Index Trên transaction_dateuser_id
CREATE INDEX idx_transactions_date_user ON transactions(transaction_date, user_id);
Bước 2: Kích Hoạt Parallel Query
SET max_parallel_workers_per_gather = 4;
Bước 3: Execution Plan Sau Khi Tối Ưu
[  
  {  
    "Plan": {  
      "Node Type": "Gather",  
      "Parallel Aware": true,  
      "Startup Cost": 1000.00,  
      "Total Cost": 15000.00,  
      "Plan Rows": 50000,  
      "Plan Width": 20,  
      "Actual Startup Time": 200.123,  
      "Actual Total Time": 5000.456,  
      "Actual Rows": 50000,  
      "Actual Loops": 1,  
      "Workers Planned": 4,  
      "Workers Launched": 4,  
      "Plans": [  
        {  
          "Node Type": "HashAggregate",  
          "Parent Relationship": "Outer",  
          "Parallel Aware": true,  
          "Startup Cost": 800.00,  
          "Total Cost": 12000.00,  
          "Plan Rows": 50000,  
          "Plan Width": 20,  
          "Actual Startup Time": 100.123,  
          "Actual Total Time": 3000.456,  
          "Actual Rows": 50000,  
          "Actual Loops": 4,  
          "Group Key": ["user_id"],  
          "Plans": [  
            {  
              "Node Type": "Index Scan",  
              "Parent Relationship": "Outer",  
              "Parallel Aware": true,  
              "Relation Name": "transactions",  
              "Alias": "transactions",  
              "Index Name": "idx_transactions_date_user",  
              "Startup Cost": 0.42,  
              "Total Cost": 10000.00,  
              "Plan Rows": 1000000,  
              "Plan Width": 12,  
              "Actual Startup Time": 0.012,  
              "Actual Total Time": 2000.123,  
              "Actual Rows": 1000000,  
              "Actual Loops": 4,  
              "Index Cond": "(transaction_date >= '2023-01-01' AND 
transaction_date <= '2023-12-31')"  
            }  
          ]  
        }  
      ]  
    }  
  }  
]
Kết Quả:
  • Index Scan thay thế Full Scan: Giảm thời gian quét từ 10 giây → 2 giây.

  • Parallel HashAggregate: 4 workers xử lý song song → Giảm thời gian tổng hợp từ 5 giây → 3 giây.

  • Tổng thời gian: Từ 45 giây5 giây (89% cải thiện).


4. Tổng Kết

Chỉ SốTrước Khi Tối ƯuSau Khi Tối Ưu
Thời Gian Quét10,000 ms2,000 ms
Thời Gian Sort30,000 ms0 ms
Thời Gian Aggregate5,000 ms3,000 ms
Tổng Thời Gian45,000 ms5,000 ms
Phương Pháp ScanSeq ScanIndex Scan

Lý Do Hiệu Quả:

  • Index hỗ trợ lọc và truy cập dữ liệu nhanh.

  • Parallel Query tận dụng đa CPU.


5. Bài Tập Thực Hành

Dataset Mẫu:
CREATE TABLE logs (  
    log_id BIGSERIAL PRIMARY KEY,  
    event_type VARCHAR(50),  
    event_time TIMESTAMP,  
    user_agent TEXT  
);  
-- Insert 20 triệu dòng dữ liệu
Yêu Cầu:
  1. Chạy query:

     SELECT event_type, COUNT(*)  
     FROM logs  
     WHERE event_time BETWEEN '2024-01-01' AND '2024-06-30'  
     GROUP BY event_type  
     HAVING COUNT(*) > 1000;
    
  2. Xuất Execution Plan dạng JSON và phân tích.

  3. Tối ưu query bằng index và parallel query.

Câu Hỏi:
  • Tại sao JSON format lại hữu ích hơn text format trong việc phân tích Execution Plan?

6. Mở Rộng & Thảo Luận

Công Cụ Trực Quan Hóa Execution Plan
  • PEV (PostgreSQL Explain Visualizer):

  • pgAdmin:

    • Hỗ trợ xem Execution Plan dạng đồ họa.
  • DBeaver:

    • Hiển thị plan chi tiết với các thông số I/O và thời gian.
Case Study: Phân Tích I/O và Worker Processes
  • Buffer Usage:

      "Shared Hit Blocks": 5000,  
      "Shared Read Blocks": 1000
    
    • Shared Hit Blocks: Số block đọc từ cache.

    • Shared Read Blocks: Số block đọc từ disk.

  • Worker Processes:

      "Workers Planned": 4,  
      "Workers Launched": 4
    
    • Số lượng workers thực tế được sử dụng.
Edge Case: Khi Nào JSON Plan Không Đủ?
  • Query quá phức tạp: Cần kết hợp với log query và monitoring tools (ví dụ: pg_stat_activity).

  • Vấn đề về tài nguyên: Cần kiểm tra hệ thống (CPU, RAM, Disk I/O).


Kết Luận

JSON format và công cụ trực quan hóa giúp bạn "nhìn thấu" Execution Plan, từ đó tối ưu query hiệu quả hơn. Trong ví dụ này, thời gian query giảm từ 45 giây xuống 5 giây nhờ index và parallel query. Ở bài cuối cùng (Bài 20), chúng ta sẽ bàn về scale database và chuyển sang kiến trúc phân tán khi tối ưu không còn đủ!

👉 Bài Tập Về Nhà: Tải dataset tại đây, sử dụng PEV để phân tích Execution Plan và chia sẻ kết quả!