文本和变长字符串(varchar或character varying)之间的区别

979
在PostgreSQL数据库中,text数据类型和character varying (varchar)数据类型有什么区别?根据文档说明,如果使用character varying而没有指定长度,该类型接受任何大小的字符串,后者是PostgreSQL的扩展。此外,PostgreSQL还提供了text类型,它存储任意长度的字符串。虽然text类型不属于SQL标准,但其他几个SQL数据库管理系统也具有该类型。那么这两种数据类型有什么区别呢?
13个回答

1174

在技术实现上,这些都是相同的,底层使用的是varlena(可变长度数组)。

可以查看Depesz的这篇文章:http://www.depesz.com/index.php/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text/

以下是一些要点:

综上所述:

  • char(n) - 当处理短于n的值时需要太多的空间(将它们填充到n),并且由于添加尾随空格可能导致微妙的错误,更改限制也会出现问题
  • varchar(n) - 在实时环境中更改限制有问题(需要独占锁定以修改表)
  • varchar - 就像text
  • text - 对我来说是最好的选择 - 因为它没有上述数据类型的问题,并且比varchar更具有区分性的名称

该文章进行了详细测试,以显示4种数据类型的插入和选择性能相似。它还详细研究了在需要时限制长度的替代方法。基于函数的约束或域提供即时增加长度约束的优势,并且基于字符串长度约束的缩小是罕见的,Depesz得出结论:它们通常是限制长度的最佳选择之一。


67
这是一篇很棒的文章。你可以说:“如果这篇文章被删除了,你能否提取一些摘录?”我试图简要概括文章的内容和结论。我希望这足以缓解您的顾虑。 - jpmc26
35
严格来说,最初的回答是在说“在底层,一切都是 varlena”,这是非常有用的信息,可以将这个答案和仅包含链接的答案区分开来。 - Bruno
44
需要翻译的内容:One thing to keep in mind with a limitless string is that they open the potential for abuse. If you allow a user to have a last name of any size, you may have someone storing LARGE amounts of info in your last name field. In an article about the development of reddit, they give the advise to "Put a limit on everything".当使用无限长度字符串时,需注意其存在潜在的滥用可能性。如果允许用户输入任意长度的姓氏,则有可能有人会在姓氏字段中存储大量信息。在一篇reddit开发相关的文章中,作者建议对所有内容都设定限制。 - Mark Hildreth
10
@MarkHildreth 好观点,不过现在这种限制通常是在应用程序更深层次上强制执行的,这样规则(以及尝试违反/重试)就可以被界面平稳地处理。如果有人仍然想在数据库中做这种事情,他们可以使用约束。请参见 http://blog.jonanin.com/2013/11/20/postgresql-char-varchar/ ,其中包括“使用TEXT和约束创建比VARCHAR更灵活的字段的示例”。 - Ethan
63
这个评论获得了如此之多的投票真是令人不安。因为它允许我输入任意长度的字符串,text 绝不能被认为是“优于 varchar 的胜者”,相反,你应该深入考虑要存储什么类型的数据,然后才允许用户输入字符串。绝不能采用“前端处理”这种策略,这是非常不可接受和糟糕的开发实践。现在看到很多开发人员都这样做,真的很惊讶。 - José L. Patiño
显示剩余16条评论

154
正如文档中所指出的"字符类型",varchar(n)char(n)text都是以相同的方式存储的。唯一的区别在于,如果给定长度,则需要额外的循环来检查长度,并且如果需要填充char(n),则需要额外的空间和时间。
然而,当您只需要存储单个字符时,使用特殊类型"char"(保留双引号——它们是类型名称的一部分)会稍微提高性能。您可以更快地访问字段,并且没有存储长度的开销。
我刚刚创建了一个包含1,000,000个随机小写字母"char"的表。查询频率分布(select count(*), field ... group by field)需要约650毫秒,而在相同的数据上使用text字段需要约760毫秒。

30
技术上说,引号不是类型名称的一部分。它们是为了将它与“char”关键字区分开来而需要的。 - Jasen
46
从技术上讲,@Jasen你是正确的... 当然,这是最好的正确。 - JohannesH
1
数据类型"char"不是char吗?在PostgreSQL 11+的今天仍然有效吗?是的:*"类型"char"(注意引号)与char(1)不同,它只使用一个字节的存储空间。它在系统目录中内部用作简单枚举类型。"*guide/datatype-character - Peter Krauss

113

2016年基准测试更新(pg9.5+)

并使用“纯SQL”基准测试(无任何外部脚本)

  1. 使用任何UTF8字符串生成器

  2. 主要基准测试:

2.1. 插入

2.2. 选择比较和计数


CREATE FUNCTION string_generator(int DEFAULT 20,int DEFAULT 10) RETURNS text AS $f$
  SELECT array_to_string( array_agg(
    substring(md5(random()::text),1,$1)||chr( 9824 + (random()*10)::int )
  ), ' ' ) as s
  FROM generate_series(1, $2) i(x);
$f$ LANGUAGE SQL IMMUTABLE;

准备具体测试(示例)

DROP TABLE IF EXISTS test;
-- CREATE TABLE test ( f varchar(500));
-- CREATE TABLE test ( f text); 
CREATE TABLE test ( f text  CHECK(char_length(f)<=500) );

进行基本测试:

INSERT INTO test  
   SELECT string_generator(20+(random()*(i%11))::int)
   FROM generate_series(1, 99000) t(i);

还有其他的测试,

CREATE INDEX q on test (f);

SELECT count(*) FROM (
  SELECT substring(f,1,1) || f FROM test WHERE f<'a0' ORDER BY 1 LIMIT 80000
) t;

我在许多机器和许多测试后得出的结果经平均后都是相同的(统计学上小于标准偏差)。

建议

  • 使用text数据类型,
    避免使用旧的varchar(x),因为有时它不是标准的,例如在CREATE FUNCTION子句中varchar(x)varchar(y)

  • 通过在CREATE TABLE中使用CHECK子句来表达限制(具有相同的varchar性能!)
    例如:CHECK(char_length(x)<=10)
    通过在 INSERT/UPDATE 中忽略可忽略的性能损失,您也可以控制范围和字符串结构
    例如:CHECK(char_length(x)>5 AND char_length(x)<=20 AND x LIKE 'Hello%')


那我将所有的列都设为varchar而不是text也没关系吗?虽然有些只有4-5个字符,肯定不到255。 - trench
2
@trench 是的,这没有关系。 - FuriousFolder
2
很酷,我重新进行了安全处理,并且我将所有东西都转换为文本。这样做效果非常好,而且很容易快速添加数百万条历史记录。 - trench
@trench和读者们:唯一的例外是更快的数据类型“char”,即使在PostgreSQL 11+时代,“char”也不是“char”。正如指南/数据类型字符所说:“类型‘char’(注意引号)与char(1)不同,它只使用一个字节的存储空间。它在系统目录中内部用作简单枚举类型。” - Peter Krauss
5
在2019年,使用pg11仍然有效:text>varchar(n)>text_check>char(n)。 - Olivier Refalo

55

在PostgreSQL手册中

这三种类型之间没有性能差异,除了使用填充空格类型时增加的存储空间以及在存储到长度受限列时检查长度所需的一些额外CPU周期。虽然在其他一些数据库系统中,character(n)具有性能优势,但在PostgreSQL中并没有这样的优势;事实上,由于其额外的存储成本,character(n)通常是三种类型中最慢的。在大多数情况下,应该使用text或character varying。

我通常使用text

参考资料:http://www.postgresql.org/docs/current/static/datatype-character.html


39

在我看来,varchar(n)有其自身的优点。是的,它们都使用相同的底层类型等等。但是,应该指出的是,在PostgreSQL中,索引在每行中具有2712字节的大小限制。

TL;DR: 如果您使用text类型没有约束条件并对这些列进行索引,很可能会命中某些列的限制,并且在尝试插入数据时出现错误,但是如果使用varchar(n),则可以避免这种情况。

更多细节:问题在于,当为text类型或varchar(n)创建索引时,PostgreSQL不会提供任何异常,其中n大于2712。但是,当尝试插入压缩大小大于2712的记录时,它将给出错误。这意味着您可以轻松地插入由重复字符组成的100,000个字符的字符串,因为它将被压缩到远低于2712以下,但是您可能无法插入一些具有4000个字符的字符串,因为压缩后的大小大于2712字节。使用varchar(n),其中n不是远大于2712,您就可以避免出现这些错误。


尝试为文本创建索引时,后续的Postgres错误仅适用于varchar(没有(n)版本)。 尽管仅在嵌入式Postgres上进行了测试。 - arntg
2
参考:https://dev59.com/UlkS5IYBdhLWcg3wXFrc,其中有指向PostgreSQL Wiki的链接:https://wiki.postgresql.org/wiki/FAQ#What_is_the_maximum_size_for_a_row.2C_a_table.2C_and_a_database.3F,该页面将最大行大小定义为400GB,因此看起来2712字节每行的限制是错误的。 数据库的最大大小?无限制(存在32TB的数据库) 表的最大大小?32TB 行的最大大小?400GB 字段的最大大小?1GB 表中的最大行数?无限制 - Bill Worthington
@BillWorthington,你发布的数字没有考虑到索引的情况。2712字节是B树的最大限制,这是一项实现细节,因此您在文档中找不到它。但是,您可以轻松测试它或通过搜索“postgresql index row size exceeds maximum 2712 for index”来谷歌它。 - yakya
我对PostgeSQL还不是很熟悉,所以并不是专家。我正在一个项目中工作,想要将新闻文章存储在表格的一列中。看起来文本列类型是我要使用的。2712字节的总行大小听起来对于一个被认为接近Oracle水平的数据库来说太低了。我是否正确理解您是在指大型文本字段的索引?我并不是在挑战或争论您,只是想了解真正的限制。如果没有涉及索引,那么行限制是否像维基上所述的400GB?感谢您的快速回复。 - Bill Worthington
1
@BillWorthington 你应该研究一下全文搜索。可以查看这个链接 - yakya

29

text和varchar具有不同的隐式类型转换。我注意到的最大影响是对尾随空格的处理。例如...

select ' '::char = ' '::varchar, ' '::char = ' '::text, ' '::varchar = ' '::text

返回的结果是true, false, true而不是你可能期望的true, true, true


2
这怎么可能?如果a = b且a = c,则b = c。 - Lucas Silva
3
经过测试,它确实是真的。不可能,但却是真的。非常、非常奇怪。 - Ellert van Koperen
3
之所以 = 运算符不仅比较值,还会进行一些转换来找到值的公共类型。这是各种语言中非常常见的行为,并且所使用的转换方式也因语言而异。例如在 JavaScript 中,你可以看到 [0 == '0.0', 0 == '0', '0.0' == '0'] -> [true, true, false] - Arsen7

12
传统和现代之间的区别在于:传统上,您需要指定每个表列的宽度。如果您指定的宽度过大,会浪费昂贵的存储空间,但如果指定的宽度过小,某些数据将无法适应。然后,您需要调整列的大小,并且必须更改许多相关的软件,修复引入的错误,这一切都非常繁琐。
现代系统允许使用动态存储分配来存储无限字符串,因此即使有大量的字符串,也不会浪费太多存储小数据项的空间。
虽然许多编程语言已经采用了无限大小的数据类型“字符串”,如C#,JavaScript,Java等,但像Oracle这样的数据库却没有。
现在,PostgreSQL支持“文本”,但很多程序员仍然习惯于使用VARCHAR(N),并且有以下理由:是的,文本与VARCHAR相同,只是VARCHAR可以添加限制N,所以VARCHAR更灵活。
您也可以这样思考:
既然我们可以简化生活,只需使用简单的“TEXT”,为什么还要费劲地使用冗长的“VARCHAR WITHOUT N”呢?
在我与Oracle的最近几年中,我很少使用CHAR(N)或VARCHAR(N)。因为Oracle没有无限字符串类型,所以我在大多数字符串列中使用VARCHAR(2000),其中2000曾经是VARCHAR的最大值,并且在实际用途上与“无限”几乎没有什么区别。
现在我正在使用PostgreSQL,我认为TEXT是真正的进步。不再强调CHAR类型的VAR特性。不再强调使用没有N的VARCHAR。此外,使用TEXT比VARCHAR节省3个按键。
年轻的同事们现在长大了,甚至不知道在旧时代没有无限字符串。就像在大多数项目中他们不需要了解汇编编程一样。
更新:Azure类型String
显然,Azure SQL的现代系统有一个名为String的通用文本类型,类似于PostgreSQL的Text类型,但限制为仅500个字符,无法配置。在Azure中,String类型似乎比具有4000个字符限制的Varchar(N)更常用。这是进步吗?

1
非常有信息量的回答,谢谢! - devnull Ψ
1
@aderchox 我猜你的意思是评论:Varchar没有N是为了向后兼容... - undefined
1
编辑:所以要点就是,只需使用TEXT。保留VARCHAR而不加N是为了向后兼容的原因。(是的,我现在已经编辑过了,谢谢:)。) - undefined

8

以下是http://www.sqlines.com/postgresql/datatypes/text提供的良好解释:

TEXT和VARCHAR(n)之间唯一的区别在于,您可以限制VARCHAR列的最大长度,例如,VARCHAR(255)不允许插入超过255个字符的字符串。

TEXT和VARCHAR的上限均为1 Gb,在它们之间没有性能差异(根据PostgreSQL文档)。


6

有点离题:如果你在使用Rails,那么网页的标准格式可能会有所不同。对于数据输入表单,text框是可以滚动的,但是character varying(Rails string)框只有一行。展示视图则根据需要长短不一。


3
如果您只使用TEXT类型,当使用AWS数据库迁移服务时可能会遇到问题:
大对象(LOBs)被使用,但目标LOB列不可为空
由于它们的大小未知且有时很大,大对象(LOBs)需要比标准对象更多的处理和资源。为了帮助调整包含LOBs的系统的迁移,AWS DMS提供以下选项
如果您只是在所有方面都坚持使用PostgreSQL,那么可能没问题。但是,如果您打算通过ODBC或DMS等外部工具与数据库进行交互,您应该考虑不要将TEXT用于所有内容。

同样适用于ODBC:Crystal Reports将文本视为“备忘录”,即使它是外键,也不允许在其上进行任何联接。 Varchar(有限或无限)可以正常工作。 - btraas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接