MySQL错误:没有密钥长度的密钥规范


363

我有一个主键为varchar(255)的表。在某些情况下,255个字符不够用。我尝试将字段更改为文本,但是出现以下错误:

BLOB/TEXT column 'message_id' used in key specification without a key length

我怎样才能解决这个问题?

编辑:我还应该指出,该表具有包含多个列的复合主键。


9
一个表不能有多个主键。您是说它具有复合主键(包括多个列)还是具有多个UNIQUE键?
Quassnoi

1
就我而言,由于某种原因,我为电子邮件列使用了TEXT类型,而不是VARCHAR。
克里斯(Kris)

使用VARCHAR表示唯一的字母数字。
JWC 5

Answers:


571

发生错误是因为MySQL只能索引BLOB或TEXT列的前N个字符。所以错误主要发生时,有一个领域/列类型TEXT或BLOB或那些属于TEXTBLOB类型,如TINYBLOBMEDIUMBLOBLONGBLOBTINYTEXTMEDIUMTEXT,和LONGTEXT您尝试使一个主键或索引。无论长度是完整的BLOB还是TEXT没有长度的,MySQL都无法保证列的唯一性,因为它具有可变和动态大小。因此,当使用BLOBor TEXT类型作为索引时,必须提供N的值,以便MySQL可以确定键的长度。但是,MySQL不支持TEXT或上的密钥长度限制BLOBTEXT(88)根本行不通。

当您尝试将表列从non-TEXTnon-BLOB类型(如VARCHARENUM转换为TEXTBLOB类型)转换为错误,并且该列已被定义为唯一约束或索引时,也会弹出该错误。Alter Table SQL命令将失败。

该问题的解决方案是从索引或唯一约束中删除TEXTBLOB列,或将另一个字段设置为主键。如果您不能这样做,并且想对TEXTor BLOB列设置限制,请尝试使用VARCHARtype并对其限制长度。默认情况下,VARCHAR最大限制为255个字符,并且必须在声明后的括号内隐式指定其限制,即,VARCHAR(200)将其限制为仅200个字符长。

有时,即使您没有在表中使用TEXTBLOB相关类型,也可能会出现错误1170。例如,当您将VARCHAR列指定为主键,但是错误地设置了它的长度或字符大小时,就会发生这种情况。VARCHAR最多只能接受256个字符,因此,诸如此类的任何操作都VARCHAR(512)将迫使MySQL自动将其转换VARCHAR(512)SMALLTEXT数据类型,如果将该列用作主键或唯一或非唯一索引,则该操作随后将在键长度上出现错误1170。要解决此问题,请指定小于256的数字作为VARCHAR字段的大小。

参考:MySQL错误1170(42000):键规范中使用的BLOB / TEXT列没有键长


13
dev.mysql.com/doc/refman/5.0/en/char.html “ VARCHAR列中的值是可变长度的字符串。长度可以指定为MySQL 5.0.3之前的0到255,以及0到65,535的值在5.0.3和更高版本中。MySQL5.0.3和更高版本中VARCHAR的有效最大长度取决于最大行大小(65,535字节,在所有列之间共享)”
umassthrower

1
在您说“ MySQL只能索引BLOB或TEXT列的前N个字符”的地方,N的值是什么?
jameshfisher 2014年

2
“在索引BLOB或TEXT列时,必须为索引指定前缀长度。” dev.mysql.com/doc/refman/5.6/en/column-indexes.html
Vinicius Pinto

86

您应该定义TEXT要索引的列的前导部分。

InnoDB768每个索引键的字节数限制,并且您创建索引的时间不能超过该长度。

这将正常工作:

CREATE TABLE t_length (
      mydata TEXT NOT NULL,
      KEY ix_length_mydata (mydata(255)))
    ENGINE=InnoDB;

请注意,键大小的最大值取决于列字符集。它767是单字节字符集(例如)的字符,LATIN1并且仅255用于UTF8MySQL仅用于每个字符BMP最多需要3字节的)字符

如果您需要将整个列作为PRIMARY KEY,请计算SHA1MD5散列并将其用作PRIMARY KEY


正是我想要的。谢谢!
乔纳森·希尔

大小(此处为255)是否真正以字符而不是字节为单位?因为我可以想象将字符用于UTF-8确实很复杂,而且没有任何额外的好处。
亚历克西斯·威尔克

抱歉,我没有听您的问题。从文档:对于使用REDUNDANTCOMPACT行格式的InnoDB表,索引键前缀长度限制为767个字节。例如,假设utf8mb3字符集且每个字符最多3个字节,则您可能在a TEXTVARCHAR列上使用超过255个字符的列前缀索引来达到此限制。 REDUNDANT并且COMPACT是给出此答案时唯一可用的格式。
Quassnoi

62

您可以在alter table请求中指定密钥长度,例如:

alter table authors ADD UNIQUE(name_first(20), name_second(20));

1
这正是要解决的相同问题。谢谢!
Per Quested Aronsson

2
使用这种方法时,您应该非常小心!这可能是最简单的解决方案,但在很多情况下不是最佳解决方案。您的键由两列组成,列顺序很重要。
MrD

最好的答案在这里。
乔治·乔卢布'18

这解决了我的问题,非常感谢!
dellair,

22

MySQL不允许索引BLOBTEXT和长VARCHAR列的完整值,因为它们包含的数据可能很大,并且隐式地DB索引会很大,这意味着无法从索引中受益。

MySQL要求您定义要索引的前N个字符,诀窍是选择一个数字N,该数字足够长以提供良好的选择性,但又足够短以节省空间。前缀应该足够长,以使索引几乎与索引整个列时一样有用。

在继续之前,让我们定义一些重要的术语。索引选择性总的不同索引值与总行数之比。这是测试表的一个示例:

+-----+-----------+
| id  | value     |
+-----+-----------+
| 1   | abc       |
| 2   | abd       |
| 3   | adg       |
+-----+-----------+

如果我们仅索引第一个字符(N = 1),那么索引表将如下表所示:

+---------------+-----------+
| indexedValue  | rows      |
+---------------+-----------+
| a             | 1,2,3     |
+---------------+-----------+

在这种情况下,指数选择性等于IS = 1/3 = 0.33。

现在让我们看看如果将索引字符的数量增加到两个(N = 2)会发生什么。

+---------------+-----------+
| indexedValue  | rows      |
+---------------+-----------+
| ab             | 1,2      |
| ad             | 3        |
+---------------+-----------+

在这种情况下,IS = 2/3 = 0.66,这意味着我们增加了索引的选择性,但同时也增加了索引的大小。关键是要找到的最小数N,这将导致以最大的索引选择性

您可以通过两种方法对数据库表进行计算。我将在此数据库转储上进行演示

假设我们要在表employees中添加列last_name到索引,并且要定义最小的数字N,它将产生最佳的索引选择性。

首先,让我们确定最常用的姓氏:

select count(*) as cnt, last_name 
from employees 
group by employees.last_name 
order by cnt

+-----+-------------+
| cnt | last_name   |
+-----+-------------+
| 226 | Baba        |
| 223 | Coorg       |
| 223 | Gelosh      |
| 222 | Farris      |
| 222 | Sudbeck     |
| 221 | Adachi      |
| 220 | Osgood      |
| 218 | Neiman      |
| 218 | Mandell     |
| 218 | Masada      |
| 217 | Boudaillier |
| 217 | Wendorf     |
| 216 | Pettis      |
| 216 | Solares     |
| 216 | Mahnke      |
+-----+-------------+
15 rows in set (0.64 sec)

如您所见,姓Baba是最常用的。现在,我们将查找最常见的last_name前缀,从五个字母的前缀开始。

+-----+--------+
| cnt | prefix |
+-----+--------+
| 794 | Schaa  |
| 758 | Mande  |
| 711 | Schwa  |
| 562 | Angel  |
| 561 | Gecse  |
| 555 | Delgr  |
| 550 | Berna  |
| 547 | Peter  |
| 543 | Cappe  |
| 539 | Stran  |
| 534 | Canna  |
| 485 | Georg  |
| 417 | Neima  |
| 398 | Petti  |
| 398 | Duclo  |
+-----+--------+
15 rows in set (0.55 sec)

每个前缀出现的次数更多,这意味着我们必须增加数字N,直到值几乎与前面的示例相同为止。

这是N = 9的结果

select count(*) as cnt, left(last_name,9) as prefix 
from employees 
group by prefix 
order by cnt desc 
limit 0,15;

+-----+-----------+
| cnt | prefix    |
+-----+-----------+
| 336 | Schwartzb |
| 226 | Baba      |
| 223 | Coorg     |
| 223 | Gelosh    |
| 222 | Sudbeck   |
| 222 | Farris    |
| 221 | Adachi    |
| 220 | Osgood    |
| 218 | Mandell   |
| 218 | Neiman    |
| 218 | Masada    |
| 217 | Wendorf   |
| 217 | Boudailli |
| 216 | Cummings  |
| 216 | Pettis    |
+-----+-----------+

这是N = 10的结果。

+-----+------------+
| cnt | prefix     |
+-----+------------+
| 226 | Baba       |
| 223 | Coorg      |
| 223 | Gelosh     |
| 222 | Sudbeck    |
| 222 | Farris     |
| 221 | Adachi     |
| 220 | Osgood     |
| 218 | Mandell    |
| 218 | Neiman     |
| 218 | Masada     |
| 217 | Wendorf    |
| 217 | Boudaillie |
| 216 | Cummings   |
| 216 | Pettis     |
| 216 | Solares    |
+-----+------------+
15 rows in set (0.56 sec)

这是非常好的结果。这意味着我们可以在last_name仅索引前10个字符的列上建立索引。在表定义中,列last_name定义为VARCHAR(16),这表示每个条目我们已保存6个字节(如果姓氏中包含UTF8字符,则节省更多字节)。在此表中,有1637个不同的值乘以6个字节,大约为9KB,并想象如果我们的表包含一百万行,这个数字将如何增长。

您可以阅读计算多种其他方式ñ在我的岗位在MySQL前缀索引


3
尚未更新足够。我发现它是容易比接受的答案,了解
Mawg说起用莫妮卡

10

将索引添加到具有文本类型列的表时出现此错误。您需要声明每种文本类型要使用的大小量。

将大小金额放在圆括号()内

如果使用了太多字节,则可以在括号中声明varchar的大小,以减少用于索引的数量。即使您为已经像varchar(1000)这样的类型声明了大小,也是如此。您无需像其他人一样创建新表。

添加索引

alter table test add index index_name(col1(255),col2(255));

添加唯一索引

alter table test add unique index_name(col1(255),col2(255));

我认为,最简单的答案立即对我有用。谢谢。
Matt Cremeens


4

解决此问题的另一种极好的方法是创建没有唯一约束的TEXT字段,并添加一个唯一的同级VARCHAR字段,并包含TEXT字段的摘要(MD5,SHA1等)。在插入或更新TEXT字段时,计算并存储整个TEXT字段的摘要,然后在整个TEXT字段(而不是某些开头部分)上具有唯一性约束,可以快速搜索该约束。


1
您还应该非常小心使用完全“随机”的字符串,例如MD5(),SHA1()或UUID()生成的字符串。您使用它们生成的每个新值都将以任意方式分布在一个较大的空间中,这会减慢INSERT和某些类型的SELECT查询的速度:
MrD 2015年

2
MD5和SHA1在非恶意数据上的分布应该是均匀的-这就是哈希的目的。
jb。

如果您能提供一些例子,那就太好了。
WebComer

3

不要将long值用作主键。那会破坏你的表现。参见mysql手册,第13.6.13节“ InnoDB性能调优和故障排除”。

取而代之的是,将替代int键作为主键(带有auto_increment),将loong键作为辅助UNIQUE。


2

添加另一个varChar(255)列(默认为空字符串,不为null),以在255个字符不足时保留溢出,并将此PK更改为同时使用这两个列。但是,这听起来不像是一个设计良好的数据库架构,因此,我建议让数据建模人员查看您所拥有的内容,以便对其进行重构以实现更多的标准化。


2

解决该问题的方法是,在您的CREATE TABLE语句中,可以UNIQUE ( problemtextfield(300) )在列创建定义之后添加约束,以指定字段key300字符长度TEXT。然后300,该problemtextfield TEXT字段的第一个字符必须是唯一的,此后的任何差异都将被忽略。



1

到目前为止,没有人提到它... utf8mb4是4字节,还可以存储图释(我们永远不应该再使用3字节的utf8),并且可以避免出现诸如Incorrect string value: \xF0\x9F\x98\...不应该使用典型的VARCHAR(255)而是VARCHAR( 191),因为如果utf8mb4和VARCHAR(255)的同一部分数据存储在页面外,则不能为VARCHAR(255)列创建索引,但可以为VARCHAR(191)创建索引。这是因为ROW_FORMAT = COMPACT或ROW_FORMAT = REDUNDANT的最大索引列大小为767字节。

对于较新的行格式ROW_FORMAT = DYNAMIC或ROW_FORMAT = COMPRESSED(需要较新的文件格式innodb_file_format = Barracuda而不是较早的Antelope),最大索引列大小为3072。当innodb_large_prefix = 1时,MySQL> = 5.6.3可用(默认情况下禁用) MySQL <= 5.7.6,默认情况下已启用MySQL> = 5.7.7)。因此,在这种情况下,我们可以为utf8mb4使用VARCHAR(768)(对于旧的utf8使用VARCHAR(1024))作为索引列。从5.7.7版本开始,不推荐使用innodb_large_prefix选项,因为它的行为是内置的MySQL 8(在此版本中,该选项已删除)。




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.