MySQL — система управления базами данных

MySQL — одна из самых популярных реляционных систем управления базами данных в мире. Она используется на миллионах сайтов и приложений для хранения и обработки данных.

Уровень: начальный 17 разделов ⏱ 2–3 часа

1. Что такое MySQL

MySQL — свободная реляционная СУБД с открытым исходным кодом, разрабатываемая компанией Oracle. Она используется повсеместно:

Клиент-серверная архитектура

MySQL работает по модели «клиент — сервер». Сервер mysqld слушает порт 3306 и обрабатывает запросы от клиентов — консольной утилиты mysql, GUI-инструментов или приложений на Python.

Обе СУБД с открытым кодом. MySQL проще и быстрее для типичных CRUD-задач, а PostgreSQL предлагает расширенную поддержку JSON, пользовательских типов и строгую целостность.

2. Установка MySQL

Скачай MySQL Installer с официального сайта dev.mysql.com.

ОСРекомендация
WindowsMySQL Installer (MSI) — включает Workbench
macOSDMG-пакет или brew install mysql
Linux (Debian/Ubuntu)sudo apt install mysql-server
Выбери тип Developer Default, задай пароль для root и установи MySQL Workbench. Проверь сервер: mysql --version.

3. Подключение и первые команды

Подключись к серверу через консоль:

mysql -u root -p

Система запросит пароль, после чего ты окажешься в интерактивной оболочке MySQL. Вот базовые команды:

SHOW DATABASES;
SELECT VERSION();
SELECT NOW();
STATUS;
QUIT;

Каждая команда завершается точкой с запятой ;. Без неё клиент будет ждать продолжения.

4. Создание базы данных и таблиц

Создай базу данных с правильной кодировкой:

CREATE DATABASE shop
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

Переключись на неё и создай таблицу:

USE shop;

CREATE TABLE products (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  price DECIMAL(10, 2) NOT NULL,
  category ENUM('books', 'electronics', 'clothing') NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

SHOW TABLES;
DESCRIBE products;
utf8mb4 поддерживает все символы Unicode, включая эмодзи. Всегда используй его вместо устаревшего utf8.

5. Типы данных MySQL

ТипРазмерОписание
INT4 байтаЦелые числа от −2 млрд до +2 млрд
BIGINT8 байтОчень большие целые числа
VARCHAR(n)до n символовПеременная строка фиксированной максимальной длины
TEXTдо 64 КБДлинный текст, статьи, описания
DECIMAL(p, s)точныйДробные числа без потери точности (деньги)
DATE3 байтаДата: ГГГГ-ММ-ДД
TIMESTAMP4 байтаДата и время с автоматической меткой
ENUM1-2 байтаЗначение из фиксированного списка
Для денежных сумм всегда используй DECIMAL, а не FLOAT или DOUBLE — те хранят приближённые значения.

6. Работа с данными

INSERT — добавление

INSERT INTO products (name, price, category)
VALUES ('Мышка Logitech', 1590.00, 'electronics'),
       ('Фантастика', 450.00, 'books'),
       ('Футболка Nike', 2990.00, 'clothing');

SELECT — выборка

SELECT * FROM products;

SELECT name, price FROM products
WHERE category = 'electronics';

SELECT * FROM products
WHERE price < 1000
ORDER BY price ASC
LIMIT 10;

SELECT * FROM products
WHERE name LIKE '%мыш%';

UPDATE и DELETE

UPDATE products
SET price = 1290.00
WHERE name = 'Мышка Logitech';

DELETE FROM products
WHERE id = 3;
Всегда используй WHERE в UPDATE и DELETE. Без него изменения затронут ВСЕ строки таблицы.

7. Порядок выполнения запроса и продвинутая выборка

Ключевые слова SQL выполняются строго в таком порядке: SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT.

JOIN — соединение таблиц

Создадим таблицу заказов, связанную с товарами:

CREATE TABLE orders (
  id INT AUTO_INCREMENT PRIMARY KEY,
  product_id INT NOT NULL,
  quantity INT NOT NULL DEFAULT 1,
  order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (product_id) REFERENCES products(id)
);

SELECT orders.id, products.name, orders.quantity, products.price
FROM orders
INNER JOIN products ON orders.product_id = products.id
ORDER BY orders.order_date DESC;

GROUP BY и агрегатные функции

SELECT category, COUNT(*) AS cnt, AVG(price) AS avg_price
FROM products
GROUP BY category
HAVING avg_price > 500
ORDER BY cnt DESC;

8. Пользователи и права

Работать под root в продакшене опасно — полные права могут привести к потере данных. Создавай отдельных пользователей:

CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'Надёжный_Пароль123!';

GRANT SELECT, INSERT, UPDATE ON shop.* TO 'app_user'@'localhost';

FLUSH PRIVILEGES;

SHOW GRANTS FOR 'app_user'@'localhost';
Назначай каждому приложению отдельного пользователя с минимально необходимыми правами. Никогда не выдавай GRANT ALL на продакшене без веских причин.

9. Резервное копирование и работа из Python

Экспорт и восстановление

mysqldump -u root -p shop > shop_backup.sql
mysql -u root -p shop < shop_backup.sql

Подключение из Python

Установи драйвер: pip install mysql-connector-python

Пример: SELECT из Python

import mysql.connector

conn = mysql.connector.connect(
    host='localhost', user='app_user',
    password='Надёжный_Пароль123!', database='shop'
)
cursor = conn.cursor()
cursor.execute('SELECT name, price FROM products WHERE category = %s', ('electronics',))
for row in cursor.fetchall():
    print(f'{row[0]} — {row[1]} руб.')
cursor.close()
conn.close()

Пример: INSERT из Python

import mysql.connector

conn = mysql.connector.connect(
    host='localhost', user='app_user',
    password='Надёжный_Пароль123!', database='shop'
)
cursor = conn.cursor()
sql = 'INSERT INTO products (name, price, category) VALUES (%s, %s, %s)'
data = ('Наушники Sony', 4990.00, 'electronics')
cursor.execute(sql, data)
conn.commit()
print(f'Добавлен товар с id: {cursor.lastrowid}')
cursor.close()
conn.close()
Всегда используй параметризованные запросы (%s-плейсхолдеры), а не форматирование строк. Это защищает от SQL-инъекций.

10. Индексы и производительность запросов

Индекс — это структура данных (в MySQL — B-tree), которая ускоряет поиск по выбранным колонкам. Без индекса сервер сканирует таблицу построчно, с индексом находит строки за отсечки. Индексы стоит ставить на колонки из WHERE, JOIN и ORDER BY.

CREATE INDEX idx_products_category ON products(category);

EXPLAIN SELECT * FROM products
WHERE category = 'electronics' AND price > 1000;
Командой EXPLAIN проверяйте план запроса: колонка type=ALL означает полное сканирование — значит, индекса не хватает.
Каждый индекс замедляет INSERT и UPDATE. Не плодите индексы «на всякий случай»: составной индекс (category, price) эффективнее двух одиночных.

Число строк оценивайте через rows в EXPLAIN. Для чтения с пагинацией используйте OFFSET с индексом по ключу или движок InnoDB с первичным ключом.

11. Транзакции и уровни изоляции

Транзакция объединяет несколько операций в одну: либо все они применяются, либо ни одна. В MySQL транзакции поддерживает движок InnoDB (по умолчанию с 5.5).

START TRANSACTION;
UPDATE accounts SET balance = balance - 500
WHERE user_id = 42 AND balance >= 500;
UPDATE accounts SET balance = balance + 500
WHERE user_id = 17;
COMMIT;  -- или ROLLBACK при ошибке
Проверку balance >= 500 выполняйте внутри транзакции, а не до неё — так вы не получите отрицательный остаток при параллельных списаниях.

12. Хранимые процедуры и триггеры

Хранимая процедура — процедура на SQL, выполняемая на сервере. Она уменьшает сетевой трафик и централизует бизнес-логику. Триггер автоматически срабатывает до или после INSERT, UPDATE, DELETE.

DELIMITER $$
CREATE PROCEDURE update_price
  (IN prod_id INT, IN delta DECIMAL(10,2))
BEGIN
  UPDATE products
  SET price = price + delta
  WHERE id = prod_id;
END$$
DELIMITER ;

CALL update_price(101, 300.00);

CREATE TRIGGER products_updated_at
BEFORE UPDATE ON products
FOR EACH ROW SET NEW.updated_at = NOW();
Триггеры невидимы в приложении и усложняют отладку. Применяйте их для аудита и синхронизации, а основную бизнес-логику держите в коде — иначе поведение базы становится «магией».

13. JSON, полнотекстовый поиск и ошибки новичков

ВозможностьЗапрос
JSON в колонкеWHERE JSON_EXTRACT(doc, '$.name') = 'X'
Full-text индексMATCH(title) AGAINST('список')
Миграция колонкиALTER TABLE ... CHANGE COLUMN

Тип JSON валидирует данные на сервере и хранит их в бинарном виде — это удобнее и быстрее, чем строка с JSON. Для быстрого фильтра по JSON-полю создавайте generated-колонку с индексом.

Теперь ты знаешь основы MySQL: от установки до работы из Python. Рекомендуемый следующий шаг — изучение PostgreSQL для сравнения подходов, или углублённое изучение SQL для сложных запросов.

← Вернуться к курсам

14. MySQL в Docker: воспроизводимая среда

Контейнер решает проблему «на моей машине работало»: версия сервера, кодировка и настройки фиксируются в одном файле. Образ mysql:8 готов к запуску за пару минут, а данные выносите в volume, чтобы они пережили пересоздание контейнера.

docker run --name mysql-dev -d \
  -e MYSQL_ROOT_PASSWORD=root \
  -e MYSQL_DATABASE=shop \
  -p 3306:3306 \
  -v mysql_data:/var/lib/mysql \
  mysql:8

После первого запуска не забудьте сменить пароль root и создать прикладного пользователя с минимальными правами. Для проекта удобнее docker-compose.yml: сервисы приложения и базы поднимаются одной командой, а настройки передаются через переменные окружения, а не захардкоженный конфиг.

Проверьте настройки по умолчанию: в контейнере utf8mb4 включается аргументом --character-set-server=utf8mb4. Иначе русский текст может сохраняться с искажениями.
Не монтируйте каталог данных поверх существующей папки контейнера без копирования — контейнер перезапишет её метаданными образа.

15. Репликация и отказоустойчивость

Репликация создаёт копии данных на других серверах: реплика читает binlog ведущего и воспроизводит те же изменения. Основная польза — отказоустойчивость (при сбое ведущего роль берёт реплика) и разгрузка чтения через реплики, настроенные как только для чтения.

-- на ведущем
CREATE USER 'replica'@'%' IDENTIFIED BY 'replica_pass';
GRANT REPLICATION SLAVE ON *.* TO 'replica'@'%';

-- на реплике
CHANGE REPLICATION SOURCE TO
  SOURCE_HOST='master',
  SOURCE_USER='replica',
  SOURCE_PASSWORD='replica_pass';
START REPLICA;

Статус реплики контролируйте командой SHOW REPLICA STATUS\G: следите за Seconds_Behind_Source. Если оно растёт — реплика не успевает догонять нагрузку, и пора думать о шардировании или оптимизации запросов.

В MySQL 8.4 клон реплики через CLONE позволяет быстро развернуть новый узел без полного дампа данных.

16. Binlog и восстановление на точку во времени

Бинарный журнал записывает все изменения данных. Он нужен не только для репликации, но и для восстановления после сбоя: дамп + последующие транзакции из binlog дают состояние ровно на нужный момент, а не на момент дампа.

# полный дамп
mysqldump --all-databases --single-transaction \
  --master-data=2 > backup_sun.sql

# применение изменений до 14:30
mysqlbinlog --stop-datetime="2026-09-19 14:30:00" \
  binlog.000023 | mysql

Запланируйте бэкапы через cron или встроенный mysqldump-скрипт и храните копии на отдельной машине. Ключевое правило: бэкап без регулярной проверки восстановления — это просто лишний набор файлов.

ТипСкоростьТочность
mysqldumpмедленныйлогический, переносимый
физический (файлы)быстрыйпривязан к версии
binlog + дампсреднийpoint-in-time
Настройте binlog_format=ROW: формат STATEMENT ломается на функциях вроде NOW(), давая другие результаты на репликах.

17. Профилирование: slow log и performance_schema

Главные данные о проблeмных запросах лежат в журнале медленных запросов (slow query log). Включите его и задайте порог: всё, что выполняется дольше секунды, попадает в лог для разбора. Нагрузку при этом замеряет performance_schema.

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SET GLOBAL log_queries_not_using_indexes = ON;

SHOW VARIABLES LIKE 'slow%';

Найдя медленный запрос, снимите план командой EXPLAIN ANALYZE — он показывает фактические затраты по каждой строке. Признаки проблем: полные сканирования (ALL), временная таблица и файловая сортировка. Решение чаще всего простое — создание индекса под условие WHERE.

Собирайте логи медленных запросов в одну панель (через Elasticsearch или платформенный логгер), чтобы видеть тренды по базе, а не по одному серверу.

18. Партиционирование больших таблиц

Когда таблица разрастается до миллиардов строк, проще работать с частью данных, чем со всей. Партиционирование делит таблицу на физически отдельные сегменты по правилу, а оптимизатор отбрасывает неподходящие секции ещё до чтения — это partition pruning.

CREATE TABLE orders (
  id INT NOT NULL,
  created_at DATE NOT NULL,
  amount DECIMAL(10,2),
  PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (YEAR(created_at))
  (PARTITION p2024 VALUES LESS THAN (2025),
   PARTITION p2025 VALUES LESS THAN (2026),
   PARTITION p2026 VALUES LESS THAN (2027));

Архивные секции удаляются мгновенно — отбрасывается целая партиция, а не миллионы строк. Однако ключ партиционирования обязан входить в первичный ключ, а каждый индекс тоже «режется» на части.

Партиционирование не ускоряет одиночные запросы по индексу без фильтра на ключ секции: выигрыш есть только когда запрос затрагивает малую долю секций.
Если задача — просто ускорение, начните с обычных индексов: они проще и работают предсказуемее, чем партиции.

19. InnoDB изнутри: буферный пул и MVCC

Двигатель InnoDB держит самые нужные данные и индексы в памяти — в буферном пуле. Чем больше пул, тем реже MySQL обращается к диску. Классическая настройка для выделенного сервера — 70–80% оперативной памяти; утилизация смотрится по статусным переменным.

SET GLOBAL innodb_buffer_pool_size = 12 * 1024 * 1024 * 1024;
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
# read_requests растёт, а read_physical почти стоит

Дополнительно InnoDB ведёт change buffer для отложенного обновления вторичных индексов, doublewrite buffer против «полузаписей» при падении питания и undo-логи, обеспечивающие многоверсионность (MVCC). Именно undo-логи позволяют читающим запросам видеть старую версию строки, пока транзакция не завершена.

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

20. Миграции схем: Flyway и Liquibase

Ручное изменение структуры БД на проде — источник ночных инцидентов. Инструменты миграций хранят скрипты версиями и применяют по порядку, фиксируя в таблице журналирования, что уже выполнено.

-- V1__create_users.sql
CREATE TABLE users (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  email VARCHAR(255) NOT NULL UNIQUE
);

-- V2__add_status.sql
ALTER TABLE users
  ADD COLUMN status ENUM('active','blocked')
  DEFAULT 'active';
Liquibase удобен в сложных проектах (XML/YAML с условиями), Flyway — для простой цепочки SQL-скриптов. Оба поддерживают MySQL.
Миграции независимы от деплоя кода: сначала обновите схему, потом выкатывайте код — иначе старый код упадёт на новом поле.

21. Целостность: внешние ключи и CHECK

Внешний ключ гарантирует, что ссылка на другую таблицу не «повиснет». Кроме запрета сиротских записей, он управляет каскадами: при удалении родителя движок сам удалит, ограничит или обнулит дочерние строки.

CREATE TABLE orders (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  amount DECIMAL(10,2),
  CONSTRAINT fk_orders_user
    FOREIGN KEY (user_id) REFERENCES users(id)
    ON DELETE RESTRICT
);
ДействиеПоведение при удалении родителя
RESTRICTудаление запрещено, пока есть ссылки
CASCADEдочерние строки удаляются вместе
SET NULLвнешний ключ становится NULL

С MySQL 8.0.16 работают и CHECK-ограничения: они проверяют не связи, а значения строки. Индексы под внешние ключи создаются автоматически, но наличие FK замедляет массовые вставки — на огромных таблицах связи иногда осознанно не заводят.

Архивные таблицы, куда копируются «выключенные» строки, лучше держать без внешних ключей — разрывы ссылок проще обходить миграциями.