MySQL: Por que o auto_increment é limitado apenas às chaves primárias?

10

Eu sei que o MySQL limita as colunas auto_increment às chaves primárias. Por que é isso? Meu primeiro pensamento é que é uma restrição de desempenho, pois provavelmente existe uma tabela de contador em algum lugar que deve ser bloqueada para obter esse valor.

Por que não posso ter várias colunas de incremento automático na mesma tabela?

Obrigado.

Christopher Armstrong
fonte
Acabei de perceber que esqueci de usar o @pop nas transações. Tentei o exemplo novamente e postei na parte inferior da minha resposta !!!
RolandoMySQLDBA
Eu gosto dessa pergunta porque ela me fez pensar fora da caixa com o MySQL, o que eu não faço muito na sexta à noite. +1 !!!
RolandoMySQLDBA

Respostas:

9

Por que você deseja ter uma coluna de incremento automático que não é a chave primária?

Se você deseja que uma coluna seja um incremento automático, por definição, não está armazenando dados significativos nessa coluna. O único caso em que o armazenamento de informações não significativas faz sentido é o caso especial em que você deseja ter uma chave primária sintética. Nesse caso, a falta de informações é um benefício, pois não há risco de alguém aparecer no futuro e desejar alterar os dados porque algum atributo de alguma entidade foi alterado.

Ter várias colunas de incremento automático na mesma tabela parece ainda mais estranho. As duas colunas teriam os mesmos dados - elas estão sendo geradas pelo mesmo algoritmo e são preenchidas ao mesmo tempo, afinal. Suponho que você possa criar uma implementação em que seja possível que eles estejam um pouco fora de sincronia se houver sessões simultâneas suficientes. Mas não consigo imaginar como isso seria útil em um aplicativo.

Justin Cave
fonte
Era mais uma questão teórica - não tenho utilidade prática por ter várias colunas de auto_increment, só queria ouvir as explicações das pessoas sobre por que não era possível. Obrigado por reservar um tempo para responder! :)
Christopher Armstrong
2
"Por que você deseja ter uma coluna de incremento automático que não é a chave primária?" - Eu posso pensar em algumas razões, pessoalmente. Mas isso é OT. :-)
Denis de Bernardy
Estou fazendo algo assim agora - e não são dados sem sentido. Preciso conhecer uma contagem de todos os registros já inseridos em uma tabela, mas já temos chaves primárias mais úteis. Isso resolve isso, pois todo novo registro tem um valor do qual é. Uma analogia (fraca) seria o requisito "você é o nosso 10.000 clientes". Os clientes são excluídos com o tempo, portanto, COUNT (*) não é válido.
cilíndrico
Por que você deseja ter uma coluna de incremento automático que não é a chave primária?
46540 phpBaixar
2
Por que você deseja ter uma coluna de incremento automático que não é a chave primária? Os possíveis motivos incluem: Como o PK também é usado para ordenar as linhas fisicamente. Exemplo: digamos que você armazene mensagens para os usuários. Você precisa ler todas as mensagens por usuário, portanto, deseja tê-las juntas para uma recuperação eficiente. Desde tipos InnoDB eles por PK você pode querer fazer isso: criar mensagens de mesa (usuário, id, txt, chave primária (usuário, id))
phil_w
8

De fato, o atributo AUTO_INCREMENT não está limitado à PRIMARY KEY (mais nada). Costumava ser assim em versões antigas - definitivamente 3.23 e provavelmente 4.0. Ainda assim, o manual do MySQL para todas as versões desde o 4.1 é assim:

Pode haver apenas uma coluna AUTO_INCREMENT por tabela, ela deve ser indexada e não pode ter um valor DEFAULT.

Portanto, você pode realmente ter uma coluna AUTO_INCREMENT em uma tabela que não é a chave primária. Se isso faz sentido, é um tópico diferente.

Também devo mencionar que uma coluna AUTO_INCREMENT deve sempre ser um tipo inteiro (tecnicamente também é permitido um tipo de ponto flutuante) e que deve ser UNSIGNED. Um tipo ASSINADO não apenas desperdiça metade do espaço-chave, mas também pode levar a enormes problemas se um valor negativo for inserido por acidente.

Finalmente, o MySQL 4.1 e posterior define um alias de tipo SERIAL para BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.

o XL
fonte
11
+1 pelo fato de o incremento automático não se limitar a PKs. Não sei por que você acha que o uso de números negativos para uma chave substituta leva a "grandes problemas".
Nvogel
4

Essa é uma pergunta interessante, porque diferentes bancos de dados têm abordagens exclusivas para fornecer incremento automático.

MySQL : Apenas uma chave auto_increment é gerada para identificar exclusivamente uma linha em uma tabela. Não há muita explicação por trás do motivo, mas apenas implementação. Dependendo do tipo de dados, os valores de incremento automático são fixados pelo comprimento do tipo de dados em bytes:

  • Max TINYINT é 127
  • O TINTINT UNSIGNED máximo é 255
  • O INT máximo é 2147483647
  • O INT NÃO ASSINADO máximo é 4294967295

PostgreSQL

O tipo de dados interno serial é usado para incremento automático de 1 a 2.147.483.647. Intervalos maiores são permitidos usando bigserial.

Oracle : O objeto de esquema chamado SEQUENCE pode criar novos números simplesmente chamando a função nextval. O PostgreSQL também possui esse mecanismo.

Aqui está um bom URL que fornece como outros bancos de dados os especificam: http://www.w3schools.com/sql/sql_autoincrement.asp

Agora, com relação à sua pergunta, se você realmente deseja ter várias colunas de incremento automático em uma única tabela, precisará imitar isso.

Duas razões pelas quais você deve emular isso:

  1. O MySQL acomoda apenas uma coluna de incremento por tabela, assim como o PostgreSQL, Oracle, SQL Server e MS Access.
  2. O MySQL não possui um objeto de esquema SEQUENCE como Oracle e PostgreSQL.

Como você o emularia ???

Usando várias tabelas que possuem apenas uma coluna de incremento automático e mapeando-as para as colunas desejadas nas tabelas de destino. Aqui está um exemplo:

Copie e cole este exemplo:

use test
DROP TABLE IF EXISTS teacher_popquizzes;
CREATE TABLE teacher_popquizzes
(
    teacher varchar(20) not null,
    class varchar(20) not null,
    pop_mon INT NOT NULL DEFAULT 0,
    pop_tue INT NOT NULL DEFAULT 0,
    pop_wed INT NOT NULL DEFAULT 0,
    pop_thu INT NOT NULL DEFAULT 0,
    pop_fri INT NOT NULL DEFAULT 0,
    id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
INSERT INTO teacher_popquizzes (teacher,class) VALUES
('mr jackson','literature'),
('mrs andrews','history'),
('miss carroll','spelling');
DROP TABLE IF EXISTS mon_seq;
DROP TABLE IF EXISTS tue_seq;
DROP TABLE IF EXISTS wed_seq;
DROP TABLE IF EXISTS thu_seq;
DROP TABLE IF EXISTS fri_seq;
CREATE TABLE mon_seq
(
    val INT NOT NULL DEFAULT 0,
    nextval INT NOT NULL DEFAULT 1,
    PRIMARY KEY (val)
);
CREATE TABLE tue_seq LIKE mon_seq;
CREATE TABLE wed_seq LIKE mon_seq;
CREATE TABLE thu_seq LIKE mon_seq;
CREATE TABLE fri_seq LIKE mon_seq;
BEGIN;
INSERT INTO tue_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;
SELECT nextval INTO @pop FROM mon_seq;
UPDATE teacher_popquizzes SET pop_tue = pop_tue + 1 WHERE id = 2;
COMMIT;
BEGIN;
INSERT INTO tue_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;
SELECT nextval INTO @pop FROM tue_seq;
UPDATE teacher_popquizzes SET pop_tue = pop_tue + 1 WHERE id = 1;
COMMIT;
BEGIN;
INSERT INTO wed_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;
SELECT nextval INTO @pop FROM wed_seq;
UPDATE teacher_popquizzes SET pop_wed = pop_wed + 1 WHERE id = 2;
COMMIT;
SELECT * FROM teacher_popquizzes;

Isso criará uma tabela de questionários pop para professores. Também criei cinco emuladores de sequência, um para cada dia da semana escolar. Cada emulador de sequência funciona inserindo o valor 0 na coluna val. Se o emulador de sequência estiver vazio, ele começará com val 0, nextval 1. Caso contrário, a coluna nextval será incrementada. Você pode buscar a coluna nextval no emulador de sequência.

Aqui estão os resultados da amostra do exemplo:

mysql> CREATE TABLE teacher_popquizzes
    -> (
    ->     teacher varchar(20) not null,
    ->     class varchar(20) not null,
    ->     pop_mon INT NOT NULL DEFAULT 0,
    ->     pop_tue INT NOT NULL DEFAULT 0,
    ->     pop_wed INT NOT NULL DEFAULT 0,
    ->     pop_thu INT NOT NULL DEFAULT 0,
    ->     pop_fri INT NOT NULL DEFAULT 0,
    ->     id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
    -> );
Query OK, 0 rows affected (0.11 sec)

mysql> INSERT INTO teacher_popquizzes (teacher,class) VALUES
    -> ('mr jackson','literature'),
    -> ('mrs andrews','history'),
    -> ('miss carroll','spelling');
Query OK, 3 rows affected (0.03 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> DROP TABLE IF EXISTS mon_seq;
Query OK, 0 rows affected (0.06 sec)

mysql> DROP TABLE IF EXISTS tue_seq;
Query OK, 0 rows affected (0.03 sec)

mysql> DROP TABLE IF EXISTS wed_seq;
Query OK, 0 rows affected (0.03 sec)

mysql> DROP TABLE IF EXISTS thu_seq;
Query OK, 0 rows affected (0.05 sec)

mysql> DROP TABLE IF EXISTS fri_seq;
Query OK, 0 rows affected (0.07 sec)

mysql> CREATE TABLE mon_seq
    -> (
    ->     val INT NOT NULL DEFAULT 0,
    ->     nextval INT NOT NULL DEFAULT 1,
    ->     PRIMARY KEY (val)
    -> );
Query OK, 0 rows affected (0.12 sec)

mysql> CREATE TABLE tue_seq LIKE mon_seq;
Query OK, 0 rows affected (0.09 sec)

mysql> CREATE TABLE wed_seq LIKE mon_seq;
Query OK, 0 rows affected (0.08 sec)

mysql> CREATE TABLE thu_seq LIKE mon_seq;
Query OK, 0 rows affected (0.07 sec)

mysql> CREATE TABLE fri_seq LIKE mon_seq;
Query OK, 0 rows affected (0.14 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO tue_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;
Query OK, 1 row affected (0.00 sec)

mysql> SELECT nextval INTO @pop FROM mon_seq;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> UPDATE teacher_popquizzes SET pop_tue = pop_tue + 1 WHERE id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.03 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO tue_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;
Query OK, 2 rows affected (0.00 sec)

mysql> SELECT nextval INTO @pop FROM tue_seq;
Query OK, 1 row affected (0.00 sec)

mysql> UPDATE teacher_popquizzes SET pop_tue = pop_tue + 1 WHERE id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.03 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO wed_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;
Query OK, 1 row affected (0.00 sec)

mysql> SELECT nextval INTO @pop FROM wed_seq;
Query OK, 1 row affected (0.00 sec)

mysql> UPDATE teacher_popquizzes SET pop_wed = pop_wed + 1 WHERE id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.03 sec)

mysql> SELECT * FROM teacher_popquizzes;
+--------------+------------+---------+---------+---------+---------+---------+----+
| teacher      | class      | pop_mon | pop_tue | pop_wed | pop_thu | pop_fri | id |
+--------------+------------+---------+---------+---------+---------+---------+----+
| mr jackson   | literature |       0 |       1 |       0 |       0 |       0 |  1 |
| mrs andrews  | history    |       0 |       1 |       1 |       0 |       0 |  2 |
| miss carroll | spelling   |       0 |       0 |       0 |       0 |       0 |  3 |
+--------------+------------+---------+---------+---------+---------+---------+----+
3 rows in set (0.00 sec)

mysql>

Se você realmente precisa de vários valores de incremento automático no MySQL, esta é a maneira mais próxima de imitá-lo.

De uma chance !!!

ATUALIZAÇÃO 23-06-2011 21:05

Acabei de notar no meu exemplo que não uso o valor @pop.

Desta vez, substituí 'pop_tue = pop_tue + 1' por 'pop_tue = @pop' e tentei novamente o exemplo:

mysql> use test
Database changed
mysql> DROP TABLE IF EXISTS teacher_popquizzes;
Query OK, 0 rows affected (0.05 sec)

mysql> CREATE TABLE teacher_popquizzes
    -> (
    ->     teacher varchar(20) not null,
    ->     class varchar(20) not null,
    ->     pop_mon INT NOT NULL DEFAULT 0,
    ->     pop_tue INT NOT NULL DEFAULT 0,
    ->     pop_wed INT NOT NULL DEFAULT 0,
    ->     pop_thu INT NOT NULL DEFAULT 0,
    ->     pop_fri INT NOT NULL DEFAULT 0,
    ->     id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
    -> );
Query OK, 0 rows affected (0.06 sec)

mysql> INSERT INTO teacher_popquizzes (teacher,class) VALUES
    -> ('mr jackson','literature'),
    -> ('mrs andrews','history'),
    -> ('miss carroll','spelling');
Query OK, 3 rows affected (0.03 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> DROP TABLE IF EXISTS mon_seq;
Query OK, 0 rows affected (0.03 sec)

mysql> DROP TABLE IF EXISTS tue_seq;
Query OK, 0 rows affected (0.03 sec)

mysql> DROP TABLE IF EXISTS wed_seq;
Query OK, 0 rows affected (0.03 sec)

mysql> DROP TABLE IF EXISTS thu_seq;
Query OK, 0 rows affected (0.01 sec)

mysql> DROP TABLE IF EXISTS fri_seq;
Query OK, 0 rows affected (0.03 sec)

mysql> CREATE TABLE mon_seq
    -> (
    ->     val INT NOT NULL DEFAULT 0,
    ->     nextval INT NOT NULL DEFAULT 1,
    ->     PRIMARY KEY (val)
    -> );
Query OK, 0 rows affected (0.08 sec)

mysql> CREATE TABLE tue_seq LIKE mon_seq;
Query OK, 0 rows affected (0.09 sec)

mysql> CREATE TABLE wed_seq LIKE mon_seq;
Query OK, 0 rows affected (0.13 sec)

mysql> CREATE TABLE thu_seq LIKE mon_seq;
Query OK, 0 rows affected (0.11 sec)

mysql> CREATE TABLE fri_seq LIKE mon_seq;
Query OK, 0 rows affected (0.08 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO tue_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;

Query OK, 1 row affected (0.01 sec)

mysql> SELECT nextval INTO @pop FROM tue_seq;
Query OK, 1 row affected (0.00 sec)

mysql> UPDATE teacher_popquizzes SET pop_tue = @pop WHERE id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.03 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO tue_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;

Query OK, 2 rows affected (0.00 sec)

mysql> SELECT nextval INTO @pop FROM tue_seq;
Query OK, 1 row affected (0.00 sec)

mysql> UPDATE teacher_popquizzes SET pop_tue = @pop WHERE id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.03 sec)

mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> INSERT INTO wed_seq (val) VALUES (0) ON DUPLICATE KEY UPDATE nextval = nextval + 1;

Query OK, 1 row affected (0.01 sec)

mysql> SELECT nextval INTO @pop FROM wed_seq;
Query OK, 1 row affected (0.00 sec)

mysql> UPDATE teacher_popquizzes SET pop_wed = @pop WHERE id = 2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> COMMIT;
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT * FROM teacher_popquizzes;
+--------------+------------+---------+---------+---------+---------+---------+----+
| teacher      | class      | pop_mon | pop_tue | pop_wed | pop_thu | pop_fri | id |
+--------------+------------+---------+---------+---------+---------+---------+----+
| mr jackson   | literature |       0 |       2 |       0 |       0 |       0 |  1 |
| mrs andrews  | history    |       0 |       1 |       1 |       0 |       0 |  2 |
| miss carroll | spelling   |       0 |       0 |       0 |       0 |       0 |  3 |
+--------------+------------+---------+---------+---------+---------+---------+----+
3 rows in set (0.00 sec)

mysql>
RolandoMySQLDBA
fonte
Seu resumo não está completamente correto: o PostgreSQL suporta qualquer número de colunas de incremento automático (serial), assim como o Oracle (em ambos os casos, as colunas são preenchidas com um valor de uma sequência). O PostgreSQL também oferece o bigserialtipo de dados que oferece um intervalo muito maior que 2.147.483.647
a_horse_with_no_name
@a_horse_with_no_name: desculpe pela supervisão. Eu ainda sou um viajante com o postgresql. Vou atualizar minha resposta mais tarde. Estou na estrada resposta do iPhone. Tenha um bom dia!
RolandoMySQLDBA
0

Como o XL diz, não se limita apenas às chaves primárias. É uma limitação em potencial que você só pode ter uma dessas colunas por tabela, mas a melhor solução é gerar quantos números você precisar em outra tabela e depois inseri-los onde for necessário.

nvogel
fonte