← Назад к вопросам

Насколько хорошо владеешь PostgreSQL

1.0 Junior🔥 191 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Мой уровень владения PostgreSQL

Я имею глубокое знание PostgreSQL на уровне Senior/Lead разработчика. Использовал его во всех production приложениях и решал сложные архитектурные задачи.

Уровень компетенции: Senior

Я работал с PostgreSQL на приложениях, обрабатывающих миллионы запросов в день, с требованиями высокой доступности и консистентности.

Основные операции

1. Подключение и базовые запросы

-- Подключение
psql -U user -d database -h localhost

-- Создание таблицы
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) NOT NULL UNIQUE,
  name VARCHAR(255),
  age INT CHECK (age >= 0),
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- Основные операции
SELECT * FROM users WHERE age > 18;
INSERT INTO users (email, name, age) VALUES ('john@example.com', 'John', 30);
UPDATE users SET name = 'Jane' WHERE id = '123';
DELETE FROM users WHERE age < 18;

2. Индексирование (критическое для production)

-- Простой индекс
CREATE INDEX idx_users_email ON users(email);

-- Составной индекс
CREATE INDEX idx_users_status_created ON users(status, created_at DESC);

-- Уникальный индекс
CREATE UNIQUE INDEX idx_users_email_unique ON users(email);

-- Partial index (для фильтрации)
CREATE INDEX idx_active_users ON users(id) WHERE deleted_at IS NULL;

-- GIN индекс для полнотекстового поиска
CREATE INDEX idx_products_search ON products USING GIN(to_tsvector('english', description));

-- BRIN индекс для временных рядов
CREATE INDEX idx_events_time ON events USING BRIN(created_at);

-- Анализ индекса
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'john@example.com';

-- Просмотр индексов
SELECT * FROM pg_indexes WHERE tablename = 'users';

-- Удаление неиспользуемых индексов
SELECT schemaname, tablename, indexname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0;

3. Транзакции и ACID

BEGIN;

UPDATE users SET balance = balance - 100 WHERE id = 'user1';
UPDATE users SET balance = balance + 100 WHERE id = 'user2';

COMMIT;  -- или ROLLBACK если ошибка

-- Уровни изоляции
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
-- операции
COMMIT;

-- Savepoints
BEGIN;
UPDATE users SET status = 'active';
SAVEPOINT sp1;
UPDATE orders SET status = 'processed';  -- Может ошибиться
ROLLBACK TO sp1;  -- Откатиться к point
COMMIT;  -- Применить первый UPDATE

4. JOINs и отношения

-- Внутреннее соединение
SELECT u.name, o.total
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

-- Левое соединение
SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;

-- Несколько JOIN
SELECT u.name, p.title, c.text
FROM users u
INNER JOIN posts p ON u.id = p.user_id
LEFT JOIN comments c ON p.id = c.post_id
WHERE u.status = 'active';

-- Self-join (иерархия)
SELECT e1.name as employee, e2.name as manager
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.id;

5. Aggregation и GROUP BY

-- Основное группирование
SELECT status, COUNT(*) as count
FROM orders
GROUP BY status;

-- С условием
SELECT status, SUM(total) as total_revenue
FROM orders
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY status
HAVING SUM(total) > 10000;

-- Window functions (мощные инструменты)
SELECT
  id,
  name,
  salary,
  AVG(salary) OVER (PARTITION BY department) as dept_avg,
  RANK() OVER (PARTITION BY department ORDER BY salary DESC) as rank,
  ROW_NUMBER() OVER (ORDER BY salary DESC) as row_num,
  LEAD(salary) OVER (ORDER BY hire_date) as next_salary
FROM employees;

-- LAG и LEAD для сравнения рядов
SELECT
  user_id,
  date,
  revenue,
  LAG(revenue) OVER (PARTITION BY user_id ORDER BY date) as prev_revenue,
  revenue - LAG(revenue) OVER (PARTITION BY user_id ORDER BY date) as change
FROM daily_stats;

6. Миграции и Goose

# Создание миграции
goose create add_users_table sql

# Файл миграции: 00001_add_users_table.sql
-- +goose Up
CREATE TABLE users (
  id UUID PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- +goose Down
DROP TABLE users;

# Выполнение миграций
goose up
goose down  # Откатить последнюю
goose status

7. Оптимизация запросов

-- EXPLAIN для анализа запроса
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM users WHERE email = 'john@example.com';

-- Видеть план выполнения
EXPLAIN SELECT u.*, COUNT(o.id)
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;

-- Плохой запрос: N+1
SELECT * FROM users;  -- 100 запросов
-- затем для каждого user:
SELECT * FROM orders WHERE user_id = ?;  -- 100 раз

-- Хороший запрос: JOIN
SELECT u.*, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;

8. Полнотекстовый поиск

-- Создание GIN индекса
CREATE INDEX idx_posts_search ON posts USING GIN(
  to_tsvector('english', title || ' ' || content)
);

-- Поиск
SELECT title, content
FROM posts
WHERE to_tsvector('english', title || ' ' || content) @@ plainto_tsquery('english', 'typescript nodejs')
ORDER BY ts_rank(to_tsvector('english', title || ' ' || content), 
                  plainto_tsquery('english', 'typescript nodejs')) DESC;

9. Функции и процедуры

-- Функция на PL/pgSQL
CREATE FUNCTION get_user_with_posts(user_id UUID)
RETURNS TABLE (
  user_id UUID,
  email VARCHAR,
  post_count INT
) AS $$
BEGIN
  RETURN QUERY
  SELECT u.id, u.email, COUNT(p.id)::INT
  FROM users u
  LEFT JOIN posts p ON u.id = p.user_id
  WHERE u.id = user_id
  GROUP BY u.id;
END;
$$ LANGUAGE plpgsql;

-- Вызов
SELECT * FROM get_user_with_posts('123'::UUID);

10. Типы данных

-- UUID вместо INT для id
ALTER TABLE users ADD COLUMN id UUID PRIMARY KEY DEFAULT gen_random_uuid();

-- JSONB для структурированных данных
CREATE TABLE user_settings (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL,
  settings JSONB DEFAULT '{}'::jsonb
);

INSERT INTO user_settings (user_id, settings)
VALUES ('123'::UUID, '{"theme": "dark", "notifications": true}'::jsonb);

-- Запрос JSONB
SELECT settings->>'theme' as theme FROM user_settings WHERE user_id = '123';

-- Arrays
CREATE TABLE posts (
  id UUID PRIMARY KEY,
  tags TEXT[] DEFAULT ARRAY[]::TEXT[]
);

SELECT * FROM posts WHERE 'typescript' = ANY(tags);

-- ENUM для статусов
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'completed', 'cancelled');

CREATE TABLE orders (
  id UUID PRIMARY KEY,
  status order_status DEFAULT 'pending'
);

11. Производительность

Query optimization:

  • Всегда используй индексы на columns, которые фильтруешь
  • Используй EXPLAIN для анализа плана выполнения
  • Избегай N+1 запросов
  • Используй batch операции вместо одного за раз

Connection pooling:

  • PgBouncer для управления connections
  • PostgreSQL может обработать только определённое количество connections
  • В high-load нужен пулер

Мониторинг:

-- Долгие запросы
SELECT pid, usename, query, query_start
FROM pg_stat_activity
WHERE state = 'active' AND query NOT LIKE '%pg_stat_activity%'
ORDER BY query_start;

-- Размер таблиц
SELECT tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename))
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;

-- Индексы которые не используются
SELECT indexname FROM pg_indexes WHERE schemaname = 'public'
EXCEPT
SELECT indexname FROM pg_stat_user_indexes;

12. Практический пример: High-Load приложение

-- Денормализация для скорости чтения
CREATE TABLE user_stats (
  user_id UUID PRIMARY KEY,
  post_count INT DEFAULT 0,
  follower_count INT DEFAULT 0,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Trigger для обновления stats
CREATE OR REPLACE FUNCTION update_user_stats()
RETURNS TRIGGER AS $$
BEGIN
  UPDATE user_stats
  SET post_count = (SELECT COUNT(*) FROM posts WHERE user_id = NEW.user_id),
      updated_at = CURRENT_TIMESTAMP
  WHERE user_id = NEW.user_id;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER posts_insert_trigger
AFTER INSERT ON posts
FOR EACH ROW
EXECUTE FUNCTION update_user_stats();

Мой опыт на production

Проблемы, которые решал:

  1. Slow queries — анализ через EXPLAIN, добавление индексов
  2. Connection exhaustion — внедрение PgBouncer
  3. Bloat таблиц — регулярная VACUUM и ANALYZE
  4. Deadlocks — правильный order of operations в транзакциях
  5. Масштабирование — replication и failover

Размеры баз данных:

  • От 100 MB в development
  • До 500 GB в production
  • С миллионами записей в день

Уровень владения

Знаю на отлично:

  • SQL синтаксис и оптимизация
  • Индексирование и query planning
  • Транзакции и ACID
  • Window functions и aggregation
  • Миграции и versioning
  • Мониторинг и performance tuning

Неплохо знаю:

  • Replication и HA
  • Partitioning для очень больших таблиц
  • Плагины и расширения

Что бы улучшил:

  • Deeper в logical replication
  • Advanced security features
  • Sharding стратегии

Best Practices на production

  1. Всегда используй UUID для id — не INT
  2. Индексируй правильно — составные индексы для часто используемых фильтров
  3. VACUUM и ANALYZE — регулярно автоматизировать
  4. Backup strategy — минимум daily backups, желательно непрерывная репликация
  5. Мониторинг — query performance, connections, disk space
  6. Миграции — используй Goose или Flyway, не hand-crafted
  7. Connection pooling — обязателен для production

Мой уровень владения PostgreSQL: Senior / Lead — я могу спроектировать и оптимизировать БД архитектуру для high-load приложений.