Claude Code là công cụ CLI (command-line interface) chạy trực tiếp trong terminal, và cũng có extension tích hợp vào VS Code để bạn vừa code vừa chat với agent ngay trong editor. Khi gắn thêm PostgreSQL MCP server, Claude Code không còn là "trợ lý viết code" thông thường nữa — nó trở thành một đồng nghiệp có thể tự kết nối database, đọc schema thật, chạy query thật, và dựa vào dữ liệu thật để viết code cho bạn thay vì đoán mò theo tên biến hay comment. Bài này mình sẽ đi từ bước cài đặt MCP server cho Claude Code, đến các workflow thực chiến: map schema lạ, sinh SQL phức tạp từ ngôn ngữ tự nhiên, và dùng EXPLAIN ANALYZE để agent tự chẩn đoán query chậm.
Cài đặt và kết nối Postgres MCP vào Claude Code
Claude Code đọc cấu hình MCP server từ file .mcp.json ở root project (project-scoped) hoặc từ config global qua lệnh claude mcp add. Với PostgreSQL, cách nhanh nhất là dùng package @modelcontextprotocol/server-postgres hoặc postgres-mcp (bản có safe mode, mình khuyến nghị cho môi trường có dữ liệu thật).
Cách 1 — thêm qua CLI (tự động ghi vào config, không cần sửa tay):
claude mcp add postgres-dev -- npx -y @modelcontextprotocol/server-postgres \
"postgres://mcp_readonly:StrongPass123@localhost:5432/app_dev"
Cách 2 — khai báo trực tiếp trong .mcp.json ở root repo, để cả team dùng chung config khi checkout project (nhớ không commit password thật, dùng biến môi trường):
{
"mcpServers": {
"postgres-dev": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-postgres",
"postgres://mcp_readonly:${PGPASSWORD}@localhost:5432/app_dev"
]
}
}
}
Sau khi thêm, kiểm tra lại bằng lệnh:
claude mcp list
Nếu chạy trong VS Code, extension Claude Code sẽ tự đọc cùng file .mcp.json, bạn không cần cấu hình riêng lần hai. Mở panel chat của extension, bạn sẽ thấy MCP server postgres-dev xuất hiện trong danh sách tool khả dụng (thường có icon plug/connector). Gõ /mcp trong CLI hoặc mở phần MCP status trong VS Code để xác nhận server đã connect thành công — trạng thái "connected" nghĩa là agent đã có thể gọi các tool như list_schemas, list_tables, query.
Mẹo: Luôn tạo riêng một profile
.mcp.jsoncho từng môi trường (dev/staging) và đặt tên server rõ ràng (postgres-dev,postgres-staging-readonly). Đừng để một session Claude Code vừa có quyền truy cập dev vừa production cùng lúc — rất dễ nhầm khi bạn hỏi nhanh "chạy query này giúp mình" mà quên xem agent đang trỏ vào DB nào.
Để agent tự map schema lạ trước khi viết code ứng dụng
Tình huống rất thường gặp: bạn được giao maintain một service cũ, database có hàng chục bảng, không có ERD, không có doc, chỉ có vài migration file rải rác. Thay vì tự mình chạy \d+ từng bảng trong psql, hãy để agent làm việc "khám phá" đó và tóm tắt lại cho bạn.
Prompt mẫu để agent map toàn bộ schema:
Kết nối MCP postgres-dev, liệt kê toàn bộ table trong schema "public".
Với mỗi table, cho biết:
- Các column quan trọng và data type
- Primary key, foreign key và bảng nó tham chiếu tới
- Index hiện có
Sau đó vẽ lại bằng văn bản (dạng mô tả quan hệ) cách các bảng "orders",
"order_items", "products", "customers" liên kết với nhau.
Lưu kết quả này ra file docs/db-schema-overview.md để team dùng làm tài liệu tham khảo.
Điểm mạnh của cách làm này so với đọc migration file bằng mắt: agent đọc trực tiếp state hiện tại của schema (bao gồm mọi thay đổi tay đã áp bằng ALTER TABLE thủ công mà không có migration tương ứng — tình huống rất phổ biến ở project lâu năm), nên bản tóm tắt luôn khớp với thực tế database, không khớp với "những gì migration file nói nó nên là".
Khi agent đã có bản tóm tắt trong context, bạn có thể hỏi tiếp các câu follow-up mà không cần agent introspect lại từ đầu, ví dụ:
Dựa trên schema đã map, viết cho mình một Sequelize model (hoặc Prisma schema, TypeORM entity —
tuỳ ORM project đang dùng) cho bảng "order_items", đảm bảo đúng kiểu dữ liệu, đúng ràng buộc
NOT NULL và foreign key như trong database thật.
Mẹo: Yêu cầu agent lưu bản tóm tắt schema ra file markdown trong repo (không chỉ giữ trong context của session). Lần sau mở session mới, bạn chỉ cần bảo agent "đọc file docs/db-schema-overview.md trước khi làm việc" — tiết kiệm rất nhiều lượt gọi tool MCP và context window (cửa sổ ngữ cảnh).
Sinh và kiểm chứng câu SQL phức tạp từ ngôn ngữ tự nhiên
Claude Code với Postgres MCP không chỉ viết SQL theo trí nhớ huấn luyện — nó có thể chạy thử ngay câu query trên database thật (ở môi trường bạn cho phép), xem kết quả, rồi tự sửa nếu sai cú pháp hoặc sai logic join. Đây là khác biệt lớn so với việc bạn hỏi ChatGPT một câu SQL rồi tự copy-paste đi test.
Ví dụ prompt cho một query báo cáo nhiều bảng, có điều kiện phức tạp:
Viết query PostgreSQL để tính: với mỗi customer, tổng số order trong 90 ngày gần nhất,
tổng revenue (tính theo order_items.quantity * order_items.unit_price), và average order
value. Chỉ tính order có status = 'completed'. Sắp xếp theo revenue giảm dần, lấy top 20.
Chạy thử query trên postgres-dev để xác nhận kết quả hợp lý trước khi đưa cho mình,
và giải thích ngắn cách bạn xử lý trường hợp customer chưa có order nào.
Agent thường sẽ trả về câu SQL dạng CTE (common table expression) để dễ đọc và debug từng bước:
WITH recent_orders AS (
SELECT o.customer_id, o.id AS order_id
FROM orders o
WHERE o.status = 'completed'
AND o.created_at >= now() - interval '90 days'
),
order_revenue AS (
SELECT ro.customer_id, ro.order_id,
SUM(oi.quantity * oi.unit_price) AS order_total
FROM recent_orders ro
JOIN order_items oi ON oi.order_id = ro.order_id
GROUP BY ro.customer_id, ro.order_id
)
SELECT c.id AS customer_id, c.name,
COUNT(order_revenue.order_id) AS order_count,
COALESCE(SUM(order_revenue.order_total), 0) AS total_revenue,
COALESCE(AVG(order_revenue.order_total), 0) AS avg_order_value
FROM customers c
LEFT JOIN order_revenue ON order_revenue.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY total_revenue DESC
LIMIT 20;
Sau khi có kết quả thật, bạn nên yêu cầu agent đối chiếu vài dòng bằng tay (spot-check) — ví dụ chọn 1 customer, tính tay tổng revenue từ vài order cụ thể rồi so với kết quả query, để chắc chắn logic join và filter đúng, không có hiện tượng nhân bản dòng do join sai (fan-out join) làm revenue bị thổi phồng.
Mẹo: Với mọi query aggregate nhiều bảng, luôn nhờ agent thêm bước "chạy COUNT(*) riêng từng bảng liên quan trước, so sánh với COUNT trong kết quả cuối" — cách này bắt được rất nhiều lỗi fan-out join mà nhìn kết quả cuối không thấy ngay được.
Dùng output EXPLAIN ANALYZE để AI chẩn đoán query chậm
Đây là use case mình thấy giá trị nhất của Postgres MCP với Claude Code: thay vì bạn tự chạy EXPLAIN ANALYZE, đọc plan tree lồng nhau rối mắt, rồi tự suy luận nút nào là bottleneck — bạn giao thẳng việc đó cho agent, agent chạy, đọc, và trả lời bằng ngôn ngữ dễ hiểu kèm đề xuất cụ thể.
Prompt mẫu:
Query này đang chạy 4.2 giây trên production (đã test trên bản đọc read-replica):
SELECT o.id, o.created_at, c.name, c.email
FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.status = 'pending'
AND o.created_at > now() - interval '30 days'
ORDER BY o.created_at DESC;
Chạy EXPLAIN (ANALYZE, BUFFERS) cho query này trên postgres-dev với data giả lập tương tự
kích thước production, giải thích nút nào tốn thời gian nhất, và đề xuất index cụ thể
(kèm câu CREATE INDEX) nếu cần. Nếu đề xuất thêm index, hãy ước lượng luôn tác động tới
tốc độ ghi (write) của bảng orders.
Agent sẽ đọc plan, thường sẽ chỉ ra kiểu vấn đề quen thuộc: Seq Scan trên bảng lớn do thiếu index composite cho (status, created_at), hoặc Sort node tốn RAM do work_mem thấp phải spill ra disk. Một plan điển hình agent phân tích:
Sort (cost=15234.12..15234.62 rows=200 width=64) (actual time=3980.201..3980.350 rows=187 loops=1)
Sort Key: o.created_at DESC
Sort Method: external merge Disk: 4096kB
-> Hash Join (cost=890.00..14200.00 rows=200 width=64) (actual time=12.400..3910.220 rows=187 loops=1)
Hash Cond: (o.customer_id = c.id)
-> Seq Scan on orders o (cost=0.00..13100.00 rows=205 width=24)
(actual time=0.020..3820.100 rows=201 loops=1)
Filter: (status = 'pending'::text AND created_at > (now() - '30 days'::interval))
Rows Removed by Filter: 998201
-> Hash (cost=880.00..880.00 rows=800 width=48)
-> Seq Scan on customers c (...)
Từ plan này, agent thường chỉ ra: Rows Removed by Filter: 998201 nghĩa là Postgres đọc gần 1 triệu dòng chỉ để lọc ra 201 dòng khớp — dấu hiệu rõ ràng thiếu index. Và Sort Method: external merge Disk nghĩa là bước sort phải tràn ra đĩa vì work_mem không đủ — vấn đề tăng work_mem có thể giải quyết tạm, nhưng gốc rễ vẫn là thiếu index để giảm số dòng cần sort từ đầu.
Đề xuất index kèm theo thường là:
CREATE INDEX CONCURRENTLY idx_orders_status_created_at
ON orders (status, created_at DESC)
WHERE status = 'pending';
Lưu ý CONCURRENTLY để không lock bảng khi tạo index trên production, và index kiểu partial (WHERE status = 'pending') giúp index nhỏ hơn nhiều so với index full bảng nếu pending chỉ là một phần nhỏ trong tổng số order.
Mẹo: Luôn yêu cầu agent chạy
EXPLAIN (ANALYZE, BUFFERS)thay vì chỉEXPLAIN ANALYZE— phầnBUFFERScho biết số block đọc từ cache (shared hit) hay từ đĩa (read), rất quan trọng để phân biệt vấn đề "thiếu index" với vấn đề "cache lạnh do server mới restart".
Tips
Vài lưu ý thực chiến khi dùng PostgreSQL MCP với Claude Code CLI và VS Code hàng ngày:
- Bật
postgres-mcp(bản CrystalDBA) thay vì bản MCP tham chiếu cơ bản nếu bạn cần safe mode tự phân loại query nguy hiểm — đặc biệt quan trọng khi làm việc gần production. - Trong VS Code, ghim (pin) panel MCP status để luôn nhìn thấy server nào đang connect — tránh trường hợp bạn tưởng đang hỏi dev database nhưng session cũ vẫn giữ connection tới staging.
- Với schema nhiều hơn 50 bảng, đừng để agent introspect toàn bộ mỗi lần — dùng file tóm tắt schema đã lưu sẵn (xem phần map schema ở trên) để tiết kiệm token và tăng tốc phản hồi.
- Khi agent đề xuất index mới, luôn yêu cầu nó chạy thử trên bản sao dữ liệu giống production (kích thước, phân phối dữ liệu) trước khi bạn apply thật — index tốt trên bảng 1000 dòng có thể vô dụng trên bảng 10 triệu dòng.
- Với query có side effect (UPDATE/DELETE), luôn yêu cầu agent trả về câu
SELECTtương đương trước để bạn xem trước dữ liệu sẽ bị ảnh hưởng, rồi mới cho phép chạy câu ghi dữ liệu thật.
Mẹo: Tạo một file
CLAUDE.mdở root project ghi rõ: MCP server nào trỏ tới môi trường nào, danh sách bảng nhạy cảm cấm truy cập, và quy tắc "luôn hỏi trước khi chạy DDL/DML". Claude Code tự động đọc file này mỗi session, giúp bạn không phải nhắc lại quy tắc an toàn mỗi lần mở terminal mới.