🗄️ Базы данных
Безопасная работа с PostgreSQL и MySQL через Claude Code. Read-only роль, MCP-инструменты, защита миграций, настройки против OOM при ETL-операциях.
🛡️ Философия: Claude = только чтение
Главное правило: Claude Code имеет доступ к БД только для чтения. Все изменения схемы (миграции) выполняются разработчиком вручную. Это предотвращает случайную потерю данных.
✅ Claude может
- SELECT запросы к любым таблицам
- EXPLAIN ANALYZE для анализа планов
- Просмотр схемы (information_schema)
- Анализ индексов и статистик
- Написать SQL для миграции
- Сгенерировать migration-файл
❌ Claude не может
- INSERT / UPDATE / DELETE данных
- DROP TABLE / TRUNCATE
- ALTER TABLE напрямую
- Запускать миграции (migrate:fresh)
- pg_dump / mysqldump
- Изменять пользователей и права
🐘 PostgreSQL — настройка доступа
Шаг 1: Создать read-only роль
-- Выполнить от суперпользователя:
CREATE ROLE claude_readonly WITH LOGIN PASSWORD 'STRONG_PASSWORD_HERE';
-- Дать доступ к схеме
GRANT CONNECT ON DATABASE my_database TO claude_readonly;
GRANT USAGE ON SCHEMA public TO claude_readonly;
-- Только SELECT на все существующие таблицы
GRANT SELECT ON ALL TABLES IN SCHEMA public TO claude_readonly;
-- И на будущие таблицы
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO claude_readonly;
Шаг 2: Настроить postgres-mcp в .mcp.json
// project/.mcp.json
{
"mcpServers": {
"postgres-pro": {
"command": "uvx",
"args": [
"--from", "postgres-mcp",
"postgres-mcp",
"--access-mode=restricted" // ← Только SELECT, блок DDL на уровне MCP
],
"env": {
"DATABASE_URI": "postgresql://claude_readonly:PASS@host.docker.internal:5432/my_db"
}
}
}
}
Двойная защита: роль
claude_readonly (уровень PostgreSQL) + --access-mode=restricted (уровень MCP). Даже если Claude попытается выполнить UPDATE — PostgreSQL откажет.Шаг 3: Настройки PostgreSQL против OOM
При ETL-операциях с большими объёмами данных PostgreSQL потреблял всю WSL2 память и убивался OOM. Зафиксированные параметры:
-- Применить без рестарта (кроме shared_buffers):
ALTER SYSTEM SET shared_buffers = '4GB'; -- требует рестарта PG
ALTER SYSTEM SET work_mem = '64MB';
ALTER SYSTEM SET max_parallel_workers_per_gather = '1';
SELECT pg_reload_conf(); -- применить без рестарта
| Параметр | Значение | Причина |
|---|---|---|
shared_buffers | 4GB | Не более 25% от WSL2 лимита (38GB). 16GB → OOM |
work_mem | 64MB | Множится на N workers × N processes. Большой = OOM |
max_parallel_workers_per_gather | 1 | При ETL каждый воркер = отдельный work_mem блок |
ETL режим: перед массовым INSERT/UPDATE установить
max_parallel_workers_per_gather=1, после ETL вернуть. Иначе OOM при параллельных операциях.🔄 Миграции — безопасный процесс
Laravel Migrations
// ✅ Правильный процесс:
// 1. Claude ГЕНЕРИРУЕТ migration файл:
// database/migrations/2026_05_08_create_payments_table.php
// 2. Разработчик ПРОВЕРЯЕТ файл вручную
// 3. Разработчик ЗАПУСКАЕТ вручную:
// docker exec backend-1 php artisan migrate
// или через docker MCP:
mcp__docker__docker_exec({ container: "backend-1", command: "php artisan migrate" })
// 4. Перед production — ВСЕГДА pg_dump вручную!
// ❌ ЗАПРЕЩЕНО (hooks заблокируют):
php artisan migrate:fresh // Удалит ВСЕ данные!
php artisan db:wipe // Уничтожит БД!
php artisan migrate:reset // Откатит все миграции!
Alembic (Python)
# ✅ Правильный процесс Alembic:
# 1. Claude СОЗДАЁТ revision файл
alembic revision --autogenerate -m "add_payments_table"
# 2. Разработчик ПРОВЕРЯЕТ versions/*.py
# 3. Claude или разработчик запускает через MCP:
mcp__docker__docker_exec({
container: "python-etl-app-1",
command: "alembic upgrade head"
})
# 4. Откат при проблеме:
# alembic downgrade -1
НИКОГДА не давать Claude выполнять
alembic downgrade base — это откатит все миграции и уничтожит схему. Только -1 (один шаг назад).🐬 MySQL — настройка доступа
Для MySQL/MariaDB — аналогичный подход с read-only пользователем:
-- Создать read-only пользователя MySQL:
CREATE USER 'claude_ro'@'%' IDENTIFIED BY 'STRONG_PASSWORD';
GRANT SELECT, SHOW VIEW ON my_database.* TO 'claude_ro'@'%';
FLUSH PRIVILEGES;
// .mcp.json для MySQL (используем другой MCP или прямой запрос):
{
"mcpServers": {
"mysql": {
"command": "npx",
"args": ["-y", "@benborla29/mcp-server-mysql"],
"env": {
"MYSQL_HOST": "host.docker.internal",
"MYSQL_PORT": "3306",
"MYSQL_USER": "claude_ro",
"MYSQL_PASSWORD": "STRONG_PASSWORD",
"MYSQL_DATABASE": "my_database",
"ALLOW_INSERT_OPERATION": "false",
"ALLOW_UPDATE_OPERATION": "false",
"ALLOW_DELETE_OPERATION": "false"
}
}
}
}
🔍 Типичные запросы через postgres-mcp
Анализ структуры таблицы
-- Через mcp__postgres-pro__query:
-- Структура таблицы
SELECT
column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_name = 'payments'
ORDER BY ordinal_position;
-- Индексы таблицы
SELECT
indexname, indexdef
FROM pg_indexes
WHERE tablename = 'payments';
-- Медленные запросы (pg_stat_statements)
SELECT
query, calls, mean_exec_time, total_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;
EXPLAIN для анализа производительности
-- Анализ плана запроса (только читает, не меняет):
EXPLAIN ANALYZE SELECT *
FROM payments p
JOIN users u ON p.user_id = u.id
WHERE p.status = 'pending'
AND p.created_at > NOW() - INTERVAL '7 days';
-- Это безопасно! EXPLAIN не модифицирует данные
⚡ ETL — правила безопасного импорта
При ETL-пайплайнах Claude Code помогает писать код, но сами операции выполняются в контейнере через MCP:
# Python ETL — правила из CLAUDE.md:
# 1. Тестовая БД — всегда SQLite in-memory:
from sqlalchemy import create_engine
engine = create_engine("sqlite:///:memory:") # Никогда не prod в тестах!
# 2. Батчинг — не более 10K строк за транзакцию:
BATCH_SIZE = 10_000
for i in range(0, len(records), BATCH_SIZE):
batch = records[i:i + BATCH_SIZE]
with session.begin():
session.bulk_insert_mappings(Model, batch)
# 3. Логирование каждого шага:
logger.info(f"ETL step=extract start")
# ... логика ...
logger.info(f"ETL step=extract end count={len(records)}")
# 4. Rollback при ошибке:
except Exception as e:
session.rollback()
etl_errors_table.insert(error=str(e), step="transform")
💾 Резервное копирование перед рискованными операциями
# PostgreSQL дамп ПЕРЕД миграцией (вручную!):
mcp__docker__docker_exec({
container: "my-project-db-1",
command: "pg_dump -U postgres my_db > /tmp/backup_before_migration.sql"
})
# Или через docker (если Bash не заблокирован):
# docker exec my-project-db-1 pg_dump -U postgres my_db > backup.sql
# Полный Caddy backup (включает сертификаты):
# proxy backup
Правило бэкапа: pg_dump должен выполняться разработчиком вручную, не Claude. Hooks блокируют
pg_dump из bash. Если нужен дамп — выполняй ! docker exec ... напрямую в терминале вне Claude Code.