MySQL: Perché auto_increment è limitato alle sole chiavi primarie?


10

So che MySQL limita le colonne auto_increment alle chiavi primarie. Perchè è questo? Il mio primo pensiero è che si tratta di una limitazione delle prestazioni, dal momento che probabilmente c'è qualche tabella contatore da qualche parte che deve essere bloccata per ottenere questo valore.

Perché non posso avere più colonne auto_increment nella stessa tabella?

Grazie.


Ho appena notato che ho dimenticato di usare @pop nelle transazioni. Ho riprovato l'esempio e l'ho pubblicato in fondo alla mia risposta !!!
RolandoMySQLDBA

Mi piace questa domanda perché mi ha fatto pensare fuori dagli schemi con MySQL che non faccio molto venerdì sera. +1 !!!
RolandoMySQLDBA

Risposte:


9

Perché dovresti avere una colonna auto_increment che non è la chiave primaria?

Se si desidera che una colonna sia un auto_increment, per definizione, non si memorizzano dati significativi in ​​quella colonna. L'unico caso in cui la memorizzazione di informazioni non significative ha senso è il caso speciale in cui si desidera disporre di una chiave primaria sintetica. In tal caso, la mancanza di informazioni è un vantaggio perché non vi è alcun rischio che qualcuno possa mai venire in futuro e voler cambiare i dati perché alcuni attributi di alcune entità sono cambiati.

Avere più colonne auto_increment nella stessa tabella sembra ancora più strano. Le due colonne avrebbero gli stessi dati: vengono generate dallo stesso algoritmo e dopo tutto popolate allo stesso tempo. Suppongo che potresti realizzare un'implementazione in cui è possibile che siano leggermente fuori sincrono se ci fossero abbastanza sessioni simultanee. Ma non riesco a immaginare come ciò possa mai essere utile in un'applicazione.


Era più una domanda teorica - non ho alcun uso pratico per avere più colonne auto_increment, volevo solo ascoltare le spiegazioni delle persone sul perché non fosse possibile. Grazie per il tempo dedicato a rispondere! :)
Christopher Armstrong,

2
"Perché dovresti avere una colonna auto_increment che non è la chiave primaria?" - Posso pensare a qualche motivo, personalmente. Ma quello è OT. :-)
Denis de Bernardy,

Sto facendo qualcosa del genere ora - e non sono dati insignificanti. Ho bisogno di sapere un conteggio di tutti i record mai inseriti in una tabella, ma ho già chiavi primarie più utili. Questo risolve questo, come ogni nuovo record ha un valore di quello che è. Un'analogia (debole) sarebbe il requisito "sei il nostro 10.000 ° cliente". I clienti vengono eliminati nel tempo, quindi COUNT (*) non è valido.
Cilindrico,

Perché dovresti avere una colonna auto_increment che non è la chiave primaria?
phil_w,

2
Perché dovresti avere una colonna auto_increment che non è la chiave primaria? Le possibili ragioni includono: Perché il PK è anche usato per ordinare fisicamente le righe. Esempio: supponiamo che memorizzi i messaggi per gli utenti. Devi leggere tutti i messaggi per utente, quindi vuoi averli insieme per un recupero efficiente. Dato che innodb li ordina per PK, potresti voler farlo: creare messaggi di tabella (utente, id, txt, chiave primaria (utente, id))
phil_w

8

In realtà l'attributo AUTO_INCREMENT non è limitato al PRIMARY KEY (non più). Lo era nelle vecchie versioni, sicuramente 3.23 e probabilmente 4.0. Comunque il manuale di MySQL per tutte le versioni dalla 4.1 si legge così

Può esserci solo una colonna AUTO_INCREMENT per tabella, deve essere indicizzata e non può avere un valore DEFAULT.

Quindi puoi davvero avere una colonna AUTO_INCREMENT in una tabella che non è la chiave primaria. Se questo ha senso, è un argomento diverso però.

Vorrei anche menzionare che una colonna AUTO_INCREMENT dovrebbe sempre essere un tipo intero (tecnicamente è consentito anche un tipo a virgola mobile) e che dovrebbe essere UNSIGNED. Un tipo SIGNED non solo spreca metà dello spazio chiave, ma può anche causare enormi problemi se un valore negativo viene inserito per errore.

Infine, MySQL 4.1 e versioni successive definiscono un alias di tipo SERIAL per BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.


1
+1 per il fatto che auto_increment non è limitato ai PK. Non sono sicuro del motivo per cui pensi che usare numeri negativi per una chiave surrogata porti a "enormi problemi".
nvogel,

4

Questa è una domanda interessante perché database diversi hanno approcci unici per fornire auto_increment.

MySQL : viene generata una sola chiave auto_increment per identificare in modo univoco una riga in una tabella. Non ci sono molte spiegazioni dietro il perché, ma solo l'implementazione. A seconda del tipo di dati, i valori di auto_increment sono fissati dalla lunghezza del tipo di dati in byte:

  • Max TINYINT è 127
  • Max UNSIGNED TINTINT è 255
  • Max INT è 2147483647
  • Max UNSIGNED INT è 4294967295

PostgreSQL

Il tipo di dati interno seriale viene utilizzato per l'incremento automatico da 1 a 2.147.483.647. Le gamme più grandi sono consentite usando bigserial.

Oracle : l'oggetto schema chiamato SEQUENCE può creare nuovi numeri semplicemente convocando la funzione nextval. Anche PostgreSQL ha un tale meccanismo.

Ecco un bel URL che fornisce come altri DB li specificano: http://www.w3schools.com/sql/sql_autoincrement.asp

Ora riguardo alla tua domanda, se vuoi davvero avere più colonne auto_increment in una singola tabella, dovrai emularlo.

Due motivi per cui devi emulare questo:

  1. MySQL può contenere solo una colonna di incremento per tabella, così come PostgreSQL, Oracle, SQL Server e MS Access.
  2. MySQL non ha un oggetto schema SEQUENCE come Oracle e PostgreSQL.

Come lo emuleresti ???

Utilizzo di più tabelle con una sola colonna auto_increment e mappatura delle colonne desiderate nelle tabelle di destinazione. Ecco un esempio:

Copia e incolla questo esempio:

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;

Questo creerà una tabella di quiz pop per gli insegnanti. Ho anche creato cinque emulatori di sequenza, uno per ogni giorno della settimana scolastica. Ogni emulatore di sequenza funziona inserendo il valore 0 nella colonna val. Se l'emulatore di sequenza è vuoto, inizia con val 0, nextval 1. In caso contrario, la colonna nextval viene incrementata. È quindi possibile recuperare la colonna nextval dall'emulatore di sequenza.

Ecco i risultati di esempio dell'esempio:

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 hai davvero bisogno di più valori di incremento automatico in MySQL, questo è il modo più vicino per emularlo.

Provaci !!!

AGGIORNAMENTO 2011-06-23 21:05

Ho appena notato nel mio esempio che non uso il valore @pop.

Questa volta ho sostituito "pop_tue = pop_tue + 1" con "pop_tue = @pop" e ho ripetuto l'esempio:

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>

Il tuo riepilogo non è completamente corretto: PostgreSQL supporta un numero qualsiasi di colonne di incremento automatico (seriale), proprio come fa Oracle (in entrambi i casi le colonne sono riempite con un valore da una sequenza). PostgreSQL offre anche il bigserialtipo di dati che offre un intervallo molto maggiore di 2.147.483.647
a_horse_with_no_name

@a_horse_with_no_name: scusa per la svista. Sono ancora un giornalista con Postgresql. Aggiornerò la mia risposta più tardi. Sono sulla strada della risposta da iPhone. Buona giornata!
RolandoMySQLDBA,

0

Come dice XL, non si limita solo alle chiavi primarie. È una potenziale limitazione che puoi avere solo una di queste colonne per tabella, ma la soluzione migliore è quella di generare tutti i numeri che ti servono in un'altra tabella e quindi inserirli dove necessario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.