在SQL中通过VARCHAR选择CHAR的用例是什么?


270

我意识到,如果我所有值均为固定宽度,则建议使用CHAR。但是,那又如何呢?为了安全起见,为什么不为所有文本字段选择VARCHAR?

Answers:


386

如果所有行的长度接近相同,通常选择CHAR。当长度变化很大时,请选择VARCHAR。由于所有行的长度相同,因此CHAR也可能会快一些。

它因数据库的实现而异,但是通常VARCHAR除了实际数据外,还会使用一个或两个以上的字节存储空间(用于长度或终止)。因此(假设您使用的是一个字节的字符集),则存储单词“ FooBar”

  • CHAR(6)= 6个字节(无开销)
  • VARCHAR(10)= 8个字节(2个字节的开销)
  • CHAR(10)= 10个字节(4个字节的开销)

底线是CHAR可以更快,更节省空间对于相对相同长度(两个字符的长度差异以内)的数据。

注意:Microsoft SQL对于VARCHAR有2个字节的开销。这可能因数据库而异,但是通常至少需要1个字节的开销来指示VARCHAR上的长度或EOL。

正如Gaven在评论中指出的那样,如果您使用的是多字节可变长度字符集(如UTF8),则CHAR将存储存储字符数所需的最大字节数。因此,如果UTF8最多需要3个字节来存储字符,那么即使仅存储latin1字符,CHAR(6)也将固定为18个字节。因此,在这种情况下,VARCHAR成为更好的选择。


20
另一个原因是页面拆分和碎片化。我有一个IDEN PK的表,由于varchar列上的页面拆分而导致99%的碎片。一个非常活跃的表,根据应用程序的性质,将创建然后填充一个新行空行。Char修复了碎片问题。
狗仔队

12
@Jim McKeeth-仅当您使用latin1字符集时,这些计算才是正确的。由于这些天大多数人都应该使用utf8,因此您的CHAR列将平均使用3倍的空间作为VARCHAR,该空间主要在基本多语言平面中存储字符。
Gavin Towey 2014年

11
@JimMcKeeth是的,完全正确。由于CHAR是固定长度的,因此必须将其固定在可以使用的最大可能空间上。在UTF8中,每个字符3个字节。对于varchar,可以根据需要免费使用每个字符1-3个字节。这是MySQL手册:dev.mysql.com/doc/refman/5.0/en/charset-unicode-utf8.html
加文Towey

3
字符串FooBar和varchar(100)vs char(100)有什么区别?我认为这样可以更好地显示差异,是吗?没有?
内诺普(Nenotlep),2014年

4
@GavinTowey SQLSERVER使用UCS-2作为其NCHAR和NVARCHAR数据类型。每个字符总是两个字节。
1510年

69

如果您正在与我合作,并且正在与Oracle合作,那么我可能会让您varchar在几乎每种情况下都使用。假设char使用的处理能力比varchar现在还低……但是……随着时间的流逝,数据库引擎会变得更好,而这种一般规则将成为未来的“神话”。

另一件事:我从未见过性能问题,因为有人决定使用varchar。您将可以更好地利用时间编写优质的代码(对数据库的调用更少)和有效的SQL(索引如何工作,优化器如何制定决策,为什么要这样做)。exists比平时更快in...)。

最终的想法:我已经看到了使用的各种问题CHAR,人们在应该寻找“”时寻找“”,或者人们在寻找“ FOO(此处是一堆空格)”时寻找“ FOO”的人们,或者没有修剪尾随空白的人,或者是Powerbuilder的错误,使它从Oracle过程返回的值最多增加2000个空白。


20
我有点不同意您的第一段,因为char可能会提供一个提示,可能对优化器(甚至是将来的优化器)有用,并且可能有助于传达本专栏的意图。但是您的第三段为+1。我讨厌所有多余的空间。一个字段应该只存储我输入的内容,而无需所有[重复的]填充。基本上,如果所有数据现在都是永久的,那么只要所有数据的长度都完全相同,我就使用char。当然,这很少见,通常是char(1)。
Jeffrey L Whitledge,2009年

char还向分析人员和开发人员提供了提示...这个东西是x的字符数...。如果他们正在考虑以其他格式对它进行序列化,那可能会有所帮助。(我被迫在不具有uuid类型的mssql中的char中存储md5校验和,而且我从来都不希望<32个字节...也对该列施加了约束)。
joefromct

31

除性能优势外,CHAR还可用于指示所有值都应具有相同的长度,例如,美国州缩写的列。


或国家/地区代码-可以帮助区分使用2或3个字符的国家/地区代码缩写
Dan Field,

如果确实是固定长度,则应该强制执行该约束。尽管如果使用CHAR,则必须确保约束折扣填充。
jpmc26

18

Char快一点,因此如果您知道的列长度一定,请使用char。例如,存储性别已知的(M)ale /(F)emale /(U)n,或存储美国州的2个字符。


4
不确定这是一个很好的答案,因为ENUM通常更有意义,尽管我不确定(在MySQL之外)该类型的支持范围。
鲍比·杰克

在我看来,状态集不一定是不变的,因此char(2)似乎比枚举更合适。
Kearns

1
@Bobby Jack-我不知道任何特定的SQL枚举实现的具体细节,但是请记住,以4字节整数存储的枚举可能需要比char(1)或char(2)列更多的空间。相同的数据。从某种意义上说,枚举在解释方面更合乎逻辑,并且可能令人信服,但是RDBMS系统中的所有事物在某种程度上都是抽象的,并且要遵循为表定义的谓词。
Jeffrey L Whitledge,2009年

4
不好的例子,ENUM最适合这种情况。更好的例子是3个字母的国际航空运输协会机场代码
Andrew G. Johnson

5
@Andrew,并非所有数据库都支持ENUM数据类型。例如,MSSQLServer不会。同样,存储为int的ENUM占用4个字节。CHAR(1)占用1个字节,而NCHAR(1)占用2个字节。
Jarrett Meyer

17

NChar或Char的性能是否优于其var替代品?

好问题。在某些情况下,简单的答案是肯定的。让我们看看这是否可以解释。

显然,我们都知道,如果我创建一个具有varchar(255)列的表(我们将此列命名为myColumn),并插入一百万行,但每行只向myColumn放入几个字符,则该表将小得多(总的来说) (如果我将myColumn创建为char(255),则存储引擎所需的数据页数)。每当我在该表上执行操作(DML)并请求很多行时,当myColumn为varchar时,它就会更快,因为我不必在末尾移动所有这些“多余”空间。移动,例如在SQL Server执行内部排序(例如在不重复或合并操作期间)或在查询计划期间选择合并等时。

但是使用varchar会有一些开销。SQL Server必须在每个行上使用两个字节的指示符(开销)来知道特定行的myColumn中有多少个字节。并不是出现问题的多余2个字节,而是必须“解码”每行myColumn中数据的长度。

以我的经验,在要加入查询的列上使用char而不是varchar是最有意义的。例如,表的主键或其他将要建立索引的列。人口统计表上的CustomerNumber或解码表上的CodeID或订单表上的OrderNumber。通过使用char,查询引擎可以更快地执行联接,因为它可以(确定性地)执行直接指针算术,而不必在读取页面时将其指针移动可变数量的字节。我知道我可能在最后一句话上失去了你。SQL Server中的联接基于“谓词”的概念。谓词是条件。例如myColumn = 1或OrderNumber <500。

因此,如果SQL Server正在执行DML语句,并且谓词或要连接的“键”是固定长度(char),则查询引擎不必执行太多工作就可以将一个表中的行与另一个表中的行进行匹配。另一张桌子。不必找出行中的数据有多长,然后沿着字符串查找结尾即可。所有这一切都需要时间。

现在请记住,这很容易实现不佳。我已经看到char用于在线系统中的主键字段。宽度必须保持较小,即char(15)或合理的值。它在联机系统中效果最好,因为您通常只检索或上载少量行,因此不得不“整理”结果集中的尾随空格是一项微不足道的任务,而不是必须加入数百万行从一个表的行到另一表的数百万行。

联机系统上CHAR比varchar有意义的另一个原因是它减少了页面拆分。通过使用char,您实际上是在“保留”(并浪费)该空间,因此,如果用户以后出现,并将更多数据放入该列中,SQL已经为其分配了空间。

使用CHAR的另一个原因与第二个原因相似。如果程序员或用户对数百万行进行了“批处理”更新,例如在note字段中添加了一些句子,那么您就不会在半夜接到来自DBA的电话,不知道为什么他们的驱动器已满。换句话说,它导致数据库大小的增长更加可预测。

因此,在线(OLTP)系统可以从char受益于varchar的3种方式。在仓库/分析/ OLAP场景中,我几乎不会使用char,因为通常您拥有如此多的数据,所有这些char列都可能导致大量浪费的空间。

请记住,char可以使您的数据库更大,但是大多数备份工具都具有数据压缩功能,因此您的备份大小通常与使用varchar相同。例如LiteSpeed或RedGate SQL Backup。

另一个用途是在为将数据导出到固定宽度文件而创建的视图中。假设我必须将一些数据导出到平面文件中,以供大型机读取。它是固定宽度(不定界)。我喜欢将数据作为“ varchar”存储在“ staging”表中(从而减少了数据库空间),然后使用视图将所有内容都转换为等同于char的字符,其长度对应于该列固定宽度的宽度。例如:

create table tblStagingTable (
pkID BIGINT (IDENTITY,1,1),
CustomerFirstName varchar(30),
CustomerLastName varchar(30),
CustomerCityStateZip varchar(100),
CustomerCurrentBalance money )

insert into tblStagingTable
(CustomerFirstName,CustomerLastName, CustomerCityStateZip) ('Joe','Blow','123 Main St Washington, MD 12345', 123.45)

create view vwStagingTable AS
SELECT CustomerFirstName = CAST(CustomerFirstName as CHAR(30)),
CustomerLastName = CAST(CustomerLastName as CHAR(30)),
CustomerCityStateZip = CAST(CustomerCityStateZip as CHAR(100)),
CustomerCurrentBalance = CAST(CAST(CustomerCurrentBalance as NUMERIC(9,2)) AS CHAR(10))

SELECT * from vwStagingTable

这很酷,因为在内部,由于使用varchar,我的数据占用的空间更少。但是,当我使用DTS或SSIS甚至只是从SSMS剪切并粘贴到记事本时,我都可以使用该视图并获得正确数量的尾随空格。在DTS中,我们曾经有一个功能,该死的,我忘了我认为它被称为“建议列”之类的东西。在SSIS中,您不能再这样做了,您必须繁琐地定义平面文件连接管理器。但是,由于您已经设置了视图,因此SSIS可以知道每一列的宽度,并且可以在构建数据流任务时节省大量时间。

因此,底线...使用varchar。使用char的原因非常少,仅出于性能原因。如果您的系统具有数以百万计的行,那么如果谓词是确定性的(char),您会看到明显的不同,但是对于大多数使用char的系统来说,这只是浪费空间。

希望能有所帮助。杰夫


您是说固定聊天不仅占用存储空间,还占用了您说的传输或“移动”空间?例如从DB Server到我的客户端?什么时候丢失这些空字节?
红豌豆

9

有性能上的好处,但是这里没有提到:行迁移。使用char时,您可以预先保留整个空间。假设您有一个char(1000),并且存储了10个字符,则将用完所有1000个字符。在varchar2(1000)中,您将仅使用10个字符。当您修改数据时,问题就来了。假设您将列更新为现在包含900个字符。扩展varchar的空间可能在当前块中不可用。在这种情况下,DB引擎必须将该行迁移到另一个块,并在原始块中创建一个指向新块中新行的指针。要读取此数据,数据库引擎现在将必须读取2个块。
没有人可以明确地说varchar或char更好。有时间权衡的余地,并考虑是否要更新数据,尤其是在很有可能增长数据的情况下。


我认为您的帖子中有错字-varchar2(1000)不应该是CHAR(1000)吗?
Matt Rogish

8

早期的性能优化与使用最佳实践类型的规则有所不同。如果要创建新表,在该表中始终具有固定长度的字段,则使用CHAR是有意义的,在这种情况下,应使用它。这不是早期的优化,而是实施经验法则(或最佳实践)。

即-如果您有2个字母的状态字段,请使用CHAR(2)。如果您有一个带有实际状态名称的字段,请使用VARCHAR。


8

我会选择varchar,除非该列存储的固定值如美国州代码-始终为2个字符,并且有效的美国州代码列表不会经常更改:)。

在其他所有情况下,即使像存储哈希密码(固定长度)一样,我也会选择varchar。

为什么-char类型的列始终用空格填充,这使得my_column列定义为char(5),其内部比较值为:ABC:

my_column = 'ABC' -- my_column stores 'ABC  ' value which is different then 'ABC'

假。

功能可能会在开发过程中导致许多令人讨厌的错误,并使测试更加困难。


1
至少在MSSQL Server中,“ abc” =“ abc”。我从没想过是喜欢还是讨厌该功能……
Mark Brackett

一个很好的了解焦炭的填充位置填充
爱德华

6

如果您在该字段中的所有数据值长度相同,则CHAR占用的存储空间将比VARCHAR少。现在,也许在2009年,如果将VARCHAR转换为CHAR,则800GB的数据库在所有意图和用途上都与810GB相同,但是对于短字符串(1个或2个字符),CHAR仍然是业界“最佳实践”。

现在,如果您查看大多数数据库提供的各种各样的数据类型,甚至仅为整数(位,tiny,int,bigint)提供的数据,就有理由选择一种。每次简单地选择bigint实际上实际上是对该领域的目的和用途的了解。如果一个字段仅代表一个人的年龄(岁),那么bigint就是过大了。现在,它不一定是“错误的”,但是效率不高。

但这是一个有趣的论点,并且随着数据库的不断改进,可以说CHAR与VARCHAR的相关性降低了。


4

我支持吉姆·麦基思的评论。

此外,如果表只有CHAR列,则索引编制和全表扫描速度更快。基本上,如果优化器仅具有CHAR列,则优化器将能够预测每个记录的大小,同时它需要检查每个VARCHAR列的大小值。

此外,如果将VARCHAR列更新为大于其先前内容的大小,则可能会强制数据库重建其索引(因为您已强制数据库将记录物理移动到磁盘上)。而使用CHAR列则永远不会发生。

但是除非表很大,否则您可能不会在意性能下降。

记住吉克斯特拉的明智之言。早期的性能优化是万恶之源。


4
您的评论有些猜测。我一次又一次地看到像这样的假设得到检验,而事实恰恰相反。问题是许多工程师会将这样的信息当作福音。请大家创建反映您实际情况的测试用例。
伊桑·波斯特

伊桑是完全正确的。因此,这取决于您使用的实现,而没有引用实际的(Product,Version),这是完全没有用的。
David Schmitt,

更新CHAR列时,索引也需要更新。在这方面,更新VARCHAR或CHAR列没有区别。考虑更新FOOBAR
a_horse_with_no_name 2014年

4

许多人指出,如果您知道使用CHAR的值的确切长度会有一些好处。但是,尽管今天将CHAR(2)存储在美国各州是一件很棒的事情,但是当您从销售中得知“我们刚刚在澳大利亚进行了首次销售”时,您就处于痛苦中。我总是高估我认为字段将需要多长时间,而不是做出“准确”的猜测来涵盖未来的事件。VARCHAR将在这方面给我更多的灵活性。


3

我认为在您的情况下,可能没有理由不选择Varchar。它为您提供了灵活性,正如许多受访者所提到的那样,现在的表现是如此出色,除非在非常特殊的情况下,我们的普通人(与Google DBA相对)不会注意到两者之间的差异。

当涉及到数据库类型时,一个值得注意的有趣事情是sqlite(一种性能非常出色的流行小型数据库)将所有内容以字符串和类型的形式实时存储到数据库中。

我总是使用VarChar,通常使它比我可能需要的大得多。例如。正如您所说的,“姓氏”为50。


3

我永远不会使用字符。我与许多人进行过辩论,他们总是提出陈腐的陈词滥调,即char更快。好吧,我说,快多少?我们在这里谈论的是毫秒,秒,如果是多少秒?您是在告诉我,因为有人声称它要快几毫秒,我们应该在系统中引入大量难以修复的错误吗?

因此,您会遇到以下一些问题:

每个字段都会被填充,因此您最终将永远拥有到处都是RTRIMS的代码。对于较长的字段,这也是巨大的磁盘空间浪费。

现在,假设您有一个典型的char字段示例,该字符字段只有一个字符,但该字段是可选的。如果有人将一个空字符串传递给该字段,它将成为一个空格。因此,当另一个应用程序/进程查询它时,如果不使用rtrim,它们将得到一个空格。我们有xml文档,文件和其他程序,仅在可选字段中显示一个空格,然后破坏内容。

因此,现在必须确保将空值而不是空字符串传递给char字段。但这不是null的正确使用。这是null的使用。假设您从供应商处获取文件

名称|性别|城市

鲍勃||洛杉矶

如果未指定性别,则在表中输入Bob,空字符串和Los Angeles。现在,假设您获取了文件,并且文件格式发生了变化,并且不再包含性别,而是过去。

名称|城市

鲍勃|西雅图

现在好了,因为不包括性别,所以我将使用null。Varchars支持这一点而没有问题。

另一方面,字符不同。您总是必须发送空值。如果您发送空字符串,最终将得到一个带有空格的字段。

在大约20年的开发过程中,我可以继续解决chars必须修复的所有错误。


2

在计算列值的实际所需大小并为Varchar分配空间时,会产生一些小的处理开销,因此,如果您确定该值将始终保留多长时间,则最好使用Char并避免命中。


2

这是经典的空间与性能的权衡。

在MS SQL 2005中,Varchar(对于每个字符需要两个字节(即中文)的劳苦语,则为NVarchar)是可变长度的。如果在将数据写入硬盘后添加到该行中,它将在不连续的位置将数据定位到原始行,并导致数据文件碎片化。这会影响性能。

因此,如果空间不成问题,那么Char的性能会更好,但是如果您想减小数据库的大小,那么varchars会更好。


2

碎片化。Char保留空间,而VarChar不保留空间。可能需要分页以适应对varchar的更新。


由于许多其他因素,更新CHAR列时可能会发生页面拆分。
里克·詹姆斯

1

使用varchar值时,SQL Server每行需要额外的2个字节来存储有关该列的某些信息,而如果使用char,则不需要这样做,除非您


0

在某些SQL数据库中,VARCHAR将被填充到其最大大小以优化偏移量,这是为了加快全表扫描和索引的速度。

因此,与CHAR(200)相比,使用VARCHAR(200)不会节省任何空间


3
哪些数据库以这种方式实现VARCHAR?
Troels Arvin

5
认真地说,是什么数据库以这种方式实现的?您描述的内容通常适用于CHAR,而不适用于VARCHAR。
理查德·西蒙斯(RichardSimões)2009年

如果同一表中有char和varchar,mysql会将varchar转换为char。
Malfist

我对MySQL注释的解释是,这不适用于主表存储,但可能与临时表有关。用于对数据进行分组/排序。dev.mysql.com/doc/refman/8.0/en/char.html stackoverflow.com/questions/262238/...
托马斯W¯¯

0

使用CHAR(NCHAR)和VARCHAR(NVARCHAR)带来了数据库服务器存储数据的方式的差异。第一个介绍尾随空白;在SQL SERVER函数中与LIKE运算符一起使用时遇到问题。因此,我必须始终使用VARCHAR(NVARCHAR)使其安全。

例如,如果我们有一个表TEST(ID INT,Status CHAR(1)),并且您编写了一个函数来列出具有某些特定值的所有记录,如下所示:

CREATE FUNCTION List(@Status AS CHAR(1) = '')
RETURNS TABLE
AS
RETURN
SELECT * FROM TEST
WHERE Status LIKE '%' + @Status '%'

在此函数中,我们希望当我们使用默认参数时,该函数将返回所有行,但实际上并非如此。将@Status数据类型更改为VARCHAR将解决此问题。


也可以通过ansi_padding更改值的获取方式
爱德华(Edward)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.