MySQL-Fehler: Schlüsselspezifikation ohne Schlüssellänge

363

Ich habe eine Tabelle mit einem Primärschlüssel, der ein Varchar ist (255). In einigen Fällen sind 255 Zeichen nicht ausreichend. Ich habe versucht, das Feld in einen Text zu ändern, erhalte jedoch die folgende Fehlermeldung:

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

Wie kann ich das beheben?

Bearbeiten: Ich sollte auch darauf hinweisen, dass diese Tabelle einen zusammengesetzten Primärschlüssel mit mehreren Spalten hat.

GSto
quelle
9
Eine Tabelle kann nicht mehrere Primärschlüssel haben. Meinen Sie damit, dass es einen zusammengesetzten Primärschlüssel hat (der mehr als eine Spalte enthält) oder mehrere UNIQUESchlüssel hat?
Quassnoi
1
In meinem Fall hatte ich aus irgendeinem Grund einen TEXT-Typ für eine E-Mail-Spalte anstelle von VARCHAR.
Kris
Verwenden Sie VARCHAR für eindeutige alphanumerische Zeichen.
JWC

Antworten:

571

Der Fehler tritt auf, weil MySQL nur die ersten N Zeichen eines BLOBs oder einer TEXTSpalte indizieren kann . So Der Fehler tritt vor allem , wenn es ein Feld / Spalte Typ ist TEXToder BLOB oder solche , gehören in TEXToder BLOBTypen wie TINYBLOB, MEDIUMBLOB, LONGBLOB, TINYTEXT, MEDIUMTEXT, und LONGTEXTdass Sie versuchen , einen Primärschlüssel oder Index zu machen. Mit vollem BLOBoder TEXTohne Längenwert kann MySQL die Eindeutigkeit der Spalte nicht garantieren, da sie eine variable und dynamische Größe hat. Wenn Sie also BLOBoder TEXTals Index verwenden, muss der Wert von N angegeben werden, damit MySQL die Schlüssellänge bestimmen kann. MySQL unterstützt jedoch keine Schlüssellängenbeschränkung für TEXToder BLOB. TEXT(88)wird einfach nicht funktionieren.

Der Fehler wird auch angezeigt, wenn Sie versuchen, eine Tabellenspalte von non-TEXTund nach non-BLOBwie VARCHARund ENUMin TEXToder BLOBTyp zu konvertieren , wobei die Spalte bereits als eindeutige Einschränkungen oder Index definiert wurde. Der Befehl "Tabelle SQL ändern" schlägt fehl.

Die Lösung des Problems besteht darin, die Spalte TEXToder BLOBaus dem Index oder der eindeutigen Einschränkung zu entfernen oder ein anderes Feld als Primärschlüssel festzulegen. Wenn Sie dies nicht tun können und die Spalte TEXToder einschränken möchten BLOB, versuchen Sie, VARCHARtype zu verwenden und die Länge zu begrenzen. Standardmäßig VARCHARist es auf maximal 255 Zeichen begrenzt, und sein Limit muss implizit in einer Klammer direkt nach seiner Deklaration angegeben werden, dh VARCHAR(200)es wird auf nur 200 Zeichen begrenzt.

Manchmal wird auch der Fehler 1170 angezeigt , obwohl Sie keinen TEXToder einen BLOBverwandten Typ in Ihrer Tabelle verwenden. Dies geschieht beispielsweise, wenn Sie eine VARCHARSpalte als Primärschlüssel angeben , deren Länge oder Zeichengröße jedoch falsch festlegen. VARCHARkann nur bis zu 256 Zeichen akzeptieren, sodass VARCHAR(512)MySQL gezwungen wird, das automatisch VARCHAR(512)in einen SMALLTEXTDatentyp zu konvertieren , der anschließend mit Fehler 1170 bei der Schlüssellänge fehlschlägt, wenn die Spalte als Primärschlüssel oder eindeutiger oder nicht eindeutiger Index verwendet wird. Geben Sie zur Lösung dieses Problems eine Zahl unter 256 als VARCHARFeldgröße an.

Referenz: MySQL-Fehler 1170 (42000): BLOB / TEXT-Spalte, die in der Schlüsselspezifikation ohne Schlüssellänge verwendet wird

OMG Ponys
quelle
13
dev.mysql.com/doc/refman/5.0/en/char.html "Werte in VARCHAR-Spalten sind Zeichenfolgen variabler Länge. Die Länge kann als Wert von 0 bis 255 vor MySQL 5.0.3 und 0 bis 65.535 angegeben werden in 5.0.3 und späteren Versionen. Die effektive maximale Länge eines VARCHAR in MySQL 5.0.3 und höher unterliegt der maximalen Zeilengröße (65.535 Bytes, die von allen Spalten gemeinsam genutzt werden) "
umassthrower
1
Wo Sie sagen "MySQL kann nur die ersten N Zeichen einer BLOB- oder TEXT-Spalte indizieren", wie hoch ist der Wert von N?
Jameshfisher
2
"Wenn Sie eine BLOB- oder TEXT-Spalte indizieren, müssen Sie eine Präfixlänge für den Index angeben." dev.mysql.com/doc/refman/5.6/en/column-indexes.html
Vinicius Pinto
86

Sie sollten definieren, welchen führenden Teil einer TEXTSpalte Sie indizieren möchten.

InnoDBDie Anzahl der 768Bytes pro Indexschlüssel ist begrenzt, und Sie können keinen längeren Index erstellen.

Dies wird gut funktionieren:

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

Beachten Sie, dass der Maximalwert der Schlüsselgröße vom Spaltenzeichensatz abhängt. Es sind 767Zeichen für einen Einzelbyte-Zeichensatz wie LATIN1und nur 255Zeichen für UTF8( MySQLnur Verwendungen, für BMPdie höchstens 3Bytes pro Zeichen erforderlich sind )

Wenn Sie möchten, dass Ihre gesamte Spalte die ist PRIMARY KEY, berechnen Sie SHA1oder MD5hashen Sie und verwenden Sie sie als PRIMARY KEY.

Quassnoi
quelle
Genau das, wonach ich gesucht habe. Vielen Dank!
Jonathon Hill
Ist die Größe (hier 255) wirklich in Zeichen und nicht in Bytes? Weil ich mir vorstellen könnte, dass die Verwendung von Zeichen für UTF-8 ohne zusätzliche Vorteile wirklich kompliziert wäre.
Alexis Wilke
Entschuldigung, ich folge Ihrer Frage nicht. Aus den Dokumenten: Die maximale Länge des Indexschlüsselpräfixes beträgt 767 Byte für InnoDB-Tabellen, die das Zeilenformat REDUNDANToder COMPACTverwenden. Beispielsweise können Sie diese Grenze mit einem Spaltenpräfixindex von mehr als 255 Zeichen für eine TEXToder eine VARCHARSpalte erreichen, vorausgesetzt, ein Zeichensatz utf8mb3 und maximal 3 Byte für jedes Zeichen. REDUNDANTund COMPACTwaren die einzigen verfügbaren Formate zum Zeitpunkt der Beantwortung dieser Antwort.
Quassnoi
62

Sie können die Schlüssellänge in der Anforderung zum Ändern der Tabelle angeben, z. B.:

alter table authors ADD UNIQUE(name_first(20), name_second(20));
Mike Evans
quelle
1
Dies war genau das, was ich brauchte, um das gleiche Problem zu beheben. Vielen Dank!
Per Quested Aronsson
2
Sie sollten mit diesem Ansatz sehr vorsichtig sein! Dies ist vielleicht die einfachste Lösung, aber in vielen Situationen nicht die beste. Ihr Schlüssel besteht aus zwei Spalten und die Spaltenreihenfolge ist wichtig.
MrD
Beste Antwort hier.
George Chalhoub
Dies löst mein Problem, vielen Dank!
Dellair
22

MySQL Verbietet einen vollen Wert indizieren BLOB, TEXTund lange VARCHARSpalten , weil Daten , die sie enthalten , können sehr groß sein, und implizit DB - Index wird groß sein, keinen Nutzen aus dem Index bedeutet.

MySQL erfordert, dass Sie die ersten N zu indizierenden Zeichen definieren. Der Trick besteht darin, eine Zahl N zu wählen, die lang genug ist, um eine gute Selektivität zu erzielen, aber kurz genug, um Platz zu sparen. Das Präfix sollte lang genug sein, um den Index fast so nützlich zu machen, wie es wäre, wenn Sie die gesamte Spalte indiziert hätten.

Bevor wir weiter gehen, definieren wir einige wichtige Begriffe. Die Indexselektivität ist das Verhältnis der insgesamt unterschiedlichen indizierten Werte und der Gesamtzahl der Zeilen . Hier ist ein Beispiel für eine Testtabelle:

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

Wenn wir nur das erste Zeichen indizieren (N = 1), sieht die Indextabelle wie folgt aus:

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

In diesem Fall ist die Indexselektivität gleich IS = 1/3 = 0,33.

Lassen Sie uns nun sehen, was passieren wird, wenn wir die Anzahl der indizierten Zeichen auf zwei erhöhen (N = 2).

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

In diesem Szenario ist IS = 2/3 = 0,66, was bedeutet, dass wir die Indexselektivität erhöht haben, aber wir haben auch die Größe des Index erhöht. Der Trick besteht darin, die minimale Zahl N zu finden, die zu maximaler Indexselektivität führt .

Es gibt zwei Ansätze, mit denen Sie Berechnungen für Ihre Datenbanktabelle durchführen können. Ich werde auf diesem Datenbank-Dump demonstrieren .

Lassen Sie uns sagen , dass wir Spalte hinzufügen möchten last_name in der Tabelle Mitarbeiter auf den Index, und wir wollen die kleinste Zahl definieren , N , die die beste Index Selektivität produzieren.

Lassen Sie uns zuerst die häufigsten Nachnamen identifizieren:

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)

Wie Sie sehen können, ist der Nachname Baba der häufigste. Jetzt werden wir die am häufigsten vorkommenden Nachnamen- Präfixe finden, beginnend mit Präfixen aus fünf Buchstaben.

+-----+--------+
| 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)

Es gibt viel mehr Vorkommen von jedem Präfix, was bedeutet, dass wir die Zahl N erhöhen müssen, bis die Werte fast dieselben wie im vorherigen Beispiel sind.

Hier sind die Ergebnisse für 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    |
+-----+-----------+

Hier sind Ergebnisse für 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)

Das sind sehr gute Ergebnisse. Dies bedeutet, dass wir einen Index für eine Spalte last_nameerstellen können, wobei nur die ersten 10 Zeichen indiziert werden. In der Tabellendefinition last_nameist die Spalte definiert als VARCHAR(16), und dies bedeutet, dass wir 6 Bytes (oder mehr, wenn der Nachname UTF8-Zeichen enthält) pro Eintrag gespeichert haben. In dieser Tabelle gibt es 1637 verschiedene Werte multipliziert mit 6 Bytes, was ungefähr 9 KB entspricht. Stellen Sie sich vor, wie diese Zahl wachsen würde, wenn unsere Tabelle Millionen von Zeilen enthält.

Sie können andere Methoden zur Berechnung der Anzahl von N in meinen Post- Präfix-Indizes in MySQL lesen .

MrD
quelle
3
Dies wurde nicht ausreichend aktualisiert. Ich fand es viel einfacher zu verstehen als die akzeptierte Antwort
Mawg sagt, Monica
10

Ich habe diesen Fehler beim Hinzufügen eines Index zu einer Tabelle mit Textspalten erhalten. Sie müssen die Größe angeben, die Sie für jeden Texttyp verwenden möchten.

Setzen Sie den Größenbetrag in die Klammer ()

Wenn zu viele Bytes verwendet werden, können Sie in den Klammern eine Größe für varchar angeben, um die für die Indizierung verwendete Menge zu verringern. Dies gilt auch dann, wenn Sie eine Größe für einen Typ wie varchar (1000) angegeben haben. Sie müssen keine neue Tabelle erstellen, wie andere gesagt haben.

Index hinzufügen

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

Hinzufügen eines eindeutigen Index

alter table test add unique index_name(col1(255),col2(255));
Stealth
quelle
Einfachste Antwort, glaube ich, und es hat sofort bei mir funktioniert. Vielen Dank.
Matt Cremeens
4

Eine weitere hervorragende Möglichkeit, damit umzugehen, besteht darin, Ihr TEXT-Feld ohne die eindeutige Einschränkung zu erstellen und ein Geschwister-VARCHAR-Feld hinzuzufügen, das eindeutig ist und einen Digest (MD5, SHA1 usw.) des TEXT-Felds enthält. Berechnen und speichern Sie den Digest über das gesamte TEXT-Feld, wenn Sie das TEXT-Feld einfügen oder aktualisieren. Dann gibt es eine Eindeutigkeitsbeschränkung für das gesamte TEXT-Feld (und nicht für einen führenden Teil), die schnell durchsucht werden kann.

Par
quelle
1
Sie sollten auch sehr vorsichtig mit vollständig „zufälligen“ Zeichenfolgen sein, wie sie beispielsweise von MD5 (), SHA1 () oder UUID () erzeugt werden. Jeder neue Wert, den Sie mit ihnen generieren, wird auf beliebige Weise über einen großen Bereich verteilt, was INSERT und einige Arten von SELECT-Abfragen verlangsamen kann:
MrD
2
Die Verteilung von MD5, SHA1 über nicht böswillige Daten sollte einheitlich sein - dafür sind Hashes gedacht.
jb.
Es wäre großartig, wenn Sie ein Beispiel nennen könnten.
WebComer
3

Keine langen Werte als Primärschlüssel. Das wird deine Leistung zerstören. Weitere Informationen finden Sie im MySQL-Handbuch, Abschnitt 13.6.13 'InnoDB-Leistungsoptimierung und Fehlerbehebung'.

Verwenden Sie stattdessen einen Ersatz-Int-Schlüssel als primären Schlüssel (mit auto_increment) und Ihren Loong-Schlüssel als sekundären EINZIGARTIGEN.

Per Lindberg
quelle
2

Fügen Sie eine weitere varChar (255) -Spalte hinzu (standardmäßig als leere Zeichenfolge nicht null), um den Überlauf zu halten, wenn 255 Zeichen nicht ausreichen, und ändern Sie diese PK, um beide Spalten zu verwenden. Dies klingt jedoch nicht nach einem gut gestalteten Datenbankschema, und ich würde empfehlen, dass ein Datenmodellierer überprüft, was Sie haben, um es für eine weitere Normalisierung umzugestalten.

Charles Bretana
quelle
2

Die Lösung für das Problem besteht darin, dass Sie in Ihrer CREATE TABLEAnweisung die Einschränkung hinzufügen können, UNIQUE ( problemtextfield(300) )nachdem die Spalte Definitionen erstellt hat, um beispielsweise die keyLänge der 300Zeichen für ein TEXTFeld anzugeben . Dann müssten die ersten 300Zeichen des problemtextfield TEXTFeldes eindeutig sein, und alle Unterschiede danach würden ignoriert.

Wer Dunnit
quelle
1

Wenn Sie in diesem Feld einen Index verwenden möchten, sollten Sie außerdem die MyISAM-Speicher-Engine und den FULLTEXT-Indextyp verwenden.

Alexander Valinurov
quelle
Sie können eine Erklärung und einen Link zur Dokumentation hinzufügen.
LeeGee
1

Bisher hat es noch niemand erwähnt ... mit utf8mb4, das 4-Byte ist und auch Emoticons speichern kann (wir sollten nie mehr 3-Byte utf8 verwenden), und wir können Fehler vermeiden, da Incorrect string value: \xF0\x9F\x98\...wir nicht typisches VARCHAR (255) verwenden sollten , sondern VARCHAR ( 191) weil für den Fall, dass utf8mb4 und VARCHAR (255) derselbe Teil der Daten außerhalb der Seite gespeichert wird und Sie keinen Index für die Spalte VARCHAR (255) erstellen können, aber für VARCHAR (191). Dies liegt daran, dass die maximale indizierte Spaltengröße für ROW_FORMAT = COMPACT oder ROW_FORMAT = REDUNDANT 767 Byte beträgt.

Für neuere Zeilenformate ROW_FORMAT = DYNAMIC oder ROW_FORMAT = COMPRESSED (für das ein neueres Dateiformat erforderlich ist innodb_file_format = Barracuda nicht älter Antelope) beträgt die maximale indizierte Spaltengröße 3072. Sie ist verfügbar, da MySQL> = 5.6.3, wenn innodb_large_prefix = 1 (standardmäßig deaktiviert für MySQL <= 5.7.6 und standardmäßig für MySQL aktiviert> = 5.7.7). In diesem Fall können wir also VARCHAR (768) für utf8mb4 (oder VARCHAR (1024) für altes utf8) für indizierte Spalten verwenden. Die Option innodb_large_prefix ist seit 5.7.7 veraltet, da ihr Verhalten in MySQL 8 integriert ist (in dieser Version ist die Option entfernt).

mikep
quelle
0

Sie müssen den Spaltentyp in varcharoder integerfür die Indizierung ändern .

Manoj Mishra
quelle
Sie können eine Erklärung und einen Link zur Dokumentation hinzufügen.
LeeGee
0

Gehen Sie zu MySQL edit table-> ändern Sie den Spaltentyp in varchar(45).

Manoj Mishra
quelle
-1

Verwenden Sie so

@Id
@Column(name = "userEmailId", length=100)
private String userEmailId;
Krishna Das
quelle
Sie können eine Erklärung und einen Link zur Dokumentation hinzufügen.
LeeGee