Nvarchar max сколько символов
При создании таблицы для всех ее столбцов необходимо указать определенный тип данных. Тип данных определяет, какие значения могут храниться в столбце, сколько они будут занимать места в памяти.
Язык T-SQL предоставляет множество различных типов. В зависимости от характера значений все их можно разделить на группы.
Числовые типы данных
- BIT : хранит значение от 0 до 16. Может выступать аналогом булевого типа в языках программирования (в этом случае значению true соответствует 1, а значению false — 0). При значениях до 8 (включительно) занимает 1 байт, при значениях от 9 до 16 — 2 байта.
- TINYINT : хранит числа от 0 до 255. Занимает 1 байт. Хорошо подходит для хранения небольших чисел.
- SMALLINT : хранит числа от –32 768 до 32 767. Занимает 2 байта
- INT : хранит числа от –2 147 483 648 до 2 147 483 647. Занимает 4 байта. Наиболее используемый тип для хранения чисел.
- BIGINT : хранит очень большие числа от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807, которые занимают в памяти 8 байт.
- DECIMAL : хранит числа c фиксированной точностью. Занимает от 5 до 17 байт в зависимости от количества чисел после запятой. Данный тип может принимать два параметра precision и scale: DECIMAL(precision, scale) . Параметр precision представляет максимальное количество цифр, которые может хранить число. Это значение должно находиться в диапазоне от 1 до 38. По умолчанию оно равно 18. Параметр scale представляет максимальное количество цифр, которые может содержать число после запятой. Это значение должно находиться в диапазоне от 0 до значения параметра precision. По умолчанию оно равно 0.
- NUMERIC : данный тип аналогичен типу DECIMAL.
- SMALLMONEY : хранит дробные значения от -214 748.3648 до 214 748.3647. Предназначено для хранения денежных величин. Занимает 4 байта. Эквивалентен типу DECIMAL(10,4) .
- MONEY : хранит дробные значения от -922 337 203 685 477.5808 до 922 337 203 685 477.5807. Представляет денежные величины и занимает 8 байт. Эквивалентен типу DECIMAL(19,4) .
- FLOAT : хранит числа от –1.79E+308 до 1.79E+308. Занимает от 4 до 8 байт в зависимости от дробной части. Может иметь форму опредеения в виде FLOAT(n) , где n представляет число бит, которые используются для хранения десятичной части числа (мантиссы). По умолчанию n = 53.
- REAL : хранит числа от –340E+38 to 3.40E+38. Занимает 4 байта. Эквивалентен типу FLOAT(24) .
Примеры числовых столбцов:
Salary MONEY, TotalWeight DECIMAL(9,2), Age INT, Surplus FLOAT
Типы данных, представляющие дату и время
- DATE : хранит даты от 0001-01-01 (1 января 0001 года) до 9999-12-31 (31 декабря 9999 года). Занимает 3 байта.
- TIME : хранит время в диапазоне от 00:00:00.0000000 до 23:59:59.9999999. Занимает от 3 до 5 байт. Может иметь форму TIME(n) , где n представляет количество цифр от 0 до 7 в дробной части секунд.
- DATETIME : хранит даты и время от 01/01/1753 до 31/12/9999. Занимает 8 байт.
- DATETIME2 : хранит даты и время в диапазоне от 01/01/0001 00:00:00.0000000 до 31/12/9999 23:59:59.9999999. Занимает от 6 до 8 байт в зависимости от точности времени. Может иметь форму DATETIME2(n) , где n представляет количество цифр от 0 до 7 в дробной части секунд.
- SMALLDATETIME : хранит даты и время в диапазоне от 01/01/1900 до 06/06/2079, то есть ближайшие даты. Занимает от 4 байта.
- DATETIMEOFFSET : хранит даты и время в диапазоне от 0001-01-01 до 9999-12-31. Сохраняет детальную информацию о времени с точностью до 100 наносекунд. Занимает 10 байт.
Распространенные форматы дат:
- yyyy-mm-dd — 2017-07-12
- dd/mm/yyyy — 12/07/2017
- mm-dd-yy — 07-12-17 В таком формате двузначные числа от 00 до 49 воспринимаются как даты в диапазоне 2000-2049. А числа от 50 до 99 как диапазон чисел 1950 — 1999.
- Month dd, yyyy — July 12, 2017
Распространенные форматы времени:
- hh:mi — 13:21
- hh:mi am/pm — 1:21 pm
- hh:mi:ss — 1:21:34
- hh:mi:ss:mmm — 1:21:34:12
- hh:mi:ss:nnnnnnn — 1:21:34:1234567
Строковые типы данных
- CHAR : хранит строку длиной от 1 до 8 000 символов. На каждый символ выделяет по 1 байту. Не подходит для многих языков, так как хранит символы не в кодировке Unicode. Количество символов, которое может хранить столбец, передается в скобках. Например, для столбца с типом CHAR(10) будет выделено 10 байт. И если мы сохраним в столбце строку менее 10 символов, то она будет дополнена пробелами.
- VARCHAR : хранит строку. На каждый символ выделяется 1 байт. Можно указать конкретную длину для столбца — от 1 до 8 000 символов, например, VARCHAR(10) . Если строка должна иметь больше 8000 символов, то задается размер MAX, а на хранение строки может выделяться до 2 Гб: VARCHAR(MAX) . Не подходит для многих языков, так как хранит символы не в кодировке Unicode. В отличие от типа CHAR если в столбец с типом VARCHAR(10) будет сохранена строка в 5 символов, то в столце будет сохранено именно пять символов.
- NCHAR : хранит строку в кодировке Unicode длиной от 1 до 4 000 символов. На каждый символ выделяется 2 байта. Например, NCHAR(15)
- NVARCHAR : хранит строку в кодировке Unicode. На каждый символ выделяется 2 байта.Можно задать конкретный размер от 1 до 4 000 символов: . Если строка должна иметь больше 4000 символов, то задается размер MAX, а на хранение строки может выделяться до 2 Гб.
Еще два типа TEXT и NTEXT являются устаревшими и поэтому их не рекомендуется использовать. Вместо них применяются VARCHAR и NVARCHAR соответственно.
Примеры определения строковых столбцов:
Email VARCHAR(30), Comment NVARCHAR(MAX)
Бинарные типы данных
- BINARY : хранит бинарные данные в виде последовательности от 1 до 8 000 байт.
- VARBINARY : хранит бинарные данные в виде последовательности от 1 до 8 000 байт, либо до 2^31–1 байт при использовании значения MAX (VARBINARY(MAX)).
Еще один бинарный тип — тип IMAGE является устаревшим, и вместо него рекомендуется применять тип VARBINARY.
Остальные типы данных
- UNIQUEIDENTIFIER : уникальный идентификатор GUID (по сути строка с уникальным значением), который занимает 16 байт.
- TIMESTAMP : некоторое число, которое хранит номер версии строки в таблице. Занимает 8 байт.
- CURSOR : представляет набор строк.
- HIERARCHYID : представляет позицию в иерархии.
- SQL_VARIANT : может хранить данные любого другого типа данных T-SQL.
- XML : хранит документы XML или фрагменты документов XML. Занимает в памяти до 2 Гб.
- TABLE : представляет определение таблицы.
- GEOGRAPHY : хранит географические данные, такие как широта и долгота.
- GEOMETRY : хранит координаты местонахождения на плоскости.
SQL-Ex blog

Типы данных varchar в SQL Server, Oracle и PostgreSQL
Добавил Sergey Moiseenko on Суббота, 23 июля. 2022
Здесь мы рассмотрим как сохранить максимальное число символов в столбце переменной длины и различия между тремя системами баз данных. В частности, будут рассмотрены различные процедуры, используемые для хранения больших строк в столбце с целью обработки большого текста или структурированных данных типа JSON (будет отдельная статья).
Как обычно, будем использовать свободно распространяемую тестовую базу Chinook, доступную в форматах многих реляционных СУБД. Эта база данных имитирует магазин цифровых медиа с образцовыми данными, так что все, что вам требуется, это загрузить необходимую копию, и вы получите скрипты создания структур данных и операторы вставки данных.
SQL Server
В SQL Server имеются следующие строковые типы данных: VARCHAR(n), VARCHAR(MAX) и NVARCHAR(n), NVARCHAR(MAX); имеется также тип TEXT, но поскольку он является устаревшим, его не следует больше использовать.
В типах данных VARCHAR n означает максимальное число хранимых байтов; это не число символов, и их может быть максимум 8000. Если же вы используете MAX, то максимально возможный размер достигает 2 Гб (размер хранилища) для символьного типа данных, не являющегося Unicode.
Аналогично в типах данных NVARCHAR n означает число пар байтов, поэтому предельное значение составляет 4000 (максимальная длина), а MAX означает максимальное количество в 2 Гб (максимальное хранилище) для символов Юникод.
Очевидно, что число байтов или пар байтов для латинских наборов символов эквивалентно числу символов, но для других наборов это не справедливо!
Давайте установим пример T-SQL, создающий новую таблицу, в которой мы будем хранить сообщения в клиентскую службу для магазина цифрового медиа.
CREATE TABLE CustomerMessages(
MessageId int not null,
CustomerId int NOT NULL,
InsertDate smalldatetime NOT NULL,
UpdateDate smalldatetime NULL,
MessageContent varchar(5000)
CONSTRAINT PK_Message PRIMARY KEY CLUSTERED
(
MessageId ASC
) ON [PRIMARY]
) ON [PRIMARY];
ALTER TABLE CustomerMessages ADD CONSTRAINT [FK_CustomerMessages_Customer] FOREIGN KEY(CustomerId)
REFERENCES Customer (CustomerId)
Как можно увидеть, мы создали новую таблицу, добавив внешний ключ для связи с CustomerID и проверки, что клиент существует, а также первичный ключ на MessageId. В этом случае мы получаем максимум 5000 байтов (в нашем случае, поскольку мы имеем набор латиницы, это 5000 символов) в столбце MessageContent.

Теперь предположим, что мы увидели, что 5000 символов недостаточно в некоторых случаях, а также заметили, что текст, в Messages сильно варьируется от нескольких слов до длинных параграфов, поэтому мы хотим изменить его на varchar(max). Это может быть сделано следующим образом.
ALTER TABLE CustomerMessages ALTER COLUMN MessageContent varchar(max)

Как видно, в SQL Server изменить тип очень просто!
Oracle
В Oracle строковые типы используются аналогично, но мы имеем другие предельные значения. Во-первых, есть VARCHAR2 и NVARCHAR2, которые имеют предел 4000 байтов, если параметр MAX_STRING_SIZE установлен в значение STANDARD (об этом ниже). Они более или менее похожи на строки переменной длины VARCHAR и NVARCHAR в SQL Server.
Два других типа, используемых для хранения большого текста, это CLOB и NCLOB. LOB — это сокращение для Large Objects (большие объекты), C здесь означает символы (characters), а N, как и N в NVARCHAR, обозначает National Character (национальные символы) для данных Юникод. Эти типы данных довольно удобны для хранения большого количества данных и ограничены размером в 4 Гб. Они могут быть очень сложными и, вообще говоря, не очень хороши с точки зрения производительности, т.к. хранятся в отдельных табличных пространствах, а также требуют специальной обработки на стороне приложения.
Давайте добавим ту же таблицу, что и для SQL Server.
CREATE TABLE CHINOOK.CUSTOMERMESSAGES
(MESSAGEID NUMBER NOT NULL ENABLE,
CUSTOMERID NUMBER NOT NULL ENABLE,
INSERTDATE date NOT NULL ENABLE,
UPDATEDATE date,
MESSAGECONTENT varchar2(4000),
CONSTRAINT PK_MESSAGE PRIMARY KEY (MESSAGEID));
ALTER TABLE CHINOOK.CUSTOMERMESSAGES add CONSTRAINT FK_CUSTOMERID FOREIGN KEY (CUSTOMERID)
REFERENCES CHINOOK.CUSTOMER (CUSTOMERID) ENABLE;

Структура и типы данных таблицы аналогичны использованным в SQL Server, но обратите внимание, что столбец MESSAGECONTENT имеет ограничение 4000 байтов (символов в нашем случае). Теперь давайте попытаемся изменить на 5000, как в SQL Server.
ALTER TABLE CHINOOK.CUSTOMERMESSAGES MODIFY (MESSAGECONTENT VARCHAR2(5000 BYTE));

Как видно, мы достигли предела, но, начиная с версии Oracle 12c, имеется решение, о котором я намекал ранее, которое позволяет увеличить предел типов данных VARCHAR2 и NVARCHAR2 до 32767 байтов.
Сначала давайте проверим упомянутый выше параметр MAX_STRING_SIZE.
SHOW PARAMETER MAX_STRING_SIZE

Как ожидалось, параметр установлен в значение по умолчанию STANDARD. Для увеличения предела VARCHAR2 и NVARCHAR2 до 32767 байтов нам нужно установить этот инициализационный параметр в значение EXTENDED.
Прежде чем начинать процедуру изменения инициализационного параметра, нужно проверить параметр COMPATIBLE, который должен быть установлен в 12.0.0.0 или выше, чтобы выполнить эту процедуру.
SELECT name, value, description FROM v$parameter WHERE name = 'compatible';

Теперь, когда мы уверены, продолжим, но должны держать в уме, что эта процедура потребует, по крайней мере, пару перезапусков базы данных, и должна быть выполнена пара внешних скриптов преобразования.
Я покажу эти шаги для изменения параметра на единственной PDB.
Первый шаг — это остановить PDB и снова открыть её в режиме UPGRADE (или миграции), что может быть сделано подключением к хосту в ssh, а затем подключением к Container DB или CDB с помощью sqlplus и выполнением команды:

Теперь мы можем выполнить команду для её открытия в режиме миграции:

Мы теперь можем подключиться к PDB и изменить параметр инициализации:


Сейчас мы можем выполнить эти два скрипта для преобразования и перекомпиляции объектов в нашей базе данных:
и 
Теперь мы готовы перезапустить базу данных (PDB):

И снова проверим параметр:

Теперь мы можем снова попытаться изменить столбец MESSAGECONTENT на 5000 байтов:
ALTER TABLE CHINOOK.CUSTOMERMESSAGES MODIFY (MESSAGECONTENT VARCHAR2(5000 BYTE));

Проверяя таблицу в Oracle Developer, мы видим, что теперь размер столбца равен 5000 байтов:

Помните, что предельное значение составляет 32767 байтов, поэтому VARCHAR2(32767) — это максимум, который мы можем задать.
ALTER TABLE CHINOOK.CUSTOMERMESSAGES MODIFY (MESSAGECONTENT VARCHAR2(32767 BYTE) );

PostgresSQL
В PostgreSQL у нас есть два типа данных, которые используются для хранения больших текстов: VARCHAR(N) и TEXT.
Имеется пара отличий от SQL Server и Oracle: первое, N является числом символов, а не байтов, более того, мы можем опустить это число, и тогда это будет эквивалентно VARCHAR(MAX) в SQL Server и типу данных TEXT. В этом случае предельное значение числа символов не указывается в официальной документации PostgreSQL, хотя имеется такая информация: «В любом случае максимальная строка символов, которая может быть сохранена, имеет длину около 1 Гб».
Теперь мы можем попробовать создать таблицу, аналогичную двум другим СУБД:
CREATE TABLE IF NOT EXISTS public."CustomerMessages"
(
"MessageId" integer not null,
"CustomerId" integer NOT NULL,
"InsertDate" timestamp without time zone NOT NULL,
"UpdateDate" timestamp without time zone,
"MessageContent" varchar(5000),
CONSTRAINT "PK_Message" PRIMARY KEY ("MessageId")
)
ALTER TABLE public."CustomerMessages"
ADD CONSTRAINT "FK_CustomerMessages_Customer" FOREIGN KEY("CustomerId")
REFERENCES public."Customer" ("CustomerId") MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION;

Обратите внимание на предложение IF NOT EXISTS в CREATE TABLE, это очень удобно!
Поскольку мы увидели, что 5000 символов недостаточно, модифицируем столбец, выходя за предел, чтобы мы могли хранить максимальное символов:
ALTER TABLE "CustomerMessages" ALTER COLUMN "MessageContent" type varchar

Вот и все для PostgreSQL!
Заключение
Здесь мы рассмотрели типы данных, которые обычно используются для хранения больших строк в SQL Server, Oracle и PostgreSQL. Мы увидели различные предельные значения синтаксис, а также модификацию инициализационных параметров базы данных для увеличения предельного значения в Oracle.
Обратные ссылки
Нет обратных ссылок
Комментарии
Показывать комментарии Как список | Древовидной структурой
Автор не разрешил комментировать эту запись
Типы данных в MS SQL. Transact-SQL
Согласно процедурному расширению языка SQL (Transact-SQL), для работы с MS SQL Server предусмотрены различные типы данных. Конкретный тип данных указывается для любого столбца во время создания таблицы и определяет, какие именно значения можно будет хранить, а также сколько места в памяти компьютера они будут занимать. Эта статья представляет собой обзор существующих типов данных для MS SQL Server, которые, в зависимости от поддерживаемых значений, делятся на группы.
Несколько слов о T-SQL
Transact-SQL является процедурным расширением SQL (structured query language), которое создано корпорацией Microsoft специально для баз данных Microsoft SQL Server и Sybase ASE. Известный язык программирования SQL был расширен рядом дополнительных возможностей:
- управляющими операторами,
- локальными и глобальными переменными,
- вспомогательными функциями для обработки строк, дат и пр.,
- поддержкой аутентификации Microsoft Windows.
Сегодня язык Transact-SQL — это, по сути, ключ к применению MS SQL Server. Таким образом, все программные приложения, взаимодействующие с экземпляром MS SQL Server, вне зависимости от реализации и UI, выполняют отправку серверу инструкций Transact-SQL.
Директивами сценария являются специфические команды, используемые лишь в MS SQL. Такие команды позволяют серверу определять правила работы с транзакциями и скриптом.
Типы данных (data types)
Как это принято и в других языках программирования, язык T-SQL поддерживает множество разных типов данных, используемых для хранения переменных в MS SQL. Эти типы данных можно разделить на группы:
- числовые. Служат для хранения числовых переменных;
- типы данных для дат. Обеспечивают хранение значений времени и даты;
- символьный. Нужен для символьных типов данных;
- двоичные. Обеспечивают хранение бинарных типов данных;
- большого объема. Этот тип данных (data type) нужен для хранения больших бинарных значений;
- специальные. Сюда входит широкий спектр типов данных: указатели, таблицы и пр.
Давайте рассмотрим некоторые из вышеописанных типов данных подробнее.
Числовые типы данных
К числовым типам данных относят:
- BIT: может храниться 0 либо 1. По сути, это аналог булевого типа в программировании. Занимает всего один байт;
- TINYINT: хранит целые числа в пределах 0-255. Тоже занимает 1 байт и часто используется в целях хранения небольших чисел;
- SMALLINT: сохраняет числа в пределах от –32 768 до 32 767. Занимает уже 2 байта;
- INT: диапазон хранения — от –2 147 483 648 до 2 147 483 647. 4 байта. Тип данных int чаще всего применяется для сохранения числовых значений;
- BIGINT: предназначен для очень больших значений (от -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807) и занимает в памяти 8 байт;
- DECIMAL: для хранения переменных c фиксированной точностью. В зависимости от количества чисел после запятой переменная типа Decimal может занимать от 5 до 17 байт.
Также этот тип данных способен принимать 2 параметра — scale и precision: DECIMAL(precision, scale).
Параметр precision служит для представления наибольшего количества цифр, способных хранить числовое значение. По дефолту оно равняется 18-ти, а вообще должно располагаться в диапазоне 1-38.
Параметр scale служит для представления максимального количества цифр, которые способно содержать число после запятой. Такое значение должно быть в диапазоне от 0 до значения, которое указывается параметром precision. По умолчанию равно нулю;
- NUMERIC: этот тип данных аналогичен предыдущему;
- SMALLMONEY: сохраняет дробные значения в пределах от -214 748.3648 до 214 748.3647. Служит для хранения денежных величин. Эквивалентен DECIMAL(10,4), занимает 4 байта;
- MONEY: сохраняет дробные значения в диапазоне от -922 337 203 685 477.5808 до 922 337 203 685 477.5807. Служит для представления денежных величин, занимает 8 байт. Соответствует DECIMAL(19,4).
- FLOAT: для хранения значений от –1.79E+308 до 1.79E+308. В зависимости от дробной части может занимать от 4 до 8 байт.
Этот тип данных способен иметь форму определения в формате FLOAT(n), где n — это количество бит, используемых в целях хранения десятичной части числа (речь идет о мантиссе). По дефолту n равняется 53;
- REAL: сохраняет числа в диапазоне от –340E+38 to 3.40E+38. Соответствует FLOAT(24), занимает 4 байта.
Рассмотрим пример числового столбца с таким типом данных:

Тип данных для времени
Существуют типы данных, предназначенных для отображения даты и времени:
- DATE: сохраняет даты, начиная с 1 января 0001 года (0001-01-01), заканчивая 31 декабрём 9999 г. (9999-12-31). Занимает 3 байта;
- TIME: сохраняет время в диапазоне 00:00:00.0000000 — 23:59:59.9999999. Способен иметь форму TIME(n), причем n здесь представляет число цифр от 0 до 7 в дробной части секунд. Занимает 3-5 байт;
- DATETIME: обеспечивает сохранение объектов даты и времени в диапазоне 01/01/1753 — 31/12/9999. 8 байт;
- DATETIME2: сохраняет дату и время в диапазоне 01/01/0001 00:00:00.0000000 — 31/12/9999 23:59:59.9999999. Может занимать 6-8 байт, что зависит от временной точности. Способен иметь форму DATETIME2(n), причем n здесь — это число цифр в пределах 0-7 в дробной части секунд;
- SMALLDATETIME: сохраняет даты и время в пределах 01/01/1900-06/06/2079, то есть речь идет о ближайших датах. 4 байта;
- DATETIMEOFFSET: для дат и времени в диапазоне от 0001-01-01 до 9999-12-31. Обеспечивается сохранение детальной информации о времени, точность составляет 100 наносекунд. 10 байт;
Наиболее распространены следующие форматы дат:

В этом формате 2-значные числа в пределах 00-49 воспринимаются в качестве дат диапазона 2000-2049. А числовые значения 50-99 воспринимаются в качестве диапазона 1950-1999.

Распространены следующие временные форматы:

Строковый тип данных
Какие строковые типы данных бывают:
- CHAR: для строк длиной 1-8000 символов. На каждый символ выделяется 1 байт. Этот тип данных не подходит для многих языков, а все потому, что он сохраняет символы не в кодировке Unicode.
Число символов, которое способен хранить столбец, передается с помощью скобок. К примеру, для столбца CHAR(10) выделится 10 байт. Когда мы сохраняем в столбце строку размером меньше десяти символов, она дополнится пробелами;
- VARCHAR: может хранить строки, причем здесь тоже на каждый символ происходит выделение 1 байта. Для столбца можно указать определенную длину в диапазоне 1-8 000 символов, к примеру, VARCHAR(10). Когда строка должна иметь более 8000 символов, задается размер MAX, причем на хранение строки может выделять до 2 Гб: VARCHAR(MAX).
Этот тип данных также не подходит для многих языков и все по той же причине: хранит символы не в Unicode;
- NCHAR: для строки с кодировкой Unicode и длиной 1-4 000 символов. На каждый символ — 2 байта;
- NVARCHAR: сохраняет строку в Unicode. На каждый символ — 2 байта. Есть возможность задать конкретный размер в пределах 1-4 000 символов. Когда строка должна включать более 4000 символов, задается размер MAX, а на хранение строки выделяется до 2 Гб.
Также существуют такие типы данных, как TEXT и NTEXT. Они считаются устаревшими, поэтому их лучше не использовать. Вместо только что упомянутых типов данных применяют VARCHAR и NVARCHAR.
Рассмотрим примеры определения строковых столбцов:

О бинарных типах данных
Пришла очередь и двоичным данным:
- BINARY: для сохранения бинарных типов данных в качестве последовательности в пределах 1-8 000 байт;
- VARBINARY: для хранения бинарных типов данных в качестве последовательности в диапазоне 1-8 000 байт, либо до 2^31–1 байт в случае применения значения MAX (VARBINARY(MAX)).
Такой бинарный тип, как IMAGE, тоже устарел, поэтому вместо него желательно использовать тип данных VARBINARY.
Специальные типы данных
В этой группе перечислим оставшиеся типы данных:
- UNIQUEIDENTIFIER: уникальный идентификатор GUID (строка с уникальным значением), занимающий 16 байт;
- TIMESTAMP: некоторое число, хранящее номер версии строки в таблице. Может занимать 8 байт;
- CURSOR: для набора строк;
- HIERARCHYID: для представления позиции в иерархии;
- SQL_VARIANT: для хранения значений практически любого другого типа данных T-SQL;
- XML: для хранения документов XML либо фрагментов XML-документов. Занимает до двух гигабайт;
- TABLE: представляет определение таблицы;
- GEOGRAPHY: обеспечивает хранение географических данных (широты, долготы);
- GEOMETRY: для координат местонахождения на плоскости.
Примечание: для работы с русскими символами тип данных применяют, используя приставку «n» (nchar, nvarchar, ntext). Такие приставки кодируют символы 2-мя байтами. Если говорить иными словами, то для работы с кодировкой Unicode используют тип данных с «n» (вспоминаем слово national). Так же записываются и строковые константы.
Когда речь идет о типе данных для значения переменной длины, задействуют тип данных, имеющий приставку «var» (character varying). Тип данных без приставки «var» характеризуется фиксированной длиной области памяти, причем неиспользованная часть заполняется пробелами либо нулями.
varchar(max)-varchar(max) и в продакшн
Недавно поучаствовал в дискуссии на тему влияния на производительность указания длины в столбцах с типом nvarchar. Доводы были разумны у обеих сторон и поскольку у меня было свободное время, решил немного потестировать. Результатом стал этот пост.
Спойлер – не всё так однозначно.
Все тесты проводились на SQL Server 2014 Developer Edition, примерно такие же результаты были получены и на SQL Server 2016 (с небольшими отличиями). Описанное ниже должно быть актуально для SQL Server 2005-2016 (а в 2017/2019 требуется тестирование, поскольку там появились Adaptive Memory Grants, которые могут несколько исправить положение).
Нам понадобятся – хранимая процедура от Erik Darling sp_pressure_detector, которая позволяет получить множество информации о текущем состоянии системы и SQL Query Stress – очень крутая open-source утилита Adam Machanic/Erik Ejlskov Jensen для нагрузочного тестирования MS SQL Server.
О чём вообще речь
Вопрос, на который я стараюсь ответить – влияет ли на производительность выбор длины поля (n)varchar (далее везде просто varchar, хотя всё актуально и для nvarchar), или можно использовать varchar(max) и не париться, поскольку если длина строки < 8000 (4000 для nvarchar) символов, то varchar(max) и varchar(N) хранятся IN-ROW.
Готовим стенд
create table ##v10 (i int, d datetime, v varchar(10)); create table ##v100 (i int, d datetime, v varchar(100)); create table ##vmax (i int, d datetime, v varchar(max));
Создаём 3 таблицы из трёх полей, разница только в длине varchar: 10/100/max. И заполним их одинаковыми данными:
;with x as (select 1 x union all select 1) , xx as (select 1 x from x x1, x x2) , xxx as (select 1 x from xx x1, xx x2, xx x3) , xxxx as ( select row_number() over(order by (select null)) i , dateadd(second, row_number() over(order by (select null)), '20200101') d , cast (row_number() over(order by (select null)) as varchar(10)) v from xxx x1, xxx x2, xxx x3 ) --262144 строк insert into ##v10 --varchar(10) select i, d, v from xxxx; insert into ##v100 --varchar(100) select i, d, v from ##v10; insert into ##vmax --varchar(max) select i, d, v from ##v10;
В итоге каждая таблица будет содержать 262144 строк. Столбец I (integer) содержит неповторяющиеся числа от 1 до 262145; d (datetime) уникальные даты и v (varchar) – cast (I as varchar(10)). Чтобы это было чуть больше похоже на реальную жизнь, создаём уникальный кластерный индекс по i:
create unique clustered index #cidx10 on ##v10(i); create unique clustered index #cidx100 on ##v100(i); create unique clustered index #cidxmax on ##vmax(i);
Поехали
Сначала посмотрим планы выполнения разных запросов.
Во-первых, проверим, что отбор по полю varchar не зависит от его длины (если там хранится < 8000 символов). Включаем действительный план выполнения и смотрим:
select * from ##v10 where v = '123'; select * from ##v100 where v = '123'; select * from ##vmax where v = '123';

Как ни странно, разница, хоть и небольшая, но есть. План запроса с varchar(max) сначала выбирает все строки, а потом их отфильтровывает, а varchar(10) и varchar(100) проверяют совпадения при сканировании кластерного индекса. Из-за этого, сканирование занимает практически в 3 раза больше времени – 0,068 секунд против 0.022 у varchar(10).
Теперь посмотрим, что будет, если просто выводить varchar-колонку, а отбирать данные по ключу кластерного индекса:
select * from ##v10 where i between 200000 and 201000; select * from ##v100 where i between 200000 and 201000; select * from ##vmax where i between 200000 and 201000;

Тут всё понятно – для таких запросов никакой разницы нет.
Теперь интересная часть. Предыдущим запросом мы получили всего-то 1001 строку, а теперь хотим отсортировать их по неиндексированной колонке. Пробуем:
select * from ##v10 where i between 200000 and 201000 order by d; select * from ##v100 where i between 200000 and 201000 order by d; select * from ##vmax where i between 200000 and 201000 order by d;

Ой, а что там такое жёлтенькое?

Забавно, т.е. запрос запросил и получил 6,5 мегабайт оперативной памяти для сортировки, а использовал только 96 килобайт. А насколько будет хуже, если строк будет побольше. Ну, пусть будет не 1000, а 100000:

А вот тут уже посерьёзнее. Причём первый запрос, который работает с самым маленьким varchar(10) тоже чем-то недоволен:

Слева тут предупреждение последнего запроса: запрошено 500 мегабайт, а использовано всего 9,5 мегабайт. А справа – предупреждение сортировки: запрошено было 8840 килобайт, но их не хватило и ещё 360 страниц (по 8 кб) было записано и прочитано из tempdb.
И тут напрашивается вопрос: WTF?
Ответ – так работает оптимизатор запросов SQL Server. Чтобы что-то отсортировать, это что-то нужно сначала поместить в память. Как понять сколько памяти нужно? Вообще, нам известно сколько какой тип данных занимает места. А что же со строками переменной длины? А вот с ними интереснее. При выделении памяти для сортировок/hash join, SQL Server считает, что в среднем они заполнены наполовину. И выделяет под них память как (размер / 2) * ожидаемое количество строк. Но varchar(max) может хранить аж 2Гб – сколько же выделять? SQL Server считает, что там будет половина от varchar(8000) – т.е. примерно 4 кб на строку.
Что интересно – такое выделение памяти приводит к проблемам не только с varchar(max) – если размер ваших varchar’ов любовно подобран так, что большая часть из них заполнена наполовину и больше – это тоже ведёт к проблемам. Проблемам иного плана, но не менее серьёзным. На рисунке выше есть описание – SQL Server не смог корректно выделить память для сортировки маленького varchar’а и использовал tempdb для хранения промежуточных результатов. Если tempdb лежит на медленных дисках, или активно используется другими запросами – это может стать очень узким местом.
SQL Query Stress
Теперь посмотрим, что происходит при массовом выполнении запросов. Запустим SQL Query Stress, подключим его к нашему серверу, и скажем выполнить все эти запросы по 10 раз в 50 потоках.
Результаты выполнения первого запроса:



Интересно, но без индексов, при поиске varchar(max) показывает себя хуже всех, причём солидно так хуже по процессорному времени на итерацию и общему времени выполнения.
sp_pressure_detector не показывает тут ничего интересного, поэтому её вывод не привожу.
Результаты выполнения второго запроса:



Тут всё ожидаемо – одинаково хорошо.
Теперь интересная часть. Запрос с сортировкой полученной тысячи строк:



Тут всё оказалось точно также, как и с предыдущим запросом – строк не много, сортировка проблем не вызывает.
Теперь последний запрос, который сортирует неоправданно много строк (я добавил в него top 1000, чтобы не тянуть весь сортированный список):



И вот тут привожу вывод sp_pressure_detector:

Что он нам говорит? Все сессии запрашивают по 489 МБ (на сортировку), но только на 22 из них SQL Server’у хватило памяти, даже учитывая, что используют все эти 22 сессии всего по 9 МБ!
Всего доступно 11 Гб памяти, 22 сессиям было выделено по 489.625 и у SQL Server’a осталось всего-то 258 доступных мегабайт, а новые сессии тоже хотят получить по 489. Что делать? Ждать, пока освободится память – они и ждут, даже не начиная выполняться. Что будут делать пользователи, если в их сессиях выполняются такие запросы? Тоже ждать.
Кстати, обратите внимание на рисунок с varchar(10) – запросы с varchar(10) выполнялись дольше, чем запросы с varchar(100) – и это при том, что у меня tempdb на очень быстром диске. Чем хуже диск под tempdb – тем медленнее будет выполняться запрос.
Отдельное примечание для SQL Server 2012/2014
В SQL Server 2012/2014 есть ещё одна забавная шутка с sort spills. Даже если вы используете тип char/nchar – это не гарантирует отсутствие spill’ов в tempdb. MS признала проблему в оптимизаторе, когда он выделял слишком мало памяти для сортировки, даже если количество строк было оценено верно.
create table ##c6 (i int, d datetime, v char(6)); insert into ##c6 (i, d, v) select i, d, v from ##v10 select * from ##c6 where i between 100000 and 200000 order by d;

Включаем документированный флаг трассировки (НЕ ДЕЛАЙТЕ ЭТОГО НА ПРОДЕ БЕЗ НЕОБХОДИМОСТИ):
DBCC TRACEON (7470, -1);

Восклицательный знак у сортировки пропал, spill’а больше нет.
Выводы
С осторожностью используйте сортировку в своих запросах, там где у вас есть колонки (n)varchar. Если сортировка всё же нужна, крайне желательно, чтобы по колонке сортировки был индекс.
Учтите, что чтобы получить сортировку совсем необязательно явно использовать order by – её появление возможно и при merge join’ах, например. Та же проблема с выделением памяти возможна и при hash join’ах, например, вот с varchar(max):
select top 100 * from ##vmax v1 inner hash join ##v10 v2 on v1.i = v2.i

Выделено 2.5 ГИГАБАЙТА памяти, используется 25 мегабайт!
Главный для меня вывод: размер колонки (n)varchar – ВАЖЕН! Если размер слишком маленький – возможны spill’ы в tempdb, если слишком большой – возможны слишком большие запросы памяти. При наличии сортировок разумным будет объявлять длину varchar как средняя длина записи * 2, а в случае SQL Server 2012/2014 — даже больше.
Неожиданный для меня вывод: varchar(max), содержащий меньше 8000 символов, реально работает медленнее, при фильтрах по нему. Пока не знаю как это объяснить — буду копать ещё.
Бонусный вывод для меня: уже почти нажав «опубликовать» я подумал, что ведь и с varchar(max) можно испытать проблему «маленького varchar’a». И правда, при хранении в varchar(max) больше чем 4000 символов (2000 для nvarchar) — сортировки могут стать проблемой.
insert into ##vmax(i, d, v) select i, d, replicate('a', 4000) v from ##v10; select * from ##vmax where i between 200000 and 201000 order by d;

truncate table ##vmax; insert into ##vmax(i, d, v) select i, d, replicate('a', 4100) v from ##v10; select * from ##vmax where i between 200000 and 201000 order by d;

Почему в самом начале я написал, что не всё так однозначно? Потому что, например, на моём домашнем ноуте с полумёртвым диском, spill’ы в tempdb при сортировке «маленьких» varchar приводили к тому, что такие запросы выполнялись на ПОРЯДКИ медленнее, чем аналогичные запросы с varchar(max). Если у вас хорошее железо, возможно, они не станут такой проблемой, но забывать о них не стоит.
Что было бы ещё интересно — посмотреть есть ли какие-то проблемы из-за слишком больших/маленьких размеров varchar’ов в других СУБД. Если у вас есть возможность проверить — буду рад, если поделитесь.
Маленький бонус
Отловить такие проблемы с помощью кэша планов запросов, к сожалению, не получится. Вот примеры планов из кэша: никаких предупреждений в них, увы, нет.