MySQL: почему auto_increment ограничивается только первичными ключами?

10

Я знаю, что MySQL ограничивает столбцы auto_increment первичными ключами. Почему это? Сначала я подумал, что это ограничение производительности, поскольку, возможно, где-то есть какая-то таблица счетчиков, которую нужно заблокировать, чтобы получить это значение.

Почему я не могу иметь несколько столбцов auto_increment в одной таблице?

Спасибо.

Кристофер Армстронг
источник
Я только заметил, что забыл использовать @pop в транзакциях. Я повторил пример и разместил его внизу моего ответа !!!
RolandoMySQLDBA
Мне нравится этот вопрос, потому что он заставил меня задуматься о MySQL, чего я не особо делаю в пятницу вечером. +1 !!!
RolandoMySQLDBA

Ответы:

9

Зачем вам нужен столбец auto_increment, который не является первичным ключом?

Если вы хотите, чтобы столбец был auto_increment, по определению вы не храните значимые данные в этом столбце. Единственный случай, когда хранение бессмысленной информации имеет смысл, - это особый случай, когда вы хотите иметь синтетический первичный ключ. В этом случае отсутствие информации является преимуществом, потому что нет никакого риска, что кто-то когда-либо появится в будущем и захочет изменить данные, потому что какой-то атрибут некоторого объекта изменился.

Наличие нескольких столбцов auto_increment в одной таблице кажется еще более странным. Эти два столбца будут иметь одинаковые данные - они генерируются по одному и тому же алгоритму и заполняются в одно и то же время. Я полагаю, вы могли бы придумать реализацию, в которой они могли бы быть немного не синхронизированы, если бы было достаточно одновременных сеансов. Но я не могу представить, как это было бы полезно в приложении.

Джастин Кейв
источник
Это был скорее теоретический вопрос - у меня нет практического использования для нескольких столбцов auto_increment, я просто хотел услышать объяснения людей о том, почему это невозможно. Спасибо, что нашли время ответить! :)
Кристофер Армстронг
2
"Зачем вам нужен столбец auto_increment, который не является первичным ключом?" - Я могу придумать несколько причин лично. Но это ОТ. :-)
Дени де Бернарди
Я делаю что-то вроде этого сейчас - и это не бессмысленные данные. Мне нужно знать количество всех записей, когда-либо вставленных в таблицу, но у меня уже есть более полезные первичные ключи. Это решает это, поскольку каждая новая запись имеет значение, которым она является. (Слабая) аналогия - требование «ты наш 10 000-й клиент». Со временем клиенты удаляются, поэтому COUNT (*) недействителен.
Цилиндрический
Зачем вам нужен столбец auto_increment, который не является первичным ключом?
phil_w
2
Зачем вам нужен столбец auto_increment, который не является первичным ключом? Возможные причины включают в себя: Потому что PK также используется для физического упорядочения строк. Пример: допустим, вы храните сообщения для пользователей. Вам необходимо прочитать все сообщения для каждого пользователя, поэтому вы хотите, чтобы они были вместе для эффективного поиска. Так как innodb сортирует их по PK, вы можете сделать это: создать табличные сообщения (user, id, txt, первичный ключ (user, id))
phil_w
8

Фактически, атрибут AUTO_INCREMENT не ограничен ПЕРВИЧНЫМ КЛЮЧОМ (больше). Раньше так было в старых версиях - определенно 3.23 и, вероятно, 4.0. Тем не менее, руководство по MySQL для всех версий, начиная с 4.1, читается так

В таблице может быть только один столбец AUTO_INCREMENT, он должен быть проиндексирован и не может иметь значение DEFAULT.

Таким образом, в таблице действительно может быть столбец AUTO_INCREMENT, который не является первичным ключом. Если это имеет смысл, это уже другая тема.

Я должен также упомянуть, что столбец AUTO_INCREMENT всегда должен быть целочисленным типом (технически тип с плавающей запятой также допускается) и что он должен быть UNSIGNED. Тип SIGNED не только тратит половину пространства ключа, но также может привести к огромным проблемам, если случайно вставить отрицательное значение.

Наконец, MySQL 4.1 и более поздние версии определяют псевдоним типа SERIAL для BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.

XL
источник
1
+1 за то, что auto_increment не ограничивается PK. Не уверен, почему вы думаете, что использование отрицательных чисел для суррогатного ключа приводит к «огромным проблемам».
nvogel
4

Это интересный вопрос, потому что разные базы данных имеют уникальные подходы для предоставления auto_increment.

MySQL : генерируется только один ключ auto_increment для уникальной идентификации строки в таблице. За этим стоит не много объяснений, а просто реализация. В зависимости от типа данных значения auto_increment фиксируются по длине типа данных в байтах:

  • Макс TINYINT составляет 127
  • Максимальный неподписанный оттенок составляет 255
  • Макс INT составляет 2147483647
  • Макс НЕ ПОДПИСАНО INT 4294967295

PostgreSQL

Внутренний серийный тип данных используется для автоматического увеличения от 1 до 2 147 483 647. Большие диапазоны разрешены при использовании bigserial.

Oracle : объект схемы SEQUENCE может создавать новые числа, просто вызывая функцию nextval. PostgreSQL также имеет такой механизм.

Вот хороший URL, который показывает, как их определяют другие БД: http://www.w3schools.com/sql/sql_autoincrement.asp

Теперь, что касается вашего вопроса, если вы действительно хотите иметь несколько столбцов auto_increment в одной таблице, вам придется подражать этому.

Две причины, почему вы должны подражать этому:

  1. MySQL поддерживает только один столбец приращения на таблицу, как это делает PostgreSQL, Oracle, SQL Server и MS Access.
  2. MySQL не имеет объекта схемы SEQUENCE, такого как Oracle и PostgreSQL.

Как бы вы подражали этому ???

Использование нескольких таблиц, имеющих только один столбец auto_increment, и сопоставление их с нужными столбцами в целевых таблицах. Вот пример:

Скопируйте и вставьте этот пример:

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;

Это создаст таблицу поп-тестов для учителей. Я также создал пять эмуляторов последовательности, по одному на каждый день школьной недели. Каждый эмулятор последовательности работает, вставляя значение 0 в столбец val. Если эмулятор последовательности пуст, он начинается с val 0, nextval 1. Если нет, столбец nextval увеличивается. Затем вы можете получить следующий столбец из эмулятора последовательности.

Вот пример результатов из примера:

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>

Если вам действительно нужно несколько значений автоинкремента в MySQL, это самый близкий способ эмулировать его.

Попробуйте!

ОБНОВЛЕНИЕ 2011-06-23 21:05

Я только заметил, что в моем примере я не использую значение @pop.

На этот раз я заменил «pop_tue = pop_tue + 1» на «pop_tue = @pop» и повторил пример:

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
источник
Ваше резюме не совсем корректно: PostgreSQL поддерживает любое количество столбцов с автоинкрементом (последовательным), как это делает Oracle (в обоих случаях столбцы заполняются значением из последовательности). PostgreSQL также предлагает bigserialтип данных, который предлагает диапазон, намного превышающий 2 147 483 647
a_horse_with_no_name
@a_horse_with_no_name: извините за недосмотр. Я все еще подмастерье с postgresql. Я обновлю свой ответ позже. Я в дороге отвечу с айфона. Хорошего дня!
RolandoMySQLDBA
0

Как говорит XL, он не ограничивается только первичными ключами. Потенциальным ограничением является то, что в каждой таблице может быть только один такой столбец, но лучший способ - сгенерировать столько чисел, сколько вам нужно, в другой таблице, а затем вставлять их там, где вам нужно.

nvogel
источник