varchar(255) 还是 varchar(256)?

25 database-design sql-server varchar sql-server-2016 enterprise-edition

我应该在设计表格时使用varchar(255)varchar(256)吗?我听说一个字节用于列的长度,或用于存储元数据。

在这一点上它不再重要了吗?

我在互联网上看到了一些帖子,但是它们适用于 Oracle 和 MySQL。

我们有 Microsoft SQL Server 2016 企业版,它如何应用于这种环境?

现在举个例子,如果我告诉我的客户保留例如 255 个字符而不是 256 个字符的文本描述,有什么区别吗?我读到的内容“最大长度为 255 个字符,DBMS 可以选择使用单个字节来指示字段中数据的长度。如果限制为 256 或更大,则需要两个字节。” 这是真的?

Han*_*non 41

适当调整每一列的大小。不要为每列使用“标准”大小。如果只需要 30 个字符,为什么要创建一个可以处理 255 个字符的列?我很高兴您不提倡使用varchar(max)用于您的字符串列。

如果您需要索引一列,或者如果您使用一列作为主键并且它有外键引用,这是特别谨慎的建议。SQL Server 使用其查询优化器中每一列的大小来了解查询处理的估计内存需求。使用过大的列可能会损害性能。

过大的列上的索引可能会导致生成错误:

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);
Run Code Online (Sandbox Code Playgroud)

尝试创建上面的索引会导致此警告:

警告!最大密钥长度为 900 字节。索引“IX_WideIndex_01”的最大长度为 1110 字节。对于某些大值组合,插入/更新操作将失败。

900 字节是聚集索引(以及 SQL Server 2012 及更早版本上的非聚集索引)的最大键大小。1700 字节是较新版本的 SQL Server 上非聚集索引的最大键大小。如果您设计具有通用宽度的列,例如 (255),您可能会比预期更频繁地遇到此警告。

如果您对存储内部结构感兴趣,可以使用以下小测试来更好地了解 SQL Server 如何存储未压缩的行存储数据。

首先,我们将创建一个表,我们可以在其中存储各种大小的列:

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
);
Run Code Online (Sandbox Code Playgroud)

现在我们将插入一行:

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

此查询使用未记录且不受支持的函数sys.fn_RowDumpCrackersys.fn_PhyslocCracker显示有关该表的一些有趣的详细信息:

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

输出将类似于以下内容:

?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ???
? 分区 ID ? 列名?是内罗吗?稀疏 ? 是RecordPrefixCompressed 吗?是符号?前缀字节?行长度?文件 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 ?
?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ?????????????????????????????????????????????????????? ???

如您所见,显示InRowLength了每个值以及每行的物理存储位置——“file_id”、“page_id”和“slot_id”。

如果我们从上面的查询结果中取出file_idpage_id值并运行DBCC PAGE它们,我们可以看到实际的物理页面内容:

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
Run Code Online (Sandbox Code Playgroud)

我的机器的结果是:

页:(1:1912)


缓冲:


BUF @0x00000000FF5B2E80

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

页眉:


页@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
元数据:AllocUnitId = 2233785421652951040                              
元数据:PartitionId = 1945555045333008384 元数据:IndexId = 0
元数据: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 DB 片段 ID = 1                      

分配状态

GAM (1:2) = 已分配 SGAM (1:3) = 未分配 PFS (1:1) = 0x41 已分配 50_PCT_FULL
差异 (1:6) = 未更改 ML (1:7) = 未 MIN_LOGGED           

插槽 0 偏移 0x60 长度 556

记录类型 = PRIMARY_RECORD 记录属性 = NULL_BITMAP VARIABLE_COLUMNS
记录大小 = 556                   
内存转储@0x000000005145A060

0000000000000000:30000400 03000003 002d002c 012c0231 31313131 0........-.,..11111
0000000000000014:31313131 31313131 31313131 31313131 31313131 11111111111111111111
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 333333333333333333333
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

插槽 0 列 1 偏移量 0xf 长度 30 长度(物理)30

varchar30 = 1111111111111111111111111111111                               

插槽 0 列 2 偏移量 0x2d 长度 255 长度(物理) 255

varchar255 = 22222222222222222222222222222222222222222222222222222222222222222222222222222222222222
2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
22222222222222222222222222222222222222222                               

插槽 0 列 3 偏移量 0x12c 长度 256 长度(物理)256

varchar256 = 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333
3333333333333333333333333333333333333333333                              


Joe*_*ish 21

其他人已经指出,存储长度所需的字节数是固定的。我想在你的问题中关注这一部分:

在这一点上它不再重要了吗?

您将问题标记为企业版,这通常意味着您将拥有大量数据。在实践中,每行一个字节的差异通常并不重要。例如,下表填满一VARCHAR(255)列,在磁盘上占用 143176 KB 空间:

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';
Run Code Online (Sandbox Code Playgroud)

结果:

??????????????????????????????????????????????????????????????????????????????????
?   name    ?         rows         ? reserved  ?   data    ? index_size ? unused ?
??????????????????????????????????????????????????????????????????????????????????
? V255_FULL ? 500000               ? 143176 KB ? 142888 KB ? 8 KB       ? 280 KB ?
??????????????????????????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

让我们创建第二个表,其中包含一个完全填充的VARCHAR(256)列。每行至少要多占用一个字节,对吗?

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';
Run Code Online (Sandbox Code Playgroud)

结果:

??????????????????????????????????????????????????????????????????????????????????
?   name    ?         rows         ? reserved  ?   data    ? index_size ? unused ?
??????????????????????????????????????????????????????????????????????????????????
? V256_FULL ? 500000               ? 143176 KB ? 142888 KB ? 8 KB       ? 280 KB ?
??????????????????????????????????????????????????????????????????????????????????
Run Code Online (Sandbox Code Playgroud)

碰巧两个表占用相同的空间量。每个 8k 页面上适合相同数量的行。很高兴您愿意花时间优化您的应用程序,但我怀疑您最好专注于不同的领域。


Dav*_*oft 7

varchar 的声明大小很少对性能产生影响1。数据实际上可能存储为具有页面压缩或行压缩的行存储。作为聚集列存储,或作为内存优化表。这些中的每一个都有不同的性能权衡,但声明 varchar(255) 还是 varchar(256) 无关紧要。


1 - 在特定情况下,字符变化列的大小会影响性能;布伦特·奥扎尔 (Brent Ozar)在这里写了一篇很棒的文章