Какой-то «разные индексы автоинкремента» для значений первичного ключа - PullRequest
1 голос
/ 24 сентября 2011

У меня есть таблица с id (первичный ключ с автоматическим приращением), uid (ключ ссылается на идентификатор пользователя, например) и что-то еще, что по моему вопросу выигралоне имеет значения.

Я хочу сделать, давайте позвоним, разные автоинкрементные клавиши на id для каждой uid записи.

Итак, Я добавлю запись с uid 10, и поле id для этой записи будет иметь 1 , так как ранее не было записей со значением 10 в uid .Я добавлю новый с uid 4 и его id будет 3 , потому что у меня уже было два ввода с uid 4 .

... Очень очевидное объяснение, но я пытаюсь быть максимально понятным и ясным, чтобы продемонстрировать идею ... ясно.

  1. Какой механизм SQL может предоставить такоефункциональность изначально?(не на базе Microsoft / Oracle)
  2. Если их нет, как мне лучше всего их воспроизвести?Возможно, триггеры?
  3. Имеет ли эта функция более подходящее имя?
  4. Если вы знаете о ядре базы данных не SQL, предоставляющем такую ​​функциональность, назовите его в любом случае, мне любопытно.

Спасибо.

Ответы [ 4 ]

3 голосов
/ 10 октября 2011

MySQL движок MySQL может сделать это.См. Их руководство в разделе Использование AUTO_INCREMENT :

Для таблиц MyISAM вы можете указать AUTO_INCREMENT для вторичного столбца в индексе из нескольких столбцов.В этом случае сгенерированное значение для столбца AUTO_INCREMENT рассчитывается как MAX (auto_increment_column) + 1 префикс WHERE = данный префикс.Это полезно, когда вы хотите поместить данные в упорядоченные группы.

Документы продолжаются после этого абзаца, показывая пример.

Механизм InnoDB в MySQL не поддерживает эту функцию, что вызывает сожаление, поскольку лучше использовать InnoDB почти во всех случаях.

Вы не можете эмулировать это поведение, используя триггеры (или любые операторы SQL, ограниченные областью транзакций) без блокировки таблиц в INSERT.Рассмотрим следующую последовательность действий:

  1. Марио начинает транзакцию и вставляет новую строку для пользователя 4.
  2. Билл начинает транзакцию и вставляет новую строку для пользователя 4.
  3. Сессия Марио запускает триггер для вычисления MAX (id) +1 для пользователя 4. Вы получаете 3.
  4. Сессия Билла запускает триггер для вычисления MAX (id).Я получаю 3.
  5. Сессия Билла заканчивает свою ВСТАВКУ и фиксирует.
  6. Сессия Марио пытается завершить свою ВСТАВКУ, но строка с (userid = 4, id = 3) теперь существует, поэтому Мариовозникает конфликт первичного ключа.

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

Решения для этого либо:

  • Получите эксклюзивную блокировку таблицы. Перед попыткой ВСТАВИТЬ заблокируйте стол.Это необходимо, чтобы предотвратить одновременные вставки от создания условия гонки , как в примере выше.Необходимо заблокировать всю таблицу, так как вы пытаетесь ограничить INSERT, нет конкретной строки для блокировки (если вы пытаетесь управлять доступом к данной строке с помощью UPDATE, вы можете заблокировать только определенную строку).Но блокировка таблицы приводит к тому, что доступ к таблице становится последовательным, что ограничивает вашу пропускную способность.

  • Делайте это вне области транзакции. Создайте номер идентификатора таким образом, чтобыне будет скрыт от двух одновременных транзакций.Кстати, именно этим и занимается AUTO_INCREMENT.Каждое из двух одновременных сеансов получит уникальное значение идентификатора, независимо от порядка их выполнения или порядка фиксации.Но для отслеживания последнего сгенерированного идентификатора по идентификатору пользователя требуется доступ к базе данных или дублированное хранилище данных.Например, ключ memcached для каждого идентификатора пользователя, который может быть увеличен атомарно .

Относительно легко гарантировать, что вставки получат уникальные значения.Но трудно гарантировать, что они получат последовательных порядковых значений.Также учтите:

  • Что произойдет, если вы ВСТАВИТЕ в транзакцию, но затем откатитесь?Вы выделили значение id 3 в этой транзакции, а затем я выделил значение 4, поэтому, если вы откатываетесь и я фиксируюсь, теперь есть пробел.
  • Что произойдет, если INSERT завершится неудачно из-за других ограничений натаблица (например, другой столбец НЕ NULL)?Вы можете получить пробелы и в этом случае.
  • Если вы когда-либо удаляете строку, нужно ли перенумеровать все последующие строки для одного и того же идентификатора пользователя?Что это делает с вашими memcached записями, если вы используете это решение?
0 голосов
/ 10 октября 2011

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

Это также значительно упрощает вставку данных, а такжеспособен справляться с удаляемыми записями, не создавая «дыр» в вашей последовательности.


Если вам это НУЖНО, будьте осторожны с именами полей.Если у вас в таблице uid и id, я ожидаю, что id будет уникальным в этой таблице, а uid будет ссылаться на что-то еще.Возможно, вместо этого используйте имена полей property_id и amendment_id.

С точки зрения реализации, как правило, есть два варианта.


1),Триггер

Реализации различаются, но логика остается той же.Поскольку вы не указываете СУБД (кроме НЕ MS / Oracle), общая логика проста ...

  • Начать транзакцию (часто это неявно уже запущено внутри триггеров)
  • Найдите MAX(amendment_id) для вставляемого property_id
  • Обновите добавленное значение с помощью MAX(amendment_id) + 1
  • Подтвердите транзакцию

Что нужно знатьare ...
- одновременная вставка нескольких записей
- вставка записей с уже заполненным идентификатором поправки
- обновления, изменяющие существующие записи


2).Хранимая процедура

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

  • Неявным образом вы знаете, что имеете дело только с одной записью,
  • Вы просто не предоставляете параметр для полей DEFAULT.
  • Вы знаете, что обновления / удаления могут и не могут произойти.
  • Вы можете реализовать всю бизнес-логику без скрытых триггеров


Я лично рекомендую маршрут хранимой процедуры, но триггеры работают.

0 голосов
/ 10 октября 2011

Важно правильно определить типы данных.

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

Если запись идентифицируется как (entity_id,version_number), примите это описание и используйте его напрямую, а не искажая значение ваших ключей. Вам придется писать запросы, которые ограничивают номер версии, но это нормально. Базы данных хороши в таких вещах.

version_number может быть отметкой времени, как предполагает a_horse_with_no_name. Это довольно хорошая идея. Нет значительного снижения производительности при использовании временных меток вместо простых целых чисел. означает , что более важно.

Вы можете вести таблицу «последней версии», которая содержит для каждой entity_id только запись с самой последней version_number. Это будет больше работать для вас, так что делайте это только в том случае, если вам действительно нужна производительность.

0 голосов
/ 24 сентября 2011

SQL Server должен позволить вам сделать это.Если вы не можете реализовать это, используя вычисляемый столбец (вероятно, нет - есть некоторые ограничения), вы наверняка сможете реализовать его в триггере .

* 1007.* MySQL также позволит вам реализовать это с помощью триггеров.

...