# 1071 - A chave especificada era muito longa; o comprimento máximo da chave é 1000 bytes

102

Sei que perguntas com este título já foram respondidas antes, mas por favor, continue lendo. Li cuidadosamente todas as outras perguntas / respostas sobre este erro antes de postar.

Estou recebendo o erro acima para a seguinte consulta:

CREATE TABLE IF NOT EXISTS `pds_core_menu_items` (
  `menu_id` varchar(32) NOT NULL,
  `parent_menu_id` int(32) unsigned DEFAULT NULL,
  `menu_name` varchar(255) DEFAULT NULL,
  `menu_link` varchar(255) DEFAULT NULL,
  `plugin` varchar(255) DEFAULT NULL,
  `menu_type` int(1) DEFAULT NULL,
  `extend` varchar(255) DEFAULT NULL,
  `new_window` int(1) DEFAULT NULL,
  `rank` int(100) DEFAULT NULL,
  `hide` int(1) DEFAULT NULL,
  `template_id` int(32) unsigned DEFAULT NULL,
  `alias` varchar(255) DEFAULT NULL,
  `layout` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`menu_id`),
  KEY `index` (`parent_menu_id`,`menu_link`,`plugin`,`alias`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Alguém tem ideia do porquê e como consertar? O problema é - essa mesma consulta funciona perfeitamente na minha máquina local e funcionou bem no meu host anterior. Btw.é de um projeto maduro - phpdevshell - então eu acho que esses caras sabem o que estão fazendo, embora você nunca saiba.

Qualquer pista é apreciada.

Estou usando o phpMyAdmin.

CodeVirtuoso
fonte

Respostas:

170

Como @Devart diz, o comprimento total do seu índice é muito longo.

A resposta curta é que você não deve indexar colunas VARCHAR tão longas, porque o índice será muito volumoso e ineficiente.

A prática recomendada é usar índices de prefixo para que você indexe apenas uma substring esquerda dos dados. A maioria dos seus dados terá menos de 255 caracteres de qualquer maneira.

Você pode declarar um comprimento de prefixo por coluna conforme define o índice. Por exemplo:

...
KEY `index` (`parent_menu_id`,`menu_link`(50),`plugin`(50),`alias`(50))
...

Mas qual é o melhor comprimento de prefixo para uma determinada coluna? Aqui está um método para descobrir:

SELECT
 ROUND(SUM(LENGTH(`menu_link`)<10)*100/COUNT(`menu_link`),2) AS pct_length_10,
 ROUND(SUM(LENGTH(`menu_link`)<20)*100/COUNT(`menu_link`),2) AS pct_length_20,
 ROUND(SUM(LENGTH(`menu_link`)<50)*100/COUNT(`menu_link`),2) AS pct_length_50,
 ROUND(SUM(LENGTH(`menu_link`)<100)*100/COUNT(`menu_link`),2) AS pct_length_100
FROM `pds_core_menu_items`;

Ele informa a proporção de linhas que não têm mais do que um determinado comprimento de string na menu_linkcoluna. Você pode ver uma saída como esta:

+---------------+---------------+---------------+----------------+
| pct_length_10 | pct_length_20 | pct_length_50 | pct_length_100 |
+---------------+---------------+---------------+----------------+
|         21.78 |         80.20 |        100.00 |         100.00 |
+---------------+---------------+---------------+----------------+

Isso indica que 80% de suas strings têm menos de 20 caracteres e todas as suas strings têm menos de 50 caracteres. Portanto, não há necessidade de indexar mais do que um comprimento de prefixo de 50 e, certamente, não há necessidade de indexar o comprimento total de 255 caracteres.

PS: Os tipos de dados INT(1)e INT(32)indicam outro mal-entendido sobre o MySQL. O argumento numérico não tem efeito relacionado ao armazenamento ou ao intervalo de valores permitidos para a coluna. INTé sempre 4 bytes e sempre permite valores de -2147483648 a 2147483647. O argumento numérico é sobre valores de preenchimento durante a exibição, que não tem efeito a menos que você use a ZEROFILLopção.

Bill Karwin
fonte
17
Muito obrigado pela explicação detalhada. Além de resolver um problema, também aprendi algo valioso.
CodeVirtuoso
Realmente, uma consulta muito útil para descobrir o comprimento em que o índice deve ser definido. Tenho usado isso algumas vezes para determinar o melhor comprimento para um índice. Obrigado por compartilhar!
Niraj Kumar
1
Há um bug sutil em sua consulta útil para medir o comprimento real das strings: você está assumindo que todas as strings estão presentes; isto é, que todos eles estão lá. Se alguns forem nulos, isso vai atrapalhar seus cálculos e subnotificar strings curtas. Você deseja usar count ([field_name]) em vez de count (*).
D Mac de
Não usará mais do que o primeiro índice de "prefixo".
Rick James
28

Este erro significa que o comprimento do índice indexé superior a 1000 bytes. MySQL e mecanismos de armazenamento podem ter essa restrição. Recebi um erro semelhante no MySQL 5.5 - 'A chave especificada era muito longa; o comprimento máximo da chave é 3072 bytes 'quando este script foi executado:

CREATE TABLE IF NOT EXISTS test_table1 (
  column1 varchar(500) NOT NULL,
  column2 varchar(500) NOT NULL,
  column3 varchar(500) NOT NULL,
  column4 varchar(500) NOT NULL,
  column5 varchar(500) NOT NULL,
  column6 varchar(500) NOT NULL,
  KEY `index` (column1, column2, column3, column4, column5, column6)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

UTF8 é multi-bytes e o comprimento da chave é calculado desta forma - 500 * 3 * 6 = 9000 bytes.

Mas observe, a próxima consulta funciona!

CREATE TABLE IF NOT EXISTS test_table1 (
  column1 varchar(500) NOT NULL,
  column2 varchar(500) NOT NULL,
  column3 varchar(500) NOT NULL,
  column4 varchar(500) NOT NULL,
  column5 varchar(500) NOT NULL,
  column6 varchar(500) NOT NULL,
  KEY `index` (column1, column2, column3, column4, column5, column6)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

... porque usei CHARSET = latin1, neste caso o comprimento da chave é 500 * 6 = 3000 bytes.

Devart
fonte
7
Obrigado pela resposta, isso funciona, mas ao custo de desistir do conjunto de caracteres utf8. Essa restrição pode ser superada de alguma forma (eu tenho acesso total ao servidor), isso existe por um bom motivo?
CodeVirtuoso
4
Este é um conselho terrível, por favor, aprenda o que são charsets e como isso causará grandes problemas no futuro. Um banco de dados de conjuntos de caracteres mistos não só causa problemas ao usar junções ou sub-seleções, mas também coloca seus dados em um formato não normalizado e pode ser quase impossível de corrigir posteriormente.
Geoffrey
17

Eu tive esse problema e resolvi da seguinte maneira:

Causa

Existe um bug conhecido com o MySQL relacionado ao MyISAM, o conjunto de caracteres UTF8 e índices que você pode verificar aqui.

Resolução

  • Certifique-se de que o MySQL esteja configurado com o mecanismo de armazenamento InnoDB.

  • Altere o mecanismo de armazenamento usado por padrão para que as novas tabelas sejam sempre criadas de forma adequada:

    set GLOBAL storage_engine='InnoDb';

  • Para MySQL 5.6 e posterior, use o seguinte:

    SET GLOBAL default_storage_engine = 'InnoDB';

  • E, por fim, certifique-se de seguir as instruções fornecidas em Migrando para o MySQL .

Referência

Vishrant
fonte
Na maioria dos casos, esquecemos de configurar o mecanismo de armazenamento como 'InnoDB'. A resposta a seguir é a mais simples e provavelmente resolverá o problema para a maioria dos usuários aqui. Obrigado.
Frederiko Cesar
10

execute esta consulta antes de criar ou alterar a tabela.

SET @@global.innodb_large_prefix = 1;

isso definirá o comprimento máximo da chave em 3072 bytes

Raza Ahmed
fonte
3

Este limite de tamanho de índice parece ser maior em compilações de 64 bits do MySQL.

Eu estava atingindo essa limitação ao tentar despejar nosso banco de dados dev e carregá-lo em um VMWare virt local. Finalmente percebi que o servidor de desenvolvimento remoto era de 64 bits e criei um virt de 32 bits. Acabei de criar um virt de 64 bits e consegui carregar o banco de dados localmente.

Ryan Olson
fonte
2

Acabei de contornar esse erro alterando apenas os valores do "comprimento" no banco de dados original para um total de cerca de "1000" alterando sua estrutura e, em seguida, exportando a mesma para o servidor. :)

Vishwadeep Kapoor
fonte
0

Eu estava enfrentando o mesmo problema, usei a consulta abaixo para resolvê-lo.

Ao criar o banco de dados, você pode usar a codificação utf-8

por exemplo. create database my_db character set utf8 collate utf8mb4;

EDITAR: (Considerando sugestões de comentários) utf8_bin alterado para utf8mb4

Sudhir Dhumal
fonte
2
Isso me apontou na direção certa. Para mim, tive que mudar meu agrupamento para: utf8_general_ci
Ian Newland
2
Esta NÃO é a direção certa, não faça isso! O MySQL utf8NÃO é utf8, é um formato proprietário bugado que nunca deveria ter se concretizado. utf8mb4é verdadeiro utf8e é o padrão recomendado para utf8suporte adequado .
Geoffrey
utf8mb4é a codificação correta para usar no mysql e nãoutf8
alok
Foi apenas um exemplo - de qualquer maneira, atualizei a resposta.
Sudhir Dhumal