MySQL: Warum ist auto_increment nur auf Primärschlüssel beschränkt?

10

Ich weiß, dass MySQL die Spalten auto_increment auf Primärschlüssel beschränkt. Warum ist das? Mein erster Gedanke ist, dass es sich um eine Leistungsbeschränkung handelt, da es wahrscheinlich irgendwo eine Zählertabelle gibt, die gesperrt werden muss, um diesen Wert zu erhalten.

Warum kann ich nicht mehrere auto_increment-Spalten in derselben Tabelle haben?

Vielen Dank.

Christopher Armstrong
quelle
Ich habe gerade bemerkt, dass ich vergessen habe, das @pop in den Transaktionen zu verwenden. Ich habe das Beispiel erneut versucht und es am Ende meiner Antwort gepostet !!!
RolandoMySQLDBA
Ich mag diese Frage, weil ich mit MySQL über den Tellerrand hinaus gedacht habe, was ich an einem Freitagabend nicht sehr oft mache. +1 !!!
RolandoMySQLDBA

Antworten:

9

Warum sollten Sie eine auto_increment-Spalte haben, die nicht der Primärschlüssel ist?

Wenn Sie möchten, dass eine Spalte automatisch definiert wird, speichern Sie per Definition keine aussagekräftigen Daten in dieser Spalte. Der einzige Fall, in dem das Speichern nicht aussagekräftiger Informationen sinnvoll ist, ist der Sonderfall, dass Sie einen synthetischen Primärschlüssel haben möchten. In diesem Fall ist der Mangel an Informationen ein Vorteil, da kein Risiko besteht, dass jemals jemand in die Zukunft kommt und die Daten ändern möchte, da sich ein Attribut einer Entität geändert hat.

Noch seltsamer erscheint es, mehrere auto_increment-Spalten in derselben Tabelle zu haben. Die beiden Spalten hätten dieselben Daten - sie werden von demselben Algorithmus generiert und schließlich zur selben Zeit ausgefüllt. Ich nehme an, Sie könnten eine Implementierung entwickeln, bei der es möglich ist, dass sie leicht nicht synchron sind, wenn genügend Sitzungen gleichzeitig stattfinden. Aber ich kann mir nicht vorstellen, wie das jemals in einer Anwendung nützlich sein würde.

Justin Cave
quelle
Es war eher eine theoretische Frage - ich habe keine praktische Verwendung für mehrere auto_increment-Spalten, ich wollte nur die Erklärungen der Leute hören, warum dies nicht möglich war. Vielen Dank, dass Sie sich die Zeit genommen haben, um zu antworten! :)
Christopher Armstrong
2
"Warum sollten Sie eine auto_increment-Spalte haben wollen, die nicht der Primärschlüssel ist?" - Ich kann mir persönlich einige Gründe vorstellen. Aber das ist OT. :-)
Denis de Bernardy
Ich mache jetzt so etwas - und es sind keine bedeutungslosen Daten. Ich muss die Anzahl aller Datensätze kennen, die jemals in eine Tabelle eingefügt wurden, habe aber bereits nützlichere Primärschlüssel. Dies löst das, da jeder neue Datensatz einen Wert hat, von dem er ist. Eine (schwache) Analogie wäre die Anforderung "Sie sind unser 10.000ster Kunde". Kunden werden im Laufe der Zeit gelöscht, daher ist COUNT (*) nicht gültig.
Zylindrisch
Warum sollten Sie eine auto_increment-Spalte haben, die nicht der Primärschlüssel ist?
Phil_w
2
Warum sollten Sie eine auto_increment-Spalte haben, die nicht der Primärschlüssel ist? Mögliche Gründe sind: Weil die PK auch verwendet wird, um die Zeilen physisch zu ordnen. Beispiel: Angenommen, Sie speichern Nachrichten für Benutzer. Sie müssen alle Nachrichten pro Benutzer lesen, damit sie zum effizienten Abrufen zusammengeführt werden können. Da innodb sie nach PK sortiert, möchten Sie möglicherweise Folgendes tun: Erstellen von Tabellennachrichten (Benutzer, ID, TXT, Primärschlüssel (Benutzer, ID))
phil_w
8

Tatsächlich ist das Attribut AUTO_INCREMENT nicht mehr auf den PRIMARY KEY beschränkt. Früher war es in alten Versionen so - definitiv 3.23 und wahrscheinlich 4.0. Das MySQL-Handbuch für alle Versionen seit 4.1 liest sich immer noch so

Es kann nur eine AUTO_INCREMENT-Spalte pro Tabelle geben, sie muss indiziert sein und darf keinen DEFAULT-Wert haben.

Sie können also tatsächlich eine AUTO_INCREMENT-Spalte in einer Tabelle haben, die nicht der Primärschlüssel ist. Wenn das Sinn macht, ist das ein anderes Thema.

Ich sollte auch erwähnen, dass eine AUTO_INCREMENT-Spalte immer ein Integer-Typ sein sollte (technisch ist auch ein Gleitkomma-Typ zulässig) und dass sie UNSIGNED sein sollte. Ein SIGNED-Typ verschwendet nicht nur die Hälfte des Schlüsselraums, sondern kann auch zu großen Problemen führen, wenn versehentlich ein negativer Wert eingefügt wird.

Schließlich MySQL 4.1 und höher definiert einen Typ alias SERIEN für BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.

die XL
quelle
1
+1 für die Tatsache, dass auto_increment nicht auf PKs beschränkt ist. Ich bin mir nicht sicher, warum die Verwendung negativer Zahlen für einen Ersatzschlüssel zu "großen Problemen" führt.
Nvogel
4

Dies ist eine interessante Frage, da verschiedene Datenbanken einzigartige Ansätze für die Bereitstellung von auto_increment haben.

MySQL : Es wird nur ein auto_increment-Schlüssel generiert, um eine Zeile in einer Tabelle eindeutig zu identifizieren. Es gibt nicht viele Erklärungen dafür, sondern nur die Implementierung. Abhängig vom Datentyp werden die Werte für die automatische Inkrementierung durch die Länge des Datentyps in Byte festgelegt:

  • Max TINYINT ist 127
  • Maximal UNSIGNED TINTINT ist 255
  • Max INT ist 2147483647
  • Max UNSIGNED INT ist 4294967295

PostgreSQL

Der interne Datentyp seriell wird für die automatische Inkrementierung von 1 auf 2.147.483.647 verwendet. Größere Bereiche sind mit bigserial zulässig.

Oracle : Das Schemaobjekt mit dem Namen SEQUENCE kann neue Zahlen erstellen, indem einfach die Funktion nextval aufgerufen wird. PostgreSQL hat auch einen solchen Mechanismus.

Hier ist eine nette URL, die angibt, wie andere DBs sie angeben: http://www.w3schools.com/sql/sql_autoincrement.asp

Wenn Sie nun wirklich mehrere auto_increment-Spalten in einer einzigen Tabelle haben möchten, müssen Sie dies emulieren.

Zwei Gründe, warum Sie dies emulieren müssen:

  1. MySQL enthält nur eine Inkrementspalte pro Tabelle, ebenso wie PostgreSQL, Oracle, SQL Server und MS Access.
  2. MySQL hat kein SEQUENCE-Schemaobjekt wie Oracle und PostgreSQL.

Wie würdest du es emulieren ???

Verwenden mehrerer Tabellen mit nur einer Spalte auto_increment und Zuordnen zu den gewünschten Spalten in Zieltabellen. Hier ist ein Beispiel:

Kopieren Sie dieses Beispiel und fügen Sie es ein:

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;

Dadurch wird eine Tabelle mit Pop-Quiz für Lehrer erstellt. Ich habe auch fünf Sequenzemulatoren erstellt, einen für jeden Tag der Schulwoche. Jeder Sequenzemulator fügt den Wert 0 in die Spalte val ein. Wenn der Sequenzemulator leer ist, beginnt er mit Wert 0, nächster Wert 1. Wenn nicht, wird die nächste Wert-Spalte inkrementiert. Sie können dann die nextval-Spalte aus dem Sequenzemulator abrufen.

Hier sind die Beispielergebnisse aus dem Beispiel:

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>

Wenn Sie in MySQL wirklich mehrere automatische Inkrementierungswerte benötigen, ist dies die beste Möglichkeit, diese zu emulieren.

Versuche es !!!

UPDATE 2011-06-23 21:05

Ich habe gerade in meinem Beispiel bemerkt, dass ich den @ pop-Wert nicht verwende.

Dieses Mal habe ich 'pop_tue = pop_tue + 1' durch 'pop_tue = @pop' ersetzt und das Beispiel wiederholt:

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
quelle
Ihre Zusammenfassung ist nicht vollständig korrekt: PostgreSQL unterstützt genau wie Oracle eine beliebige Anzahl von Spalten mit automatischer Inkrementierung (seriell) (in beiden Fällen werden die Spalten mit einem Wert aus einer Sequenz gefüllt). PostgreSQL bietet auch den bigserialDatentyp an, der einen Bereich bietet, der viel größer als 2.147.483.647 ist
a_horse_with_no_name
@a_horse_with_no_name: Entschuldigung für das Versehen. Ich bin immer noch ein Geselle mit Postgresql. Ich werde meine Antwort später aktualisieren. Ich bin unterwegs Antwort vom iPhone. Haben Sie einen guten Tag!
RolandoMySQLDBA
0

Wie XL sagt, ist es nicht nur auf Primärschlüssel beschränkt. Es ist eine mögliche Einschränkung, dass Sie nur eine solche Spalte pro Tabelle haben können. Die beste Problemumgehung besteht jedoch darin, so viele Zahlen zu generieren, wie Sie in einer anderen Tabelle benötigen, und diese dann dort einzufügen, wo Sie möchten.

nvogel
quelle