MySQL: restrição exclusiva na coluna grande

10

Estou tentando criar uma tabela do InnoDB que contém uma VARCHARcoluna que pode conter até 3071 caracteres. Eu gostaria de impor uma UNIQUErestrição aos dados desta coluna.

O MySQL parece impor restrições usando um índice. No InnoDB, os tamanhos dos índices parecem estar limitados a 767 bytes - não o suficiente para a VARCHAR(3071)coluna que está mantendo os dados.

Alguma idéia de como fazer com que o banco de dados imponha a exclusividade dos dados, sem comprometer o tamanho máximo dos dados ou o uso do InnoDB?

Guus
fonte

Respostas:

10

Você não deseja um gen_clust_index gigantesco (Índice Interno de Cluster). Esse tamanho é incrivelmente grande mesmo para um índice secundário.

Pode ser necessário recorrer a gatilhos ou procedimentos armazenados para verificar a chave com bastante antecedência.

Você também pode pensar em executar uma chamada de função SHA1 usando o VARCHAR(3071)campo SHA1 retornará um campo de 40 caracteres. Esse hash pode ser exatamente o que você precisa indexar.

Suponha que você tenha isso

CREATE TABLE mytable
(
    id int not null auto_increment,
    txt VARCHAR(3071),
    primary key (id)
) ENGINE=InnODB;

e você deseja criar um UNIQUEíndice em txt. Experimente a abordagem SHA1

CREATE TABLE mytablenew LIKE mytable;
ALTER TABLE mytable ADD txtsha1 CHAR(40);
ALTER TABLE mytable ADD UNIQUE KEY (txtsha1);
INSERT INTO mytablenew (id,txt,txtsha1)
SELECT id,txt,SHA1(txt) FROM mytable;

Então, conte-os

SELECT COUNT(1) FROM mytable;
SELECT COUNT(1) FROM mytablenew;

Se a contagem é a mesma, PARABÉNS !!! Agora você tem um índice exclusivo de comprimento 40. Você pode terminar com:

ALTER TABLE mytable RENAME mytableold;
ALTER TABLE mytablenew RENAME mytable;
DROP TABLE mytableold;

Isso pode ser mais atomicamente, conforme apontado nos comentários abaixo:

RENAME TABLE mytable TO mytableold, mytablenew TO mytable;
DROP TABLE mytableold;

Faça isso em qualquer tabela que você pretenda ter nesta grande coluna. Você deve se lembrar de adicionar o SHA1 dos dados junto com os dados INSERT.

As chances de chaves duplicadas são de 1 em 2 à potência 160 (1,4650503737309029182036848327163e + 48). Se eu conseguir o valor exato, publicarei algum dia).

De uma chance !!!

RolandoMySQLDBA
fonte
+1 Esta é basicamente uma ideia muito boa! Eu hovewer combiná-lo com um gatilho que iria verificar se duas digere são os mesmos, o conteúdo é o mesmo também, exatamente como um HashMap em Java funciona ...
ppeterka
11
Rolando - Eu tenho muitas queixas: (1) sha1 deve ser ascii, não utf8. (2) sha1 pode ser BINARY (20) se você usar HEX () e UNHEX (). (3) para tornar a renomeação atômica, sem tempo de inatividade, renomeie a tabela mytable para mytableold, mytablenew para mytable. Então DROP TABLE mytableold depois que você estiver satisfeito. (4) As probabilidades citadas são para uma única linha. (5) 2 64 está errado - são 2 160. (6) as probabilidades de uma mesa são: "Existe uma chance em 2 53 de uma mesa com 2 53 linhas ter um dup sha1". (6a) É mais provável que você pegue um asteróide enquanto coleciona uma mega loteria.
Rick James
@ RickJames todos os pontos mencionados. Desculpe minha matemática ruim para o ponto 5, é 2 ^ 160. Eu ajustei # 3 na minha resposta.
RolandoMySQLDBA
11
Pessoal, as probabilidades que você apresenta assumem: 1. O SHA tem uma distribuição perfeita; e 2. a entrada é perfeitamente aleatória. O SHA não tem uma distribuição de prefeito. Nem qualquer outro algoritmo de hash. A entrada não é perfeitamente aleatória e, embora o SHA, como outros resumos, cause grandes mudanças na saída para pequenas alterações na entrada, é perfeitamente possível que alguns conjuntos de entradas gerem a mesma saída e que essas entradas tenham algumas sistemáticas. conexão entre eles. Agora, estou aqui a maior parte da tagarelar, pois as chances são muito baixas; mas ainda assim, deve-se ter cuidado.
Shlomi Noach
As chaves de hash do @ShlomiNoach podem ser trabalhosas. Nesse ritmo, até a função PASSWORD seria aceitável ( palominodb.com/blog/2011/12/04/hashing-algorithm-mysql-password ) #
RolandoMySQLDBA