Байт баз данных: почему строки обрезаются и как это починить
VARCHAR(100) в SQL Server звучит как место для ста символов. Но для столбцов char(n) и varchar(n) число n задаёт длину в байтах. Если столбец использует UTF-8, символы за пределами ASCII занимают несколько байтов.

Поэтому строка из ста кириллических букв может не поместиться, хотя видимых символов в ней ровно сто.
На тестовом стенде такая ошибка часто не проявляется: латиница в ASCII обычно занимает по одному байту на символ. На проде приходят кириллица, эмодзи и символы из других письменностей, и прежнее представление о длине перестаёт работать. Где именно оборвётся строка, зависит от всей цепочки: приложения, драйвера, настроек соединения и правил конкретной СУБД. Иногда запись завершится ошибкой, иногда данные будут преобразованы или усечены по правилам операции.
Как это работает
Байт, символ и видимая пользователю буква — разные единицы измерения. UTF-8 кодирует символы последовательностями от одного до четырёх байтов. Буква из ASCII занимает один байт, многие кириллические буквы занимают два, а некоторые другие символы три или четыре.
В SQL Server char(n) и varchar(n) ограничены числом байтов. Для varchar с UTF-8 такое поведение важно учитывать с SQL Server 2019, когда поддержка UTF-8 появилась для соответствующих сортировок. У nvarchar(n) единица измерения другая: n означает число двухбайтовых кодовых единиц UTF-16. Некоторые символы Unicode в UTF-16 занимают две такие единицы, поэтому число n не всегда равно количеству видимых символов.
В MySQL длина CHAR и VARCHAR задаётся в символах. Это не отменяет ограничений на размер строки таблицы: её суммарное представление не может превышать установленный для MySQL предел в 65 535 байт, причём в расчёте участвуют служебные данные и правила хранения столбцов. Поэтому допустимый размер конкретного VARCHAR зависит не только от его объявления, но и от остальных полей таблицы. Для длинных значений есть типы TEXT, но их выбор тоже требует учитывать ограничения и особенности хранения.
| Параметр | SQL Server | MySQL |
|---|---|---|
CHAR(n) и VARCHAR(n) | n задаёт количество байтов | n задаёт количество символов |
Ограничение VARCHAR | До 8 000 байт; для больших значений предусмотрен varchar(max) | Зависит от кодировки, строки таблицы и ограничений типа |
| Байтовая длина значения | DATALENGTH() | LENGTH() или OCTET_LENGTH() |
| Длина в символах | LEN() | CHAR_LENGTH() |
UTF-8 для обычного varchar | Поддерживается с SQL Server 2019 при подходящей сортировке | Поддерживается, например, набором символов utf8mb4 |
Сравнивая результаты измерений, стоит помнить о деталях функций. В SQL Server LEN() не учитывает конечные пробелы, а DATALENGTH() возвращает размер в байтах и учитывает их. В MySQL LENGTH() и OCTET_LENGTH() показывают байты, а CHAR_LENGTH() — число символов в строке.
Кроме байтов, кодовых единиц и кодовых точек есть ещё графемы: единицы, которые человек воспринимает как отдельные знаки. Буква с комбинируемым диакритическим знаком может состоять из двух кодовых точек, хотя выглядит как один символ. Эмодзи тоже бывают составными последовательностями. Поэтому требования вида «в поле должно помещаться сто символов» нужно уточнять: речь о видимых знаках, кодовых точках, единицах кодировки или байтах.
Например, строка é🙂 в UTF-8 занимает шесть байтов: два для первой буквы и четыре для эмодзи. При этом в ней две кодовые точки. Если ограничить такую строку произвольным числом байтов, граница может пройти внутри многобайтовой последовательности. Но результат зависит от места и способа обрезания. Приложение может получить повреждённую последовательность, строгая проверка при записи может отклонить значение, а преобразование кодировки может заменить неподдерживаемый символ. Универсального правила, по которому СУБД обязательно сохранит невалидный UTF-8, нет.
Для varchar(8000) в SQL Server с UTF-8 предел измеряется байтами, а не видимыми знаками. Сколько символов поместится, зависит от того, сколько байтов занимает каждый из них.Практические детали
Три замера вместо одного
При диагностике полезно одновременно измерить байты, символы и настройки столбца. Один показатель редко объясняет всю картину.
1. Байтовая длина. В SQL Server используйте DATALENGTH(), в MySQL — LENGTH() или OCTET_LENGTH(). Сравните результат с ограничением столбца и проверьте не только короткий пример, но и реальные проблемные значения.
2. Длина в символах. Для MySQL подойдёт CHAR_LENGTH(). В SQL Server можно использовать LEN(), помня, что конечные пробелы в результат не входят. Если нужны точные измерения с пробелами, сопоставляйте результат с DATALENGTH() и учитывайте тип данных и кодировку.
3. Тип, кодировка и сортировка. В MySQL посмотрите определение таблицы через SHOW CREATE TABLE, включая набор символов столбца и таблицы. В SQL Server проверьте тип столбца и его collation. Функция COL_LENGTH('table', 'column') показывает объявленную длину столбца в байтах, но сама по себе не сообщает, как строка преобразуется перед записью.
Расхождение между длиной в байтах и длиной в символах не доказывает, что строка уже обрезана. Оно показывает, что в ней есть символы, занимающие больше одного байта в используемой кодировке. Дальше нужно установить, где именно возникает ограничение.
Где строка может измениться
Столбец базы данных — лишь один участок маршрута. Если значение в таблице короче, чем пользователь отправил, проверяйте этапы по порядку:
- Код приложения. Поле может иметь собственное ограничение длины, а преобразование строки в буфер фиксированного размера может сработать ещё до обращения к базе.
- Драйвер и параметры запроса. Тип и длина параметра, передаваемого в SQL, должны соответствовать ожидаемым данным. Если драйвер или ORM задаёт слишком короткий размер, значение может быть преобразовано или отклонено до выполнения записи. Поведение нужно проверять для конкретного драйвера и настроек, а не считать молчаливое усечение универсальным правилом.
- API и промежуточные форматы. Ограничение может находиться в схеме JSON, валидации REST-запроса, очереди сообщений или другом сервисе. Уточните, чем измеряется длина в этом компоненте: символами, байтами или единицами языка программирования.
- Кодировка соединения и преобразования. Если соединение и столбец используют разные кодировки, данные преобразуются при передаче. Неподдерживаемый символ может вызвать ошибку или замениться другим символом. Точный результат зависит от СУБД, настроек и конкретного преобразования.
- Тип и ограничения столбца. Если значение не помещается в целевой тип, операция может завершиться ошибкой или, в некоторых контекстах, записать укороченное значение. В SQL Server на результат влияют типы источника и назначения, способ выполнения операции и настройки. Для MySQL имеют значение режим SQL и конкретный путь записи.
Сортировка сама по себе не отсекает символы при индексации или сортировке. Она определяет правила сравнения и упорядочивания строк. Потеря или замена символа может произойти раньше или отдельно, если строку преобразуют в другую кодировку или записывают в тип, который не может её представить. Это другой механизм, и диагностировать его нужно отдельно.
Чтобы найти участок, сравните строку до передачи, после сериализации, на входе в сервис и после чтения из базы. Если доступны логи, фиксируйте длину в символах и байтах на каждой границе. Не обязательно сохранять в логах сам текст: для поиска места усечения часто достаточно метаданных и идентификатора запроса.
Почему одного увеличения поля может не хватить
Увеличение VARCHAR(100) до VARCHAR(1000) помогает, если ограничение действительно в столбце и новое объявление совместимо с ограничениями таблицы. Оно не исправит короткий параметр в приложении, ограничение API или ошибку преобразования кодировки до записи.
Переход на NVARCHAR в SQL Server или на TEXT в MySQL тоже не является универсальным исправлением. Тип нужно выбирать под требования к данным, а затем проверить передачу значения до самого столбца. Иначе проблема переместится на другой участок, а ошибку будет сложнее заметить.
Полезная привычка при расследовании: сохранить исходное тестовое значение и сравнить его с результатом чтения из базы. Для воспроизведения включайте в тесты не только латиницу, но и кириллицу, символы из нескольких письменностей, пробелы в конце строки, комбинируемые знаки и эмодзи. Это не исчерпывающий набор, но он быстро показывает, где предположение о «длине символа» расходится с реальной кодировкой.
На что обратить внимание
UTF-8-миграция в SQL Server
При переходе varchar на collation с поддержкой UTF-8 меняется то, как символы представляются в столбце. ASCII-символы остаются однобайтовыми, а многие символы других письменностей занимают больше места. Если прежние данные содержали в основном ASCII, после изменения кодировки новые значения могут потребовать больше байтов, чем старые.
Перед миграцией проверьте типы столбцов, существующие данные и операции преобразования. DATALENGTH() помогает измерить текущие значения, но не предсказывает автоматически длину будущих строк с новым составом символов. Оцените реальные сценарии заполнения, убедитесь, что целевые столбцы вмещают ожидаемые данные, и протестируйте запись и чтение после изменения collation. Если данные преобразуются из другой кодировки, отдельно проверьте, все ли символы представимы в целевом наборе.
Важно различать объявленную длину и длину конкретного значения. Даже если столбец объявлен с запасом, параметр запроса или промежуточное преобразование может иметь более короткий предел. Миграция сортировки меняет правила сравнения и кодирования, но не расширяет автоматически ограничения приложения и драйвера.
Лимит строки таблицы в MySQL
В MySQL длина VARCHAR задаётся в символах, однако при проектировании таблицы учитывают и байтовый размер строки. Для utf8mb4 отдельный символ может занимать до четырёх байтов. Поэтому десять столбцов VARCHAR(200) в наихудшем случае потребуют до 8 000 байт на содержимое, если все символы займут по четыре байта. Это оценка верхней границы для значений, а не утверждение о том, сколько места таблица займёт всегда. В расчёте лимитов также важны служебные данные и особенности хранения.
Индексы не включаются в лимит размера строки таблицы как повторные копии её содержимого. При этом у индексов есть собственные ограничения по размеру ключа и требованиям к кодировке. Если создание индекса не удаётся, это отдельная проблема, её нельзя объяснять превышением лимита строки таблицы.
Для длинных значений можно рассмотреть TEXT, уменьшить избыточные размеры полей или пересмотреть структуру данных. Но перенос поля в TEXT не отменяет необходимости понять, где проходит ограничение: тип параметра, API и логика приложения останутся прежними. Решение зависит от того, как поле читают, обновляют, индексируют и используют в запросах.
Настройки соединения важны не меньше схемы
Даже подходящий столбец не гарантирует, что данные попадут в него без потерь. Клиент передаёт строку в определённой кодировке, сервер интерпретирует её по настройкам соединения, а затем при необходимости преобразует к кодировке столбца. Если на одном из шагов набор символов не поддерживается, возможны ошибка или замена символа. Если длина параметра задана неверно, значение может быть отклонено или изменено до записи.
Поэтому проверяйте не только CHARACTER SET в описании таблицы. Уточните настройки драйвера и соединения, типы параметров в запросе и правила сериализации. Когда одна и та же строка проходит через несколько сервисов, полезно описать для каждого участка ожидаемую кодировку и то, как считается длина. Такая спецификация предотвращает спор, в котором команда приложения считает знаки, а команда базы — байты.
Точечные исправления без этого описания обычно создают новые места для ошибки: где-то увеличивают поле, где-то вручную обрезают строку, а где-то меняют кодировку только для одного соединения. Сам по себе локальный фикс может быть уместен, но он не заменяет проверку всей цепочки. Похожий принцип работает и за пределами разработки: пилотное решение без стандартизации трудно масштабировать, как показывает материал о превращении городских транспортных пилотов в постоянную инфраструктуру.
Итоги
Если строка обрезается, сначала выясните, где это происходит, и только потом меняйте схему. Проверьте определение столбца, измерьте байтовую и символьную длину, посмотрите кодировки на границах и сравните исходное значение с тем, что сохранилось. Если данные верны в приложении, но отличаются после записи, локализуйте первый участок, на котором изменилось значение.
Для MySQL учитывайте, что размер VARCHAR задан в символах, а таблица всё равно подчиняется ограничениям на байтовый размер строки. Для SQL Server помните, что varchar(n) измеряется в байтах, а nvarchar(n) — в кодовых единицах UTF-16. В обоих случаях результат зависит от типа, кодировки, настроек соединения и конкретной операции.
Универсальной длины поля, которая одинаково подходит для всех языков и систем, нет. «Сто символов» нужно превратить в проверяемое требование: какие данные должны проходить, где измеряется их длина и что делать, если ограничение превышено. Тогда ошибка размера поля varchar становится диагностируемой проблемой, а не неожиданностью, которая проявляется только на пользовательских данных.