ИТ.03 - 18 - Типы данных MySQL: выбор, объём и отличие от SQLite
Введение
Типы данных — это своего рода контракт между вами и СУБД: какие значения можно хранить в столбце, как они будут сравниваться и сколько места займут. В MySQL важно выбирать типы осознанно: это влияет на корректность данных, скорость запросов, размер резервных копий и удобство дальнейшей работы.
В этой лекции рассмотрим основные типы данных MySQL и типичные ситуации их использования. Примеры выполняются вручную в MySQL Workbench.
Почему типы данных так важны
- Корректность: неправильные значения отсекаются или приводятся по правилам MySQL.
- Производительность: компактные типы быстрее сравниваются и индексируются.
- Объём: типы напрямую влияют на размер таблиц и бэкапов.
- Поведение запросов: тип определяет, как сортируются и сравниваются значения.
Примечание
MySQL ведёт себя иначе, чем SQLite. В SQLite работает «динамическая» типизация, а в MySQL типы строже: значения приводятся по правилам MySQL, и часть некорректных данных может быть отвергнута или преобразована.
Как числа хранятся и откуда берётся размерность
Компьютер хранит числа в двоичной системе: любое значение записывается как последовательность 0 и 1.
Например, десятичное 13 в двоичном виде — это 1101.
Если взять 4 бита и одну и ту же последовательность трактовать по‑разному, получим разные значения:
| Интерпретация | Биты | Значение |
|---|---|---|
| Все биты — число | 1101 | 13 |
| Старший бит — знак | 1101 | -3 |
Во втором варианте старший бит считается битом знака, поэтому 1101 трактуется как отрицательное число.
Почему именно «минус 3», а не «минус 5»? Потому что в компьютерах обычно используется дополнительный код (two's complement) — это способ представления отрицательных чисел через инверсию битов и прибавление 1. Это принято по практическим причинам: арифметика становится проще, один и тот же алгоритм сложения/вычитания работает и для положительных, и для отрицательных чисел, а также не возникает двух разных нулей (как в схеме «+0» и «-0»).
Для 4 бит это выглядит так:
1101как положительное число — это 13.- Чтобы получить отрицательное значение, берём обратный код (
0010) и прибавляем 1 →0011, то есть 3.
Именно поэтому 1101 при таком способе интерпретации означает -3.
Один бит — это 0 или 1, а 1 байт = 8 бит. Значит, 1 байт даёт 2^8 = 256 различных комбинаций.
Если все биты используются только для величины числа, это UNSIGNED — такие числа не бывают отрицательными.
Если старший бит отведён под знак, это SIGNED — диапазон включает и отрицательные, и положительные значения.
В MySQL применяется стандартное представление со знаком, поэтому диапазон у SIGNED почти вдвое меньше:
| Байт | Битов | Диапазон UNSIGNED | Диапазон SIGNED |
|---|---|---|---|
| 1 | 8 | от 0 до 255 | от -128 до 127 |
| 2 | 16 | от 0 до 65 535 | от -32 768 до 32 767 |
Отсюда и «размерность»: каждый дополнительный байт вдвое увеличивает число возможных значений.
Наглядный пример для 3 бит (8 возможных значений):
Это один и тот же «набор позиций», но точка нуля находится в разных местах.
Целочисленные типы
По умолчанию целые числа в MySQL со знаком (SIGNED), то есть поддерживают отрицательные значения. Если отрицательные значения не нужны, выбирают вариант без знака (UNSIGNED) — диапазон положительных чисел увеличивается.
| Тип | Размер | Диапазон SIGNED | Диапазон UNSIGNED |
|---|---|---|---|
TINYINT | 1 байт | от -128 до 127 | от 0 до 255 |
SMALLINT | 2 байта | от -32 768 до 32 767 | от 0 до 65 535 |
MEDIUMINT | 3 байта | от -8 388 608 до 8 388 607 | от 0 до 16 777 215 |
INT / INTEGER | 4 байта | от -2 147 483 648 до 2 147 483 647 | от 0 до 4 294 967 295 |
BIGINT | 8 байт | от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 | от 0 до 18 446 744 073 709 551 615 |
Примеры:
rating TINYINT UNSIGNED -- оценка 1..5
birth_year SMALLINT -- год рождения
city_id MEDIUMINT UNSIGNED -- идентификатор города
user_id INT -- идентификатор пользователя
order_id BIGINT -- большой счётчик/идентификатор
balance INT -- может быть отрицательным
Заметка
Разница между INT и BIGINT — это не просто «диапазон», а и размер хранения. На миллионах строк лишние 4 байта на каждую запись — это десятки мегабайт.
Битовые значения
Иногда нужны не числа, а набор битов (флаги). Каждый бит — это признак «включено/выключено», поэтому один столбец может хранить сразу несколько независимых признаков. Для этого используют BIT(M) — хранит M бит.
| Тип | Пример |
|---|---|
BIT(1) | один флаг: 0 или 1 |
BIT(8) | набор флагов из 8 бит |
Пример:
-- Хранение состояния единичного признака
is_active BIT(1) -- 1 = активен, 0 = неактивен
-- Хранение состояния прав доступа
permissions BIT(3) -- 3 независимых флага
Заметка
Такой набор битов с набором включённых признаков называют битовой маской. Это удобно для прав и переключателей. Например, можно хранить три бита для прав владельца ресурса: чтение, запись, выполнение. Тогда комбинация 101 означает «чтение и выполнение», а 111 — «всё разрешено». Похожий принцип используется в файловых системах (права rwx).
Вещественные числа
Вещественные числа используются для хранения чисел с дробной частью. Они делятся на точные и приближённые.
Точные вещественные числа
Тип хранит число в точном виде, без ошибок округления. Для этого используют DECIMAL(P,S), где P — общее количество знаков, а S — количество знаков после точки.
| Пример | Что означает |
|---|---|
DECIMAL(10,2) | до 10 цифр всего, 2 после точки |
DECIMAL(6,0) | до 6 цифр, без дробной части |
Для DECIMAL(10,2) диапазон значений: от -99 999 999.99 до 99 999 999.99.
Примеры:
price DECIMAL(10,2) -- цена на товар
tax_rate DECIMAL(5,2) -- процент налога
Приближённые вещественные числа
Эти типы хранят число приближённо, с некоторой допустимой погрешностью.
Инфо
В реальных задачах часто нужны очень большие или очень маленькие числа, но при этом не критична их идеальная точность. Для этого существуют типы, которые хранят число в формате с плавающей точкой (аналогично научной записи числа):
Мантисса — это значимая часть числа, а степень основания — это экспонента, поэтому такая форма записи чисел называется экспоненциальной и широко применяется в различных областях науки.
Для чисел, которые хранятся и вычисляются в двоичном виде, основание — это .
Пример записи десятичного числа в двоичном виде (смысл, а не точные биты хранения):
Сначала переводим число в двоичный вид. Целая часть: . Дробная часть , а дробные разряды в двоичной системе идут как , поэтому .
Значит:
Затем нормализуем запись (переносим точку влево, чтобы мантисса была в диапазоне ). Сдвиг точки на 3 позиции влево означает умножение на :
В таком виде число и представляется внутри соответствующих типов: отдельно хранятся знак, мантисса и порядок. Это не строка и не десятичная запись.
Например, для типа FLOAT (32 бита, IEEE 754) число выглядит так:
знак: 0 порядок: 10000010 (130 = 127 + 3) мантисса: 10101000000000000000000 итого: 0 10000010 10101000000000000000000
Почему порядок именно ? В стандарте IEEE 754, который описывает формат хранения таких чисел для FLOAT поле порядка занимает 8 бит. Чтобы уметь хранить и отрицательные, и положительные степени, используют смещение (bias). Для 8 бит смещение считается как . Так как в нормализованной записи , истинный порядок равен , а в поле хранится .
Из‑за такого формата записи часть цифр неизбежно округляется — поэтому и появляется погрешность.
| Тип | Размер | Примерная точность |
|---|---|---|
FLOAT | 4 байта | ~7 значащих цифр |
DOUBLE | 8 байт | ~15 значащих цифр |
Примеры:
-- Хранение данных о физических параметрах
temperature FLOAT -- температура
-- Хранение координат GPS
latitude DOUBLE -- широта
longitude DOUBLE -- долгота
Примечание
Не используйте FLOAT и DOUBLE для финансовых данных: погрешность может накапливаться.
Строковые типы
Строковые типы используются для хранения текста и двоичных данных.
CHAR и VARCHAR
| Тип | Как хранится |
|---|---|
CHAR(N) | фиксированная длина, дополняется пробелами |
VARCHAR(N) | переменная длина |
CHAR удобно для данных фиксированной длины (коды, артикулы), VARCHAR — для строк переменной длины (имена, почта, заголовки).
Совет
Важно помнить: N в CHAR(N)/VARCHAR(N) — это количество символов, а реальный размер в байтах зависит от кодировки (например, utf8mb4 может занимать до 4 байт на символ).
Примеры:
country_code CHAR(2) -- ISO-код страны
sku CHAR(8) -- фиксированный артикул
full_name VARCHAR(255) -- имя и фамилия
email VARCHAR(255) -- почта
Заметка
CHAR(10) всегда занимает место под 10 символов, даже если хранится «ABC».VARCHAR(10) хранит ровно длину строки + небольшой служебный байт.
Большие тексты (TEXT)
TEXT используют для больших текстов.
| Тип | Максимальный размер |
|---|---|
TINYTEXT | до 255 байт |
TEXT | до 65 535 байт |
MEDIUMTEXT | до 16 МБ |
LONGTEXT | до 4 ГБ |
Примеры:
short_note TINYTEXT -- короткая заметка
article TEXT -- статья
blog_post MEDIUMTEXT -- большой пост
manual LONGTEXT -- документация
Большие двоичные данные (BLOB)
BLOB (Binary Large Object) используют для двоичных данных — файлов, архивов, изображений.
Инфо
Принципиальная разница между TEXT и BLOB в том, что TEXT — это текст с учётом кодировки и правил сравнения (collation), а BLOB — байты как есть.TEXT участвует в операциях сравнения и сортировки по правилам языка, а BLOB сравнивается побайтово и не зависит от кодировки.
| Тип | Максимальный размер |
|---|---|
TINYBLOB | до 255 байт |
BLOB | до 65 535 байт |
MEDIUMBLOB | до 16 МБ |
LONGBLOB | до 4 ГБ |
Примеры:
thumbnail TINYBLOB -- миниатюра
file_data BLOB -- файл
archive MEDIUMBLOB -- архив
backup LONGBLOB -- бэкап
Двоичные строки
Иногда нужно хранить не текст, а сырые байты (например, хэши, бинарные идентификаторы, сетевые адреса). Для этого используют двоичные строковые типы. Важно что здесь речь идёт именно о байтах (последовательности 0-255), а не об отдельных битах как в случае с типом BIT.
| Тип | Особенность |
|---|---|
BINARY(N) | фиксированная длина в байтах |
VARBINARY(N) | переменная длина в байтах |
Примеры:
uuid_bin BINARY(16) -- UUID в бинарном виде
hash VARBINARY(64) -- хэш размерностью 64 байта
mac_address BINARY(6) -- MAC-адрес
Дата и время
В MySQL есть отдельная группа календарных типов. Они отличаются диапазоном, форматом и назначением.
| Тип | Формат | Диапазон | Когда использовать |
|---|---|---|---|
DATE | YYYY-MM-DD | от '1000-01-01' до '9999-12-31' | дата без времени |
TIME | HH:MM:SS | от '-838:59:59' до '838:59:59' | время в пределах суток |
DATETIME | YYYY-MM-DD HH:MM:SS | от '1000-01-01 00:00:00' до '9999-12-31 23:59:59' | дата и время без «привязки» к эпохе |
YEAR | YYYY | от 1901 до 2155 | год как отдельное значение |
TIMESTAMP | YYYY-MM-DD HH:MM:SS | от '1970-01-01 00:00:01' UTC до '2038-01-19 03:14:07' UTC | временная метка, удобно для created_at/updated_at |
Примеры:
birthdate DATE -- дата рождения
start_time TIME -- время начала
check_in_date DATETIME -- заезд в гостиницу
check_out_date DATETIME -- выезд
graduation_year YEAR -- год выпуска
-- автоматическое проставление даты создания записи
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
Инфо
CURRENT_TIMESTAMP автоматически подставляет текущее время. Это удобно для полей created_at и updated_at.
Совет
Ключевое различие простыми словами: TIMESTAMP хранит момент времени с учётом часового пояса (может отображаться по‑разному в разных зонах), а DATETIME хранит «как есть» без пересчёта.
Если нужен именно момент времени и синхронизация между серверами — чаще выбирают TIMESTAMP. Если нужна фиксированная дата/время без привязки к зоне — DATETIME.
Примечание
TIMESTAMP принимает строку вида YYYY-MM-DD HH:MM:SS, но при сохранении/чтении учитывает часовой пояс. Поэтому значение может отображаться иначе, если временная зона сессии отличается. Если важно сохранить точное «как введено» — используйте DATETIME.
Кроме того, TIMESTAMP имеет ограничение по диапазону (до 2038 года), поэтому для дальних дат лучше использовать DATETIME.
Логические значения
MySQL не имеет отдельного логического типа: обычно используют TINYINT(1) (или BIT(1) для совсем компактного хранения).
Типы BOOL/BOOLEAN, популярные в других СУБД, MySQL понимает, но внутри всё равно приводит их к TINYINT(1).
is_active TINYINT(1)
где в качестве значений можно указать 0 — ложь, 1 — истина.
Значение NULL
NULL — это не тип данных, а специальное значение «нет данных». Оно означает, что значение неизвестно или отсутствует.
В MySQL NULL работает так же, как в SQLite:
NULLне равен ни одному значению, даже самому себе (NULL = NULLдаётNULL).- Для проверки используется
IS NULL/IS NOT NULL.
middle_name VARCHAR(50) NULL -- отчество может отсутствовать
Специальные типы MySQL, которых нет в SQLite
MySQL поддерживает несколько специальных типов, которые не встречаются в SQLite.
ENUM — одно значение из списка
ENUM ограничивает столбец фиксированным набором значений. Удобно, когда список заранее известен и редко меняется.
status ENUM('new','paid','shipped','cancelled') DEFAULT 'new'
По сути значения ENUM — это строковый список (как VARCHAR), просто с жёстким набором допустимых вариантов.
Совет
Если список значений меняется часто, лучше использовать отдельную таблицу-справочник.
SET — несколько значений из списка
SET похож на ENUM, но позволяет хранить несколько значений сразу (множественный выбор).
permissions SET('read','write','execute') DEFAULT 'read'
Фактически это тоже строковые значения (как VARCHAR), но с возможностью хранить сразу несколько элементов из списка.
Совет
SET может быть удобен для небольших наборов признаков, но при сложной логике обычно проще хранить отдельные таблицы связей.
JSON — структурированные данные
Тип JSON позволяет хранить вложенные структуры (объекты и массивы).
metadata JSON
Пример значения:
{"size":"L","color":"blue","tags":["sale","winter"]}
Геоданные
Пространственные типы (GEOMETRY, POINT и др.) нужны для координат и гео‑запросов.
location POINT -- координата точки (долгота, широта)
route LINESTRING -- маршрут (набор точек)
area POLYGON -- границы области
shape GEOMETRY -- универсальный контейнер для геометрии
-- примеры значений (WKT-формат)
POINT(37.6176 55.7558) -- Москва: долгота, широта
LINESTRING(37.60 55.75, 37.62 55.76) -- отрезок пути
POLYGON((37.60 55.75, 37.63 55.75, 37.63 55.77, 37.60 55.77, 37.60 55.75)) -- контур
GEOMETRYCOLLECTION(POINT(37.61 55.75), LINESTRING(37.60 55.75, 37.62 55.76)) -- разные объекты вместе
Как MySQL ведёт себя при неверном типе
Если попытаться записать данные «не того типа», MySQL может:
- преобразовать значение (например, строку
'123'в число123), - округлить или обрезать значение,
- заменить на значение по умолчанию,
- выдать ошибку (в строгом режиме).
Пример:
CREATE TABLE demo_types (
id INT AUTO_INCREMENT PRIMARY KEY,
qty INT NOT NULL,
price DECIMAL(10,2) NOT NULL
);
INSERT INTO demo_types (qty, price) VALUES
('10', '99.95'), -- корректно: строки приведутся к числам
('abc', '10.5'), -- qty может стать 0 или вызвать ошибку (зависит от режима)
(5, '10.567'); -- цена будет округлена до 10.57
Строгий режим проверки (sql_mode)
В MySQL есть режимы проверки, которые задаются параметром sql_mode. Они определяют, как СУБД реагирует на ошибки данных и нарушения правил.
Часто встречаются такие режимы:
STRICT_TRANS_TABLESилиSTRICT_ALL_TABLES— строгая проверка данныхNO_ZERO_DATEиNO_ZERO_IN_DATE— запрет некорректных датERROR_FOR_DIVISION_BY_ZERO— ошибка при делении на нольONLY_FULL_GROUP_BY— строгие требования кGROUP BY
В большинстве современных установок MySQL 8 по умолчанию включён строгий режим (обычно STRICT_TRANS_TABLES) и несколько дополнительных правил вроде ONLY_FULL_GROUP_BY и NO_ZERO_DATE. Точный набор можно посмотреть так:
SELECT @@sql_mode;
Это важно не только для типов данных, но и для требований к ключам и ограничениям. В дальнейшем мы ещё будем сталкиваться с этим поведением.
Почему выбор типа влияет на скорость и размер
Представим 1 000 000 строк:
INTзанимает 4 байта,BIGINT— 8 байт.- если хранить
ageкакVARCHAR(255), таблица раздуется в разы. - большие строки и
TEXTухудшают скорость индексов и увеличивают бэкапы.
Даже при одинаковой логике приложение может работать медленнее из-за неправильного типа.
Примеры корректного и некорректного выбора типов
-- Плохо: возраст хранится как строка
age VARCHAR(255)
-- Плохо: деньги в FLOAT дают погрешности
price FLOAT
-- Плохо: фиксированный код хранится как переменная строка
country_code VARCHAR(10)
-- Плохо: дата/время хранится текстом
created_at VARCHAR(50)
-- Плохо: фиксированный набор значений хранится как текст
status VARCHAR(20)
-- Плохо: структурированные данные в TEXT без JSON-структуры
attributes TEXT
-- Плохо: двоичные данные в текстовом поле
avatar TEXT
-- Плохо: бинарный идентификатор в строке
uuid_bin VARCHAR(36)
-- Плохо: логический флаг как большое целое
is_active INT
-- Хорошо: компактный беззнаковый целый
age TINYINT UNSIGNED
-- Хорошо: точный тип для денег
price DECIMAL(10,2)
-- Хорошо: фиксированный код из 2 символов
country_code CHAR(2)
-- Хорошо: дата/время с автоматическим временем
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
-- Хорошо: список допустимых значений
status ENUM('new','paid','shipped','cancelled')
-- Хорошо: структурированные данные
attributes JSON
-- Хорошо: двоичные данные как BLOB
avatar BLOB
-- Хорошо: бинарный идентификатор фиксированного размера
uuid_bin BINARY(16)
-- Хорошо: логический флаг
is_active TINYINT(1)
Самопроверка
Практические задания
Задание 1. Выбор типов
Подберите типы данных для полей (таблицу создавать не нужно):
email— электронная почтаrating— рейтинг фильма от 1 до 5bio— текстовое описание пользователяlast_login— дата и время входаpreferences— набор настроек в формате JSONcountry_code— код страны (2 символа)
email VARCHAR(255)
rating TINYINT UNSIGNED
bio TEXT
last_login DATETIME
preferences JSON
country_code CHAR(2)
Задание 2. Таблица товаров
Вы разрабатываете мини‑каталог товаров интернет‑магазина. Создайте таблицу products_demo и добавьте в неё 3 записи.
Поля (тип подберите самостоятельно):
id— уникальный идентификатор товара (PK, автоинкремент)title— название товара (строка переменной длины)price— цена товара (точное число с 2 знаками после запятой)quantity— остаток на складе (целое неотрицательное)created_at— дата и время добавления записи (по умолчанию текущее)
Данные для вставки (ориентируйтесь на них при выборе типов):
INSERT INTO products_demo (title, price, quantity, created_at) VALUES
('Ноутбук', 89990.00, 5, '2025-09-01 10:00:00'),
('Клавиатура', 4990.50, 20, '2025-09-01 12:30:00'),
('Монитор', 29990.99, 7, '2025-09-02 09:15:00');
CREATE TABLE products_demo (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL,
quantity INT UNSIGNED NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Задание 3. Пользователи и настройки
Нужно хранить учетные записи пользователей и их настройки. Создайте таблицу users_demo и добавьте в неё 3 записи.
Поля (тип подберите самостоятельно):
user_id— уникальный идентификатор пользователя (PK, автоинкремент)email— электронная почта (строка переменной длины)country_code— код страны (ровно 2 символа)birthdate— дата рождения (без времени)status— статус учётной записи (ограниченный список значений)permissions— набор прав пользователя (несколько значений из списка)settings— настройки в виде структурированных данныхis_active— активность учётной записи (логический флаг 0/1)
Данные для вставки (ориентируйтесь на них при выборе типов):
INSERT INTO users_demo (email, country_code, birthdate, status, permissions, settings, is_active) VALUES
('anna@example.com', 'RU', '2001-05-10', 'active', 'read,write', '{"theme":"dark","lang":"ru"}', 1),
('mark@example.com', 'US', '1999-11-03', 'pending', 'read', '{"theme":"light"}', 1),
('olga@example.com', 'KZ', '2002-02-21', 'blocked', 'read,execute', '{"notifications":false}', 0);
CREATE TABLE users_demo (
user_id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) NOT NULL,
country_code CHAR(2),
birthdate DATE,
status ENUM('active','blocked','pending') DEFAULT 'pending',
permissions SET('read','write','execute') DEFAULT 'read',
settings JSON,
is_active TINYINT(1) NOT NULL DEFAULT 1
);
Задание 4. Файлы и хранилище
Нужно хранить файлы и их свойства (тип, размер, контрольная сумма, превью). Создайте таблицу files_demo и добавьте в неё 3 записи.
Поля (тип подберите самостоятельно):
file_id— уникальный идентификатор файла (PK, автоинкремент)file_name— имя файла (строка переменной длины)mime_type— тип содержимого (строка видаtype/subtype)size_bytes— размер в байтах (большое неотрицательное число)checksum— контрольная сумма (фиксированное число байт)preview— бинарные данные превью (сырые байты)uploaded_at— дата и время загрузкиflags— служебные флаги (3 независимых бита)
Данные для вставки (ориентируйтесь на них при выборе типов):
INSERT INTO files_demo (file_name, mime_type, size_bytes, checksum, preview, uploaded_at, flags) VALUES
('report.pdf', 'application/pdf', 154320, 0xA1B2C3D4A5B6C7D8A9B0C1D2E3F4A5B6A7B8C9D0E1F2A3B4C5D6E7F8A9B0, 0x25504446, '2025-09-03 14:20:00', b'101'),
('photo.jpg', 'image/jpeg', 84567, 0x00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF, 0xFFD8FFE0, '2025-09-03 15:10:00', b'011'),
('archive.zip', 'application/zip', 2048000, 0xFFEEDDCCBBAA99887766554433221100FFEEDDCCBBAA99887766554433221100, 0x504B0304, '2025-09-04 09:05:00', b'001');
CREATE TABLE files_demo (
file_id BIGINT AUTO_INCREMENT PRIMARY KEY,
file_name VARCHAR(255) NOT NULL,
mime_type VARCHAR(100) NOT NULL,
size_bytes BIGINT UNSIGNED NOT NULL,
checksum VARBINARY(32),
preview BLOB,
uploaded_at DATETIME NOT NULL,
flags BIT(3)
);