varchar (255) ou varchar (256)?


21

Dois-je utiliser varchar(255)ou varchar(256)lors de la conception des tables? J'ai entendu qu'un octet est utilisé pour la longueur de la colonne ou pour stocker des métadonnées.

Est-ce plus important à ce stade?

J'ai vu quelques articles sur Internet, mais ils s'appliquent à Oracle et à MySQL.

Nous avons Microsoft SQL Server 2016 Enterprise Edition, comment s'applique-t-il à cet environnement?

Maintenant, disons par exemple, que se passe-t-il si je dis à mes clients de conserver, par exemple, une description textuelle de 255 caractères au lieu de 256, y a-t-il une différence? Ce que j'ai lu "Avec une longueur maximale de 255 caractères, le SGBD peut choisir d'utiliser un seul octet pour indiquer la longueur des données dans le champ. Si la limite était de 256 ou plus, deux octets seraient nécessaires." Est-ce vrai?


Pour info: cette question a été publiée sur les forums MSDN: social.msdn.microsoft.com/Forums/sqlserver/en-US/…
Solomon Rutzky

Réponses:


36

Dimensionnez chaque colonne de manière appropriée. N'utilisez PAS une taille "standard" pour chaque colonne. Si vous n'avez besoin que de 30 caractères, pourquoi créer une colonne pouvant en gérer 255? Je suis tellement content que vous ne préconisiez pas l'utilisation varchar(max)de vos colonnes de chaînes.

C'est un conseil particulièrement prudent si vous avez besoin d'indexer une colonne ou si vous utilisez une colonne comme clé primaire et qu'elle a des références de clé étrangère. SQL Server utilise la taille de chaque colonne dans son optimiseur de requêtes pour comprendre les besoins en mémoire estimés pour le traitement des requêtes. Le fait d'avoir des colonnes surdimensionnées peut nuire aux performances.

Les index des colonnes surdimensionnées peuvent entraîner la génération d'erreurs:

CREATE TABLE dbo.WideIndex
(
    col1 varchar(255) NOT NULL
    , col2 varchar(255) NOT NULL
    , col3 varchar(600) NOT NULL    
);

CREATE INDEX IX_WideIndex_01
ON dbo.WideIndex (col1, col2, col3);

La tentative de création de l'index ci-dessus entraîne cet avertissement:

Avertissement! La longueur de clé maximale est de 900 octets. L'index «IX_WideIndex_01» a une longueur maximale de 1110 octets. Pour une combinaison de grandes valeurs, l'opération d'insertion / mise à jour échouera.

900 octets est la taille de clé maximale pour les index cluster (et les index non cluster sur SQL Server 2012 et versions antérieures). 1700 octets est la taille de clé maximale pour les index non clusterisés sur les versions plus récentes de SQL Server. Si vous concevez des colonnes avec une largeur générique, telle que (255), vous pouvez rencontrer cet avertissement beaucoup plus souvent que prévu.

Si vous êtes intéressé par les composants internes du stockage, vous pouvez utiliser le petit test suivant pour mieux comprendre comment SQL Server stocke les données de stockage en ligne non compressées.

Tout d'abord, nous allons créer un tableau où nous pouvons stocker des colonnes de différentes tailles:

IF OBJECT_ID(N'dbo.varchartest', N'U') IS NOT NULL
DROP TABLE dbo.varchartest;
GO

CREATE TABLE dbo.varchartest
(
    varchar30 varchar(30) NOT NULL
    , varchar255 varchar(255) NOT NULL
    , varchar256 varchar(256) NOT NULL
);

Nous allons maintenant insérer une seule ligne:

INSERT INTO dbo.varchartest (varchar30, varchar255, varchar256)
VALUES (REPLICATE('1', 30), REPLICATE('2', 255), REPLICATE('3', 256));

Cette requête utilise les fonctions non documentées et non prises en charge sys.fn_RowDumpCrackeret sys.fn_PhyslocCrackerpour afficher des détails intéressants sur la table:

SELECT rdc.*
    , plc.*
FROM dbo.varchartest vct
CROSS APPLY  sys.fn_RowDumpCracker(%%rowdump%%) rdc
CROSS APPLY sys.fn_physlocCracker(%%physloc%%) plc

La sortie ressemblera à ceci:

╔═════════════════════╦════════════╦═════════╦════ ══════╦══════════════════════════╦══════════╦═════ ════════╦═════════════╦═════════╦═════════╦═══════ ══╗
║ partition_id ║ colName ║ IsInrow ║ IsSparse ║ IsRecordPrefixCompressed ║ IsSymbol ║ PrefixBytes ║ InRowLength ║ file_id ║ page_id ║ slot_id ║
╠═════════════════════╬════════════╬═════════╬════ ══════╬══════════════════════════╬══════════╬═════ ════════╬═════════════╬═════════╬═════════╬═══════ ══╣
║ 1729382263096344576 ║ varchar30 ║ 1 ║ 0 ║ 0 ║ 0 ║ 0 ║ 30 ║ 1 ║ 1912 ║ 0 ║
║ 1729382263096344576 ║ varchar255 ║ 1 ║ 0 ║ 0 ║ 0 ║ 0 ║ 255 ║ 1 ║ 1912 ║ 0 ║
║ 1729382263096344576 ║ varchar256 ║ 1 ║ 0 ║ 0 ║ 0 ║ 0 ║ 256 ║ 1 ║ 1912 ║ 0 ║
╚═════════════════════╩════════════╩═════════╩════ ══════╩══════════════════════════╩══════════╩═════ ════════╩═════════════╩═════════╩═════════╩═══════ ══╝

Comme vous pouvez le voir, le InRowLengthpour chaque valeur est affiché, ainsi que l'emplacement de stockage physique de chaque ligne - le "file_id", "page_id" et "slot_id".

Si nous prenons les valeurs file_idet page_iddes résultats de requête ci-dessus et les exécutons DBCC PAGE, nous pouvons voir le contenu réel de la page physique:

DBCC TRACEON (3604); --send display to the client
DBCC PAGE (tempdb, 1, 1912, 3); --database, file_id, page_id, 3 to show page contents
DBCC TRACEOFF (3604);--reset display back to the error log

Les résultats de ma machine sont:

PAGE: (1: 1912)


TAMPON:


BUF @ 0x00000000FF5B2E80

bpage = 0x0000000024130000 bhash = 0x0000000000000000 bpageno = (1: 1912)
bdbid = 2 breferences = 0 bcputicks = 0
bsampleCount = 0 bUse1 = 32497 bstat = 0x10b
blog = 0x212121cc bnext = 0x0000000000000000          

EN-TÊTE DE PAGE:


Page @ 0x0000000024130000

m_pageId = (1: 1912) m_headerVersion = 1 m_type = 1
m_typeFlagBits = 0x0 m_level = 0 m_flagBits = 0x8000
m_objId (AllocUnitId.idObj) = 98834 m_indexId (AllocUnitId.idInd) = 7936
Métadonnées: AllocUnitId = 2233785421652951040                              
Métadonnées: PartitionId = 1945555045333008384 Métadonnées: IndexId = 0
Métadonnées: ObjectId = 34099162 m_prevPage = (0: 0) m_nextPage = (0: 0)
pminlen = 4 m_slotCnt = 1 m_freeCnt = 7538
m_freeData = 652 m_reservedCnt = 0 m_lsn = (35: 210971: 362)
m_xactReserved = 0 m_xdesId = (0: 0) m_ghostRecCnt = 0
m_tornBits = 0 ID de fragment de base de données = 1                      

Statut d'allocation

GAM (1: 2) = SGAM ATTRIBUÉ (1: 3) = PFS NON ATTRIBUÉ (1: 1) = 0x41 ATTRIBUÉ 50_PCT_FULL
DIFF (1: 6) = NON CHANGÉ ML (1: 7) = NON MIN_LOGGED           

Emplacement 0 Décalage 0x60 Longueur 556

Type d'enregistrement = PRIMARY_RECORD Attributs d'enregistrement = NULL_BITMAP VARIABLE_COLUMNS
Taille d'enregistrement = 556                   
Vidage de la mémoire @ 0x000000005145A060

0000000000000000: 30000400 03000003 002d002c 012c0231 31313131 0 ........-.,.,. 11111
0000000000000014: 31313131 31313131 31313131 31313131 31313131 1111111111111111111111
0000000000000028: 31313131 31323232 32323232 32323232 32323232 11111222222222222222
000000000000003C: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
0000000000000050: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
0000000000000064: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
0000000000000078: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
000000000000008C: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
00000000000000A0: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
00000000000000B4: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
00000000000000C8: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
00000000000000DC: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
00000000000000F0: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
0000000000000104: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
0000000000000118: 32323232 32323232 32323232 32323232 32323232 22222222222222222222
000000000000012C: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
0000000000000140: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
0000000000000154: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
0000000000000168: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
000000000000017C: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
0000000000000190: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
00000000000001A4: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
00000000000001B8: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
00000000000001CC: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
00000000000001E0: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
00000000000001F4: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
0000000000000208: 33333333 33333333 33333333 33333333 33333333 33333333333333333333
000000000000021C: 33333333 33333333 33333333 33333333 3333333333333333

Emplacement 0 Colonne 1 Décalage 0xf Longueur 30 Longueur (physique) 30

varchar30 = 11111111111111111111111111111111                               

Emplacement 0 Colonne 2 Décalage 0x2d Longueur 255 Longueur (physique) 255

varchar255 = 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
222222222222222222222222222222222222222222                               

Emplacement 0 Colonne 3 Décalage 0x12c Longueur 256 Longueur (physique) 256

varchar256 = 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
3333333333333333333333333333333333333333333                              

16

D'autres ont déjà souligné que le nombre d'octets requis pour stocker la longueur est fixe. Je voulais me concentrer sur cette partie de votre question:

Est-ce plus important à ce stade?

Votre question est étiquetée avec l'édition entreprise, ce qui signifie généralement que vous aurez une bonne quantité de données. Souvent, les différences d'un octet par ligne n'ont pas vraiment d'importance en pratique. Par exemple, le tableau suivant avec une VARCHAR(255)colonne entièrement remplie occupe 143176 Ko d'espace sur le disque:

DROP TABLE IF EXISTS dbo.V255_FULL;

CREATE TABLE dbo.V255_FULL (
    ID1 BIGINT NOT NULL,
    ID2 BIGINT NOT NULL,
    V255 VARCHAR(255)
);

INSERT INTO dbo.V255_FULL WITH (TABLOCK)
SELECT TOP (500000) 0, 0, REPLICATE('A', 255)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

EXEC sp_spaceused 'V255_FULL';

Résultats:

╔═══════════╦══════════════════════╦═══════════╦═══════════╦════════════╦════════╗
   name             rows          reserved     data     index_size  unused 
╠═══════════╬══════════════════════╬═══════════╬═══════════╬════════════╬════════╣
 V255_FULL  500000                143176 KB  142888 KB  8 KB        280 KB 
╚═══════════╩══════════════════════╩═══════════╩═══════════╩════════════╩════════╝

Créons un deuxième tableau avec une VARCHAR(256)colonne entièrement remplie . Cela va prendre au moins un octet de plus par ligne, non?

DROP TABLE IF EXISTS dbo.V256_FULL;

CREATE TABLE dbo.V256_FULL (
    ID1 BIGINT NOT NULL,
    ID2 BIGINT NOT NULL,
    V256 VARCHAR(256)
);

INSERT INTO dbo.V256_FULL WITH (TABLOCK)
SELECT TOP (500000) 0, 0, REPLICATE('A', 256)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

EXEC sp_spaceused 'V256_FULL';

Résultats:

╔═══════════╦══════════════════════╦═══════════╦═══════════╦════════════╦════════╗
   name             rows          reserved     data     index_size  unused 
╠═══════════╬══════════════════════╬═══════════╬═══════════╬════════════╬════════╣
 V256_FULL  500000                143176 KB  142888 KB  8 KB        280 KB 
╚═══════════╩══════════════════════╩═══════════╩═══════════╩════════════╩════════╝

Il se trouve que les deux tables occupent la même quantité d'espace. Le même nombre de lignes tient sur chaque page de 8 Ko. C'est formidable que vous souhaitiez passer du temps à optimiser votre application, mais je pense que vous feriez mieux de vous concentrer sur différents domaines.


7

La taille déclarée du varchar n'a aucun impact sur les performances. Les données peuvent être réellement stockées en tant que magasin de lignes avec compression de page ou compression de ligne. En tant que magasin de colonnes en cluster ou en tant que table optimisée en mémoire. Chacun d'eux aura des compromis de performances différents, mais il importe peu que vous déclariez un varchar (255) ou varchar (256).


9
@ DavidBrowne-Microsoft non, "la taille déclarée du varchar n'a aucun impact sur les performances" n'est certainement pas vrai - la taille du type de données affecte les allocations de mémoire pour les requêtes. Voir brentozar.com/archive/2017/02/memory-grants-data-size pour plus de détails.
Brent Ozar

6
Essayer de rester simple et décourager l'optimisation prématurée.
David Browne - Microsoft
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.