Сохранение реляционной целостности с Mnesia

Недавно я погружался в Erlang, и я решил использовать Mnesia для выполнения моей работы с базой данных, поскольку он может хранить любую структуру данных Erlang без проблем, масштабировать с легкостью, использоваться со списком и т.д.

Исходя из стандартных баз данных SQL, большинство строк могут и должны быть идентифицированы с помощью первичного ключа, обычно с автоматическим добавочным целым. По умолчанию Mnesia считает, что первым полем строки является ее ключ. Насколько я знаю, он также не имеет возможности иметь автоматически увеличивающийся ключ.

Учитывая, что у меня есть эти вымышленные записи, представляющие мои таблицы:

-record(user, {name, salt, pass_hash, email}).
-record(entry, {title, body, slug}).
-record(user_entry, {user_name, entry_title}).

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

Скажите, что пользователь изменил свое имя или изменил название записи после редактирования. Как я могу убедиться, что мои данные все еще правильно связаны? Обновление каждой таблицы с использованием имени пользователя при ее изменении звучит как ужасная идея независимо от того, как она была помещена.

Какой был бы лучший способ внедрения какой-то системы первичных ключей в Mnesia?

Кроме того, как будет выглядеть таблица-посредник типа user_entry, если первое поле обычно является ключом? В противном случае лучше было бы представлять отношения "многие ко многим" в Mnesia?

Ответ 1

Я предпочитаю использовать GUID вместо автоматического инкремента ints как искусственные внешние ключи. Существует модуль Erlang uuid, доступный в GitHub, или вы можете использовать {now(), node()}, учитывая, что now/0 doc говорит: "Также гарантируется, что последующие вызовы этого BIF возвращают непрерывно возрастающие значения".

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

Не забывайте, что вам не нужно нормализовать данные в Mnesia даже до первой нормальной формы; в вашем примере я бы рассмотрел следующую структуру:

-record(user, {id, name, salt, pass_hash, email, entries}).
-record(entry, {id, title, body, slug, users}).

где entries и users - списки идентификаторов. Конечно, это зависит от запросов, которые вы хотите.

EDIT: исправлено как много-ко-многим, а не много-к-одному.

Ответ 2

Mnesia поддерживает последовательности (автоинкрементные целые числа) в форме mnesia:dirty_update_counter(Table, Key, Increment). Для его использования вам понадобится таблица с двумя атрибутами Key и Count. Несмотря на имя, dirty_update_counter является атомарным, хотя он не работает внутри транзакции.

Ульф Вигер сделал некоторые работы по предоставлению типичных функций РСУБД поверх mnesia в своем rdbms пакете. Его код обеспечивает внешние ограничения ключа, параметризованные индексы, ограничения по значению полей и т.д. К сожалению, этот код не обновлялся через два года, и, вероятно, будет сложно работать без особого опыта Erlang.

При проектировании и использовании mnesia вы должны помнить, что mnesia не является реляционной базой данных. Это транзакционное хранилище ключей/значений и гораздо проще в использовании, если вы не нормализуете.

Если ваши имена пользователей уникальны, вы можете использовать схему:

-record(user, {name, salt, pass_hash, email}).
-record(entry, {posted, title, body, slug, user_name}).

Где posted - время erlang: now(), когда статья загружается. user_name может потребоваться вторичный индекс, если вам часто требуется получить список всех статей для пользователя. Поскольку эти данные разделены на две таблицы, вам придется принудительно применять любые ограничения целостности в вашем коде приложения (например, не принимать записи без действительного имени пользователя).

Каждое значение поля в mnesia может быть любым термином erlang, поэтому, если вы не понимаете уникальный ключ в каком-либо конкретном поле, вы можете часто комбинировать некоторые поля, чтобы дать вам значение, которое всегда будет уникальным - возможно, {Имя пользователя, DatePosted, TimePosted}. Mnesia позволяет вам искать частичные ключи через mnesia:select(Table, MatchSpec). MatchSpecs довольно сложно писать вручную, поэтому помните, что ets:fun2ms/1 может преобразовать функцию psuedo erlang в matchspec для вас.

В этом примере fun2ms генерирует нам matchspec для поиска таблицы записей в блоге -record(entry, {key, title, slug, body})., где ключ {Username, {Year, Month, Day}, {Hour, Minute, Second}} - имя пользователя автора и дата и время публикации статьи. Ниже приведен пример заголовков всех сообщений в блоге TargetUsername в течение декабря 2008 года.

ets:fun2ms(fun (#entry{key={U, {Y,M,_D}, _Time}, title=T})
             when U=:=TargetUsername, Y=:=2008, M=:=12 ->
               T
           end).