MySQL错误#1071 - 指定的键太长;最大键长度为767字节。

667

当我执行了以下命令:

ALTER TABLE `mytable` ADD UNIQUE (
`column1` ,
`column2`
);

我看到了这个错误信息:

#1071 - Specified key was too long; max key length is 767 bytes

列1和列2的信息:

column1 varchar(20) utf8_general_ci
column2  varchar(500) utf8_general_ci

我认为varchar(20)只需要21个字节,而varchar(500)只需要501个字节。所以总共的字节数是522,少于767。那么为什么我会收到错误信息呢?

#1071 - Specified key was too long; max key length is 767 bytes

5
因为它不是520字节,而是2080字节,远远超过了767字节,所以你可以将列1设置为varchar(20),将列2设置为varchar(170)。如果您想要字符/字节等效,请使用latin1编码。 - Rahly
3
我认为你的计算有点错误。MySQL会使用额外的1或2个字节来记录值的长度:如果该列的最大长度小于或等于255字节,则使用1个字节,否则使用2个字节。utf8_general_ci编码每个字符需要3个字节,因此varchar(20)使用61个字节,varchar(500)总共使用1502个字节,加起来一共是1563个字节。 - lackovic10
3
mysql> 从information_schema.character_sets中选择maxlen和character_set_name,其中character_set_name为('latin1', 'utf8', 'utf8mb4')。
maxlen character_set_name
1 latin1
------ -------------------
3 utf8
------ -------------------
4 utf8mb4
- lackovic10
26
如果你需要一个字符/字节的等价物,可以使用Latin1。但是请不要这样做。使用Latin1会带来很多问题,你会后悔的。 - Stijn de Witt
请参考以下链接获取有关该问题的解决方案:https://dev59.com/s1gQ5IYBdhLWcg3w6IEH#52778785 - Pratik
37个回答

618

MySQL版本5.6(及更早版本)中,InnoDB表的前缀限制为767字节。对于MyISAM表,长度为1,000字节。该限制已在MySQL版本5.7(及以上版本)中增加至3072字节。

您还需要注意,如果在一个大的char或varchar字段上设置索引,并且该字段是utf8mb4编码,则必须将最大索引前缀长度767字节(或3072字节)除以4,结果为191。这是因为utf8mb4字符的最大长度为四个字节。对于utf8字符,它的长度为三个字节,导致最大索引前缀长度为255(或减去空终止符,254个字符)。

您可以选择在VARCHAR字段上设置较低的限制。

另一种选择(根据对此问题的回应)是仅获取列的子集,而不是整个数量,即:

ALTER TABLE `mytable` ADD UNIQUE ( column1(15), column2(200) );

调整您需要以获得应用密钥,但我想知道是否值得审查您的数据模型,以查看是否有可能进行改进,这将使您能够实现既定的业务规则,而不会遇到MySQL限制。

5
指定列的子集而不是整个数据量进行应用是一个好的解决方案。 - Steven
228
为什么长度明显低于限制的字段会超过长度限制,这并没有解释清楚。 - Cerin
7
我已尝试编辑@Cerin缺失的信息,其他人显然也认为信息遗漏了,但被拒绝并建议作为评论发布。如果想要理解为什么500 + 20>767,请查看Stefan Endrullis在Julien答案下的评论。 - Letharion
8
这可能会成为一个问题。例如:我有一个字段名称(255),并在名称中添加unique,名称长度为191,因为我使用的是utf8mb4。 如果我让我的用户使用'IJUE3ump5fiUuCi16jSofYS234MLschW4wsIktKiBrTPOTKBK6Vteh5pNuz1tKjy...aO500mlJs'作为他们的名字, 而另一个用户使用'IJUE3ump5fiUuCi16jSofYS234MLschW4wsIktKiBrTPOTKBK6Vteh5pNuz1tKjy...aO500mlJa'作为他们的名字, 唯一不同的是最后一个字符。这应该通过验证,而不是被卡在重复输入处。 - vee
43
索引限制为767个字节,而非字符。由于MySQL的utf8mb4字符集(全球通用的称谓是utf8)每个字符需要最多4个字节,因此您只能索引到VARCHAR(191)。MySQL的utf8字符集(全球通用的称谓是broken)每个字符最多需要3个字节,因此如果您使用它(您 不应该这样做),则可以索引到VARCHAR(255) - Stijn de Witt
显示剩余10条评论

452

当你达到限制时,请设置以下内容。

  • INNODB utf8 VARCHAR(255)
  • INNODB utf8mb4 VARCHAR(191)

16
因为 767 / 4 约等于 191,而 767 / 3 约等于 255。 - Accountant م
15
在哪里以及如何设置它? - Dinesh Sunny
2
是的,在 CREATE TABLE 语句的末尾指定 ENGINE=InnoDB DEFAULT CHARSET=utf8,我能够拥有一个 VARCHAR(255) 的主键。谢谢。 - xonya
1
确实!对于一些语言,如越南语,我们必须使用utf8mb4以保证搜索的准确性! - Alex
2
根据这个答案,我在我的SQL转储文件中搜索了所有的VARCHAR(255),并将它们替换为VARCHAR(191)。这个方法有效。我不知道可能会有什么副作用,但我使用它将数据从服务器导入到本地主机。 - Kaizoku Gambare
显示剩余2条评论

428

如果使用InnoDB和utf8字符集在VARCHAR(256)字段上尝试添加UNIQUE索引时遇到问题,请将其切换为VARCHAR(255)。看起来255是限制。


262
允许使用的字符数量取决于字符集。UTF8每个字符最多可使用3个字节,utf8mb4最多可使用4个字节,latin1仅使用1个字节。因此,对于UTF8,您的键长度限制为255个字符,因为“3*255 = 765 < 767”。 - Stefan Endrullis
8
正如Stefan Endrullis所说,这取决于字符集。如果使用UTF8字符集,每个字符占用3个字节:255x3=765,低于767的限制,而256x3=768则超过了该限制。但如果使用UTF8mb4字符集,每个字符将占用4个字节,即255x4=1020,因此这并不是一个真正的解决方案。 - bernhardh
对于 utf8mb4,我只需将所有字符串索引设置为长度 190,这有所帮助。 - Markus Köhler
30
这个答案是正确的。然而,如果255确实适用于您,这意味着您正在使用Mysql utf8,但很遗憾它是有问题的。它只能编码基本多文种平面中的字符。您将在超出该范围的字符上遇到问题。例如,他们添加的那些表情符号似乎就超出了它所能处理的范围。因此,不要切换到VARCHAR(255),而是切换到VARCHAR(191)并将编码切换为utf8mb4(实际上仍是 utf8,但MySql想保持向后兼容性)。 - Stijn de Witt
1
对于我的多列唯一约束条件没有帮助。它也无法解决OP的多列唯一约束条件问题(会使其总大小达到825字节)。 - Barett
这个答案没有抓住问题的根本。 - Anthony Rutledge

148

MySQL假设字符串中每个字符占用的最大字节数。 对于MySQL的'utf8'编码,每个字符占用3个字节,因为该编码不允许超出U+FFFF的字符。 对于MySQL的'utf8mb4'编码,每个字符占用4个字节,因为这是MySQL所谓的实际UTF-8。

因此,假设您使用的是'utf8'编码,则第一列将占用索引的60个字节,第二列将占用另外的1500个字节。


5
这句话的意思是,当使用utf8mb4编码时,需要将它们设置为最多191,因为191乘以4等于764,小于767。 - Isaac
2
@Isaac 是的,完全正确。 - morganwahl
2
我认为这可能是正确的答案,但您能否详细说明需要做什么来纠正这个问题?至少对于像我这样的MySQL新手来说? - Adam Grant
没有一种方法可以绕过这个索引限制。这里的问题是关于唯一约束条件的;对于这些,您可以有一个无限长度文本列,另一个列中存储该文本的哈希值(如MD5),并使用哈希列作为唯一约束条件。您将需要确保程序在更改文本时保持哈希值最新,但有各种处理方式可以轻松处理。坦率地说,MySQL应该自己实现这样的功能,这样您就不必;我不会感到惊讶如果MariaDB内置了类似的东西。 - morganwahl
在非常长的 varchar 列上创建唯一索引是罕见的。请考虑为什么需要它,因为这可能是一个设计问题。如果您只是想要搜索它的索引,请考虑一些适合 191 个字符内的“关键词”字段,或将文本拆分为短描述和长/完整文本等。或者,如果您真的需要全文搜索,请考虑使用专门的软件,例如 Apache Lucene。 - Stijn de Witt
我不确定将191个字符称为“非常长”。我相信我有一些绝对路径比这更长的文件。此外,请注意,这适用于列的 任何 索引,而不仅仅是唯一性约束的索引之一。 - morganwahl

60

在你的查询之前运行此查询:

SET @@global.innodb_large_prefix = 1;

这将把限制提高到3072个字节


4
全局更改innodb_large_prefix会有什么缺点?而且这个数据库是“全局”的还是所有数据库都是完全全局的? - SciPhi
5
只适用于使用非标准行格式。请参见http://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_large_prefix。具体来说,“启用此选项允许索引键前缀超过767个字节(最多3072个字节),适用于使用动态和压缩行格式的InnoDB表。”默认行格式不受影响。 - Chris Lear
5
这对我很有帮助-可以在这里找到更多详细信息和指南:http://mechanics.flite.com/blog/2014/07/29/using-innodb-large-prefix-to-avoid-error-1071/。 - cwd
1
需要在此之后重启MySQL服务器吗? - SimpleGuy
8
这个答案缺少重要细节。 innodb_file_format 必须是 BARRACUDA,在表级别上必须使用 ROW_FORMAT=DYNAMICROW_FORMAT=COMPRESSED。请参考@cwd在上面的评论中提供的指南。 - q0rban
显示剩余2条评论

60

Laravel框架的解决方案

根据Laravel 5.4.*文档; 您需要在app/Providers/AppServiceProvider.php文件的boot方法中设置默认字符串长度,如下所示:

use Illuminate\Support\Facades\Schema;

public function boot() 
{
    Schema::defaultStringLength(191); 
}

这个修复的解释,由Laravel 5.4.*文档提供:

Laravel默认使用utf8mb4字符集,包括在数据库中存储"表情符号"。如果你运行的MySQL版本早于5.7.7或MariaDB早于10.2.2,你可能需要手动配置迁移生成的默认字符串长度,以便MySQL为它们创建索引。你可以通过在AppServiceProvider中调用Schema::defaultStringLength方法来配置此项。

另外,你也可以为你的数据库启用innodb_large_prefix选项。请参考你的数据库文档以获取有关如何正确启用此选项的说明。


我做了一些研究并发现了不同的观点。这个问题是由于在用户表中的电子邮件字段中使用索引“unique”而引起的。因此,简单地添加一个限制值250就可以解决这个问题。这个值被添加到文件迁移中,在创建用户表的代码行上,“unique()”看起来像这样:$table->string('email', 250)->unique(); - Danang Ponorogo
它是Laravel 10版本,这个问题仍然存在。 - undefined

40

你使用了什么字符编码?一些字符集(如UTF-16等)每个字符使用超过一个字节。


9
若是UTF8编码,一个字符最多可以使用4个字节,因此20个字符的列宽会占用20 * 4 + 1字节,而500个字符的列宽则会占用500 * 4 + 2字节。 - Thanatos
5
就我所知,我刚遇到了同样的问题,将字符集从utf8_general_ci切换为utf8_unicode_ci解决了我的问题。虽然我不知道原因为何 :( - Andresch Serj
11
对于带有“UNIQUE”索引的VARCHAR(256)列,改变排序规则对我没有像@Andresch那样产生影响。但是,将长度从256减少到255确实解决了问题。我不明白为什么,因为767 /每个字符最大4字节会得出最大191? - Arjan
10
“2553 = 765; 2563 = 768”。看起来你的服务器假设每个字符占用3个字节,@Arjan。 - Amber
3
@Thanatos: UTF-8 最多使用 3 个字节。查询最大长度:SELECT MAXLEN FROM information_schema.CHARACTER_SETS WHERE CHARACTER_SET_NAME = 'utf8'; - Greg
21
@Greg:您是正确的,但需要进一步解释:UTF-8本身使用1至4个字节来表示每个代码点。MySQL的“字符集”(实际上是编码)有一个名为“utf8”的字符集,可以编码_UTF-8_的某些部分,并且每个代码点使用1至3个字节,无法编码BMP之外的代码点。它还包括另一个名为“utf8mb4”的字符集,它使用1至4个字节来表示每个代码点,并且能够编码所有Unicode代码点。(utf8mb4是UTF-8,utf8是UTF-8的一种奇怪版本。) - Thanatos

38

在您的导入文件中将utf8mb4替换为utf8

输入图像描述

但请注意,utf8字符集已被弃用,它不支持所有Unicode字符,例如表情符号,因此如果这样做,您将失去完整的Unicode支持。


4
为什么要这样做?此外,如果有任何不利之处(我认为有),您应该提到。 - Nico Haase
这意味着您的列将无法存储某些Unicode字符,最明显的是表情符号。 - Martin Tournoij
1
我相信你不想进行这个更改。但是,如果你确实想要这样做,在MySQL 5.6+中应该使用utf8mb3而不是utf8。后者只是前者的别名,并且计划在未来重新映射该别名。当使用旧/弃用的标准时,请明确指出,以免出现意外的“升级”。这是5.6、5.7和8.0文档的建议。 - AndrewF

31
我认为varchar(20)只需要21个字节,而varchar(500)只需要501个字节。因此总字节数为522,少于767。那么我为什么会收到错误消息呢?
UTF8需要3个字节来存储字符串,所以在你的情况下,20+500个字符=20*3+500*3=1560字节,这比允许的767字节多。
UTF8的限制是767/3=255个字符,对于使用4个字节每个字符的UTF8mb4,它是767/4=191个字符

如果您需要使用超过限制长度的列,则有两种解决方案:

  1. 使用“更便宜”的编码(即每个字符需要的字节数较少的编码)
    在我的情况下,我需要在包含文章SEO字符串的列上添加唯一索引,因为我只使用[A-z0-9\-]字符进行SEO,所以我使用了只使用一个字节每个字符的latin1_general_ci,因此该列可以具有767字节长度。
  2. 从您的列创建哈希并仅使用唯一索引
    我的另一个选择是创建另一个列,该列将存储SEO的哈希值,该列将具有UNIQUE键以确保SEO值是唯一的。我还将在原始SEO列上添加KEY索引以加快查找速度。

这个方法奏效了。我本来是用的varchar(256),但现在已经改成varchar(250)了。 - MaRmAR

29

许多用户已经在此处回答了为什么会出现错误消息的答案。我的回答是关于如何修复和使用它。

参考此链接

  1. 打开MySQL客户端(或MariaDB客户端)。这是一个命令行工具。
  2. 它会询问您的密码,请输入正确的密码。
  3. 使用此命令选择数据库use my_database_name;

数据库已更改

  1. set global innodb_large_prefix=on;

查询 OK,0 行受影响(0.00 秒)

  1. set global innodb_file_format=Barracuda;

查询 OK,0 行受影响(0.02 秒)

  1. 进入phpMyAdmin或类似的易于管理的工具,在“操作”选项卡中将表的ROW_FORMAT更改为DYNAMIC并保存更改。
  2. 进入表的结构选项卡>单击Unique按钮。
  3. 完成。现在应该没有错误了。

这个修复的问题是如果您将数据库导出到另一个服务器(例如从localhost到实际主机),并且在该服务器上无法使用MySQL命令行。你不能使它在那里工作。


如果您在输入上述查询后仍然出现错误,请尝试进入“phpmyadmin”>将“排序规则”设置为您喜欢的方式(我使用“utf8_general_ci”)>单击应用(即使它已经是utf8)。 - Anthony Kal
我没有使用符合你指示的工具,但我仍然会点赞,因为它试图帮助人们实际解决问题。有无数关于问题原因的解释,但实际上解决问题的方法却极少。 - Teekin

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