Что такое SQL и СУБД
Что такое SQL и СУБД
SQL - это язык, на котором ты задаёшь вопросы базе данных. Типа: «покажи всех пользователей» или «сколько заказов было вчера». База данных в ответ либо отдаёт данные, либо молча смотрит на тебя и говорит: «где WHERE?!». Подключение из Go - через database/sql, из PHP - через PDO.
Эти четыре буквы - про транзакции: гарантии, которые база даёт твоим данным. Разберём их подробно в уроке про транзакции и ACID.
Что такое СУБД
СУБД (система управления базами данных) - это программа, которая хранит данные и умеет:
- сохранять
- искать
- обновлять
- защищать от хаоса
Самые популярные: PostgreSQL, MySQL/MariaDB, SQLite.
Чем СУБД отличается от файла с данными
Законный вопрос новичка: зачем целый сервер, если данные можно сложить в 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 - документ: открыл целиком, посмотрел глазами, поправил ячейку. База - сервис: к ней подключены десятки процессов, каждый видит только то, что запросил, и «открыть файл целиком» никто не может.
Отсюда три следствия, на которых спотыкаются в первую неделю:
- Порядок строк не задан. «Строки номер 5» в таблице не существует. Без
ORDER BYбаза вправе вернуть строки в любом порядке, и этот порядок реально меняется после обновления данных - именно так ломаются тесты, которые ждали первую строку на своём месте. Сортировку задаёшь ты, через ORDER BY. - Тип колонки строгий. В ячейку Excel можно положить и число, и текст, и «примерно 5». В колонке
INTтекста не будет: база откажет с ошибкой. Это раздражает первые пять минут и экономит недели потом. - Формул в ячейках нет. Вычисления живут в запросе, а не в данных. Сумма заказа либо считается в
SELECT, либо хранится отдельной колонкой, которую обновляешь ты сам.
Что декларативность даёт на практике
Между твоим запросом и данными стоит планировщик (query planner). Он смотрит на статистику таблицы (сколько там строк, насколько разнообразны значения в колонке) и сам выбирает способ: пройти таблицу целиком или взять индекс, каким методом соединить две таблицы.
Из этого следуют две вещи, которые обычно узнают на проде:
- Один и тот же запрос выполняется по-разному в разное время. На таблице в 100 строк база проигнорирует индекс, потому что полный проход дешевле; на миллионе строк возьмёт индекс. Поэтому «на локальной базе было быстро» не доказывает ничего: там другая статистика и другой план.
- Оптимизировать SQL значит менять условия задачи, а не порядок шагов. Ты не можешь приказать «иди по индексу». Ты можешь создать индекс, переписать условие так, чтобы индекс стал применим, обновить статистику через
ANALYZE. Что база решила в итоге, показывает EXPLAIN.
Клиент и сервер: соединение - это ресурс
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;