Типы данных MySQL
Отличия от PostgreSQL
Главное:
- Меньше типов — нет массивов, UUID, JSONB, range.
AUTO_INCREMENTвместоIDENTITY.DATETIME/TIMESTAMP— оба без явной таймзоны (TIMESTAMP хранит UTC, DATETIME — как есть).ENUM— встроенный, но с оговорками.- JSON — не бинарный, индексируется слабее.
Целые числа
| Тип | Размер | Signed | Unsigned |
|---|---|---|---|
TINYINT | 1 байт | -128 … 127 | 0 … 255 |
SMALLINT | 2 | ±32 767 | 0 … 65 535 |
MEDIUMINT | 3 | ±8.4 млн | 0 … 16.7 млн |
INT | 4 | ±2.1 млрд | 0 … 4.3 млрд |
BIGINT | 8 | ±9.2 квинтиллиона | 0 … 18.4 квинтиллиона |
UNSIGNED — только положительные. Полезно для id, счётчиков.
Автоинкремент:
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
Правило:
id—BIGINT UNSIGNED AUTO_INCREMENT.- Флаги —
TINYINT(1)(MySQL не имеет настоящегоBOOLEAN, только алиас наTINYINT(1)). - Кол-во —
INT UNSIGNED.
⚠️ INT(11) — не ограничивает значения, только ширину вывода.
Дробные числа
| Тип | Особенности |
|---|---|
FLOAT | 4 байта, ~7 знаков |
DOUBLE | 8 байт, ~15 знаков |
DECIMAL(M,D) | точно, M — всего цифр, D — после точки |
Деньги — только DECIMAL:
price DECIMAL(10, 2) NOT NULL
-- 12345678.90
⚠️ FLOAT / DOUBLE для денег — потеря точности.
Строки
| Тип | Особенности |
|---|---|
CHAR(n) | фикс. длина, 0–255, добивает пробелами |
VARCHAR(n) | перем. длина, до 65 535 |
TINYTEXT | до 255 байт |
TEXT | до 65 535 байт |
MEDIUMTEXT | до 16 MB |
LONGTEXT | до 4 GB |
BLOB | бинарные (аналоги TEXT) |
VARBINARY | бинарные |
Правило:
CHAR— фикс. (страны, коды).VARCHAR(n)— обычные строки (email, имя).TEXT— длинные (описание, статьи).
Индексы:
VARCHAR— индексируется целиком (или префиксом).TEXT— только префиксный индекс:INDEX (bio(100)).
Дата и время
| Тип | Особенности |
|---|---|
DATE | только дата, 3 байта |
TIME | только время |
DATETIME | дата и время, без таймзоны, 8 байт |
TIMESTAMP | дата и время, в UTC, 4 байта |
YEAR | год |
Ключевое отличие:
DATETIME— хранит как есть.TIMESTAMP— конвертирует в UTC при записи и обратно в таймзону сессии при чтении.
⚠️ TIMESTAMP — диапазон 1970–2038. Для дат вне — только DATETIME.
Правило:
created_at,updated_at—TIMESTAMP.- Даты рождения, будущие события —
DATETIME.
Значения по умолчанию:
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
Что даёт: автообновление без триггеров.
Миллисекунды:
created_at DATETIME(3) -- с точностью до миллисекунд
created_at DATETIME(6) -- микросекунды
BOOLEAN
MySQL не имеет настоящего BOOLEAN.
is_active BOOLEAN
-- на самом деле: TINYINT(1)
Значения:
0— false.1— true.NULL— неизвестно.
Проверка:
SHOW COLUMNS FROM users LIKE 'is_active';
-- tinyint(1)
ENUM
status ENUM('pending', 'paid', 'shipped', 'cancelled') NOT NULL DEFAULT 'pending'
Плюсы:
- Валидация на уровне БД.
- Компактное хранение (1–2 байта).
Минусы:
- Тяжело менять —
ALTER TABLEпересоздаёт. - Нельзя удалить значение просто.
- Порядок важен (для
ORDER BY).
Правило: для изменяемых — lookup-таблица. Для стабильных — ENUM.
SET
Набор значений:
tags SET('php', 'laravel', 'mysql', 'devops')
Хранит до 64 значений в битовой маске.
⚠️ Не нормализовано, редко используется. Лучше — связующая таблица или JSON.
JSON
data JSON
Функции:
-- Достать поле
SELECT data->>'$.theme' FROM users;
SELECT JSON_EXTRACT(data, '$.theme') FROM users;
-- Проверить наличие
SELECT * FROM users WHERE JSON_CONTAINS_PATH(data, 'one', '$.theme');
-- Вложенный поиск
SELECT * FROM users WHERE data->>'$.prefs.lang' = 'ru';
-- Установить
UPDATE users SET data = JSON_SET(data, '$.theme', 'dark') WHERE id = 1;
Индекс (MySQL 8.0.17+):
CREATE INDEX idx_users_theme
ON users ((CAST(data->>'$.theme' AS CHAR(20))));
-- Или виртуальная колонка
ALTER TABLE users
ADD COLUMN theme VARCHAR(20)
GENERATED ALWAYS AS (data->>'$.theme') VIRTUAL;
CREATE INDEX idx_users_theme ON users (theme);
Когда:
- Динамические поля.
- Метаданные.
- Не для бизнес-логики — там колонки.
⚠️ JSON в MySQL слабее, чем JSONB в PostgreSQL. Нет GIN-индексов, нет оператора ?.
BLOB и бинарные
avatar BLOB,
data VARBINARY(255)
⚠️ Файлы — на диск или в S3, не в БД. Исключение — миниатюры, хеши.
Гео-типы
location POINT,
area POLYGON
Индексы:
CREATE SPATIAL INDEX idx_location ON places (location);
Функции:
ST_Distance_Sphere(point1, point2)
ST_Contains(polygon, point)
Пример: таблица пользователей
CREATE TABLE users (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
name VARCHAR(100) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
balance DECIMAL(12, 2) NOT NULL DEFAULT 0.00,
metadata JSON,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_users_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Приведение типов
SELECT CAST('123' AS UNSIGNED);
SELECT CAST(123 AS CHAR);
SELECT CONVERT('2026-01-01', DATE);
⚠️ MySQL приводит типы неявно — источник багов.
WHERE varchar_col = 123 -- приведёт varchar к числу
Правило: типы в сравнении — одинаковые. Не полагаться на неявное приведение.
Размер строки
Лимит 65 535 байт на строку (без TEXT/BLOB).
Проблема:
-- Ошибка, если VARCHAR(20000) + VARCHAR(20000) + ...
CREATE TABLE t (
a VARCHAR(20000) utf8mb4, -- 80 KB
b VARCHAR(20000) utf8mb4 -- 80 KB
);
Решение: TEXT для длинных, вынести в отдельную таблицу.
⚠️ utf8mb4 = 4 байта на символ. VARCHAR(255) = 1020 байт.
Частые ошибки
1. INT(11) думают = 11 цифр:
Это ширина вывода, не ограничение
Решение: для ограничения — CHECK или DECIMAL.
2. FLOAT для денег:
0.1 + 0.2 != 0.3
Решение: DECIMAL(10,2).
3. VARCHAR(255) для emoji:
utf8 (не mb4) режет 4-байтовые символы
Решение: utf8mb4 везде.
4. TIMESTAMP для дат > 2038:
Y2K38 проблема
Решение: DATETIME.
5. AUTO_INCREMENT на INT при большом объёме:
Переполнение на 2.1 млрд
Решение: BIGINT UNSIGNED.
6. TEXT без префикса в индексе:
CREATE INDEX idx ON t (body); -- ошибка
Решение: CREATE INDEX idx ON t (body(100));.
7. ENUM для изменяемых значений:
ALTER TABLE пересоздаёт таблицу
Решение: lookup-таблица.
8. JSON там, где нужны колонки:
Нет FK, слабые индексы
Решение: бизнес-данные — колонки.
Практика
1. Создайте таблицу products:
CREATE TABLE products (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
sku VARCHAR(64) NOT NULL,
name VARCHAR(255) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
stock INT UNSIGNED NOT NULL DEFAULT 0,
attributes JSON,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_products_sku (sku),
KEY idx_products_price (price)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. Вставьте товар с JSON-атрибутами:
INSERT INTO products (sku, name, price, attributes)
VALUES ('SKU001', 'Товар', 100.50, '{"color": "red", "size": "M"}');
3. Запросите по JSON:
SELECT * FROM products WHERE attributes->>'$.color' = 'red';
4. Проверьте тип is_active:
SHOW COLUMNS FROM products LIKE 'is_active';
Итог
id—BIGINT UNSIGNED AUTO_INCREMENT.- Деньги —
DECIMAL. - Строки —
VARCHAR(обычные),TEXT(длинные). - Дата —
TIMESTAMP(created/updated),DATETIME(остальное). - BOOLEAN =
TINYINT(1). - JSON — есть, но слабее PostgreSQL.
- ENUM — для стабильных.
utf8mb4— всегда.INT(11)— не ограничение.
Дальше — создание таблиц.