Что такое SQL и СУБД

Что такое SQL и СУБД

SQL - это язык, на котором ты задаёшь вопросы базе данных. Типа: «покажи всех пользователей» или «сколько заказов было вчера». База данных в ответ либо отдаёт данные, либо молча смотрит на тебя и говорит: «где WHERE?!». Подключение из Go - через database/sql, из PHP - через PDO.

Четыре свойства транзакций ACID: атомарность, согласованность, изоляция, надёжность

Эти четыре буквы - про транзакции: гарантии, которые база даёт твоим данным. Разберём их подробно в уроке про транзакции и ACID.

Что такое СУБД

СУБД (система управления базами данных) - это программа, которая хранит данные и умеет:

  • сохранять
  • искать
  • обновлять
  • защищать от хаоса

Самые популярные: PostgreSQL, MySQL/MariaDB, SQLite.

SQL - язык. PostgreSQL и MySQL - СУБД. Они похожи, но нюансы есть (как разные «диалекты»).

Чем СУБД отличается от файла с данными

Законный вопрос новичка: зачем целый сервер, если данные можно сложить в JSON-файл? Возьмём самую простую задачу - счётчик просмотров статьи.

data, _ := os.ReadFile("views.json")
var views map[string]int
json.Unmarshal(data, &views)
views["article-1"]++
os.WriteFile("views.json", mustMarshal(views), 0644)
$views = json_decode(file_get_contents('views.json'), true);
$views['article-1']++;
file_put_contents('views.json', json_encode($views));

Код рабочий, пока запрос один. Придут два одновременно - оба прочитают 100, оба запишут 101, один просмотр потерян. Это классический lost update, и он воспроизводится на двух пользователях, а не на миллионе. В базе то же самое пишется одной строкой, и потерять инкремент нельзя:

UPDATE articles SET views = views + 1 WHERE id = 1;

Что СУБД даёт помимо этого, причём без единой строки твоего кода:

  • Конкурентный доступ и блокировки. Пока строку меняют, остальные ждут микросекунды и получают её корректное значение. Руками это lock-файл, который надо не забыть снять, если процесс упал посреди работы.
  • Восстановление после сбоя. PostgreSQL сначала записывает намерение в журнал (WAL, write-ahead log), и только потом меняет данные. Выключили сервер посреди записи - при старте база доигрывает журнал и приходит в согласованное состояние. Файл, оборванный на середине file_put_contents, восстанавливать нечем: получится обрезанный JSON, который даже не распарсится.
  • Поиск без чтения всего. Найти одного пользователя из 10 млн в файле означает прочитать 10 млн записей. В базе есть индексы: несколько обращений к диску вместо полного перебора.
  • Правила рядом с данными. Заказ с несуществующим user_id не вставится, два одинаковых email не пройдут. Это ограничения, и они работают, даже когда в приложении забыли проверку или её обошёл скрипт миграции.
  • Атомарность группы изменений. «Списать у одного, начислить другому» целиком или никак - это транзакции.
Хватит ровно до второго одновременного запроса. Проблема не в объёме данных, а в количестве параллельных писателей, и для неё достаточно двух.

База - это не Excel

Самое частое первое заблуждение: таблица в базе воспринимается как лист Excel. Excel - документ: открыл целиком, посмотрел глазами, поправил ячейку. База - сервис: к ней подключены десятки процессов, каждый видит только то, что запросил, и «открыть файл целиком» никто не может.

Отсюда три следствия, на которых спотыкаются в первую неделю:

  1. Порядок строк не задан. «Строки номер 5» в таблице не существует. Без ORDER BY база вправе вернуть строки в любом порядке, и этот порядок реально меняется после обновления данных - именно так ломаются тесты, которые ждали первую строку на своём месте. Сортировку задаёшь ты, через ORDER BY.
  2. Тип колонки строгий. В ячейку Excel можно положить и число, и текст, и «примерно 5». В колонке INT текста не будет: база откажет с ошибкой. Это раздражает первые пять минут и экономит недели потом.
  3. Формул в ячейках нет. Вычисления живут в запросе, а не в данных. Сумма заказа либо считается в SELECT, либо хранится отдельной колонкой, которую обновляешь ты сам.

Что декларативность даёт на практике

Между твоим запросом и данными стоит планировщик (query planner). Он смотрит на статистику таблицы (сколько там строк, насколько разнообразны значения в колонке) и сам выбирает способ: пройти таблицу целиком или взять индекс, каким методом соединить две таблицы.

Из этого следуют две вещи, которые обычно узнают на проде:

  • Один и тот же запрос выполняется по-разному в разное время. На таблице в 100 строк база проигнорирует индекс, потому что полный проход дешевле; на миллионе строк возьмёт индекс. Поэтому «на локальной базе было быстро» не доказывает ничего: там другая статистика и другой план.
  • Оптимизировать SQL значит менять условия задачи, а не порядок шагов. Ты не можешь приказать «иди по индексу». Ты можешь создать индекс, переписать условие так, чтобы индекс стал применим, обновить статистику через ANALYZE. Что база решила в итоге, показывает EXPLAIN.
Не переписывать запрос наугад, а посмотреть план: `EXPLAIN ANALYZE SELECT ...`. Догадки о том, где узкое место, ошибаются чаще, чем план.

Клиент и сервер: соединение - это ресурс

psql, GUI-клиент, твой Go-сервис - все они клиенты. Сервер PostgreSQL на каждое соединение поднимает отдельный процесс, поэтому соединений не бывает «сколько угодно»: параметр max_connections по умолчанию порядка 100. Отсюда пул соединений в приложении: подключились один раз, переиспользуем. В Go пул встроен прямо в sql.DB - смотри урок про database/sql, в PHP похожую роль играют persistent-подключения PDO.

Ошибка FATAL: sorry, too many clients already почти никогда не означает «нагрузка выросла». Обычно это код, который открывает новое соединение на каждый запрос и не закрывает старое.

SQL как декларативный язык

SQL - это декларативный язык. Это значит: ты говоришь ЧТО ты хочешь, а не КАК это получить.

Например:

SELECT name, email FROM users WHERE age > 18;

Ты просто сказал(а): «Дай мне имена и email людей старше 18». БД сама решает, как это сделать быстрее всего. Что именно она решила, можно подсмотреть - для этого есть EXPLAIN. Ты не пишешь циклы, не индексируешь пальцем память - база разберётся.

Это отличает SQL от Go или PHP, где ты пишешь: «шаг 1, шаг 2, цикл, условие». SQL проще, но менее гибкий.

Типы SQL команд

SQL делится на три группы команд:

DDL (Data Definition Language) - структура. Здесь же задаются правила целостности, про них - урок об ограничениях и ключах:

CREATE TABLE users (id INT, name VARCHAR(100));
ALTER TABLE users ADD COLUMN email VARCHAR(100);
DROP TABLE users;

DML (Data Manipulation Language) - данные:

INSERT INTO users (id, name) VALUES (1, 'Alice');
UPDATE users SET name = 'Bob' WHERE id = 1;
DELETE FROM users WHERE id = 1;

DQL (Data Query Language) - читаем. Этой одной команде посвящён следующий урок про SELECT:

SELECT * FROM users;

Таблицы, строки, столбцы

  • Таблица - набор строк одного типа (например, users)
  • Строка - один объект (один пользователь)
  • Столбец - свойство (имя, email, created_at)

PostgreSQL vs SQLite

На обучение рекомендуем PostgreSQL потому что:

  • Максимально близок к SQL стандарту (знания переносятся на любую БД)
  • Отличная документация
  • На проде обычно именно он
  • Полноценное шифрование и безопасность из коробки

SQLite выбирай если:

  • Нужен быстрый эксперимент без установки
  • Работаешь на мобильном или встроенном устройстве
  • БД небольшая (файл на диске)

Проверка: работает ли БД

Если ты установил(а) PostgreSQL, можешь проверить, работает ли сервис:

# Linux/Mac
sudo systemctl status postgresql

# Или попробуй подключиться к БД
psql -U postgres

Если всё окей, должен вывести приглашение типа:

postgres=#

Выход: \q и Enter.

Для SQLite проще: скачиваешь бинарник, и работает везде. Проверка:

sqlite3 :memory:
sqlite>

Мини-задание

  • Выбери СУБД для обучения: PostgreSQL или SQLite
  • Установи выбранную СУБД (или проверь, есть ли уже)
  • Подключись к БД и убедись, что работает
  • Узнай, как посмотреть список таблиц: для PostgreSQL - \dt, для SQLite - .tables
  • Создай таблицу articles(id INT, views INT), вставь строку и выполни UPDATE articles SET views = views + 1 дважды: убедись, что счётчик равен 2
  • Попробуй вставить текст в колонку INT и прочитай текст ошибки целиком - потом ты будешь узнавать её с полувзгляда
  • Выполни SELECT * FROM articles без ORDER BY, обнови одну строку и повтори запрос: посмотри, изменился ли порядок
  • Посмотри значение лимита соединений: SHOW max_connections;

Зарегистрируйтесь бесплатно, чтобы пройти квиз, вести прогресс.