Erlang: Mnesia: поиск и обновление на основе полей, отличных от ключа - PullRequest
6 голосов
/ 01 декабря 2009

У меня есть таблица в mnesia, и мне нужно обновить отдельные поля в записях в ней. В соответствии с Erlang: Mnesia: Обновление значения одного поля в строке , если я делаю что-то вроде:

update_a(Tab, Key, Value) ->
  fun() ->
    [P] = mnesia:wread({Tab, Key}),
    mnesia:write(Tab, P#rec{a=Value}, write)
  end.

Теперь, как я понимаю, приведенный выше код читает запись P на основе Key, получая блокировку записи для записи, так что никакие другие транзакции не изменяют эту запись, пока она читается и записывается обратно (или короче, обновлено). Пока все хорошо.

Теперь мое требование состоит в том, чтобы мне нужно было читать записи, основанные как на Key, так и на одном другом поле в таблице, а затем выполнить обновление для него. Функция, которая сделает это при поиске, - mnesia:match_object. Теперь проблема в том, что функция поддерживает только блокировку чтения, а не блокировку записи, согласно http://www.erlang.org/doc/man/mnesia.html#match_object-3.

Следствием этого является то, что, предположим, в вышеуказанной функции я должен был использовать mnesia: match_object, я получу (группу) записей, все с блокировками чтения. После того, как я прочитал записи, мне нужно выполнить некоторые проверки восстановленных данных, а затем записать обратно обновленную запись, только если условие выполнено. Теперь предположим, что существуют две параллельные транзакции T1 и T2, инициированные двумя разными источниками. И T1, и T2 обращаются к одной и той же записи одновременно. Поскольку они заблокированы для чтения, T1 и T2 смогут читать записи параллельно. И T1, и T2 будут выполнять одну и ту же проверку для одной и той же записи, и, если условие соответствует, оба продолжат выполнять обновление. Но в моем коде, если бы T1 и T2 выполнялись последовательно, T1 внес бы изменения в запись, а в T2 он прочитал бы эти измененные записи, и условие не состоялось бы, и обновление не было бы выполнено.

Короче говоря, мне нужно написать записи блокировки, которые возвращаются mnesia: match_object. В документации четко указано, что поддерживается только блокировка чтения. Есть ли альтернативы?

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

mnesia:transaction(fun() -> mnesia:write(mytable, #rec{i={1,2}, a=2, b=3}, write) end).

Есть ли способ поиска записей, используя все равно?

Я пробовал это, но оба возвращали пустые результаты:

mnesia:transaction(fun()-> mnesia:read(mytable, {1,'_'}, read) end).
mnesia:transaction(fun()-> mnesia:read(mytable, {1,_}, read) end).

Ответы [ 4 ]

2 голосов
/ 15 августа 2010

Как это,

        case mnesia:match_object(#rec{name=Name, _='_'}) of
            [] -> not_found;
            [First|Rest] -> something
        end,
2 голосов
/ 01 декабря 2009

Даже если он возвращает их с блокировкой read, вы все равно можете обновить их с помощью записи после этого. Все чтение / чтение было просто оптимизацией.

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

1 голос
/ 03 декабря 2009

Вам не нужно беспокоиться об этом. Из документации Мнесия:

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

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

Допустим, у вас есть две транзакции, T1 и T2, которые выполняются параллельно:

  1. T1 выполняет mnesia:match_object и получает блокировку чтения для всех возвращаемых объектов.
  2. T2 делает эквивалент mnesia:match_object и получает блокировку чтения для тех же объектов.
  3. T2 пытается получить блокировку записи объектов (для ее редактирования).
  4. Mnesia автоматически прерывает T2, чтобы повторить попытку позже.
  5. T1 получает блокировку записи на объекты и редактирует их.
  6. T1 заканчивается.
  7. Мнезия повторяет Т2.

Обратите внимание, что T2 может быть повторен несколько раз, в зависимости от того, сколько времени требуется T1 для завершения (т. Е. Снятия блокировок).

Согласно моим тестам, поведение блокировки mnesia:match_object не согласовано. Например, mnesia:match_object(mytable, {mytable, 2, '_'}, LockType) заблокирует только запись с ключом 2, но mnesia:match_object(mytable, {mytable, '_', test}, LockType) заблокирует всю таблицу.

Также обратите внимание, что документация неверна, mnesia:match_object(Table, Pattern, write) работает и, похоже, следует тому же шаблону, что и «read», т.е. если вы укажете ключ, только соответствующая запись будет заблокирована от записи; если вы не укажете ключ, вся таблица будет заблокирована от записи.

Вы можете проверить это самостоятельно, выполнив что-то вроде этого:

test() ->
    mnesia:transaction(fun()-> 
        Table = mytable,
        Matches = mnesia:match_object(Table, {Table, 2, '_'}, write),
        io:format("matched: ~p~n", [Matches]),
        spawn(fun()->mnesia:transaction(fun()->
                        io:format("trying to read~n",[]),
                        io:format("read: ~p~n", [mnesia:read(Table, 2, read)])
                        end) end),        
        timer:sleep(1000),
        RereadMatches = lists:map(fun(#mytable{id=Id}) -> mnesia:read(Table, Id, write) end, Matches),
        io:format("reread matches: ~p~n", [RereadMatches])
        end).

Изменяя шаблон и тип блокировки, передаваемые на match_object, а номер ключа и тип блокировки, передаваемые на mnesia:read в порожденном процессе (или используя mnesia:write), вы можете проверить различные варианты блокировки.

Приложение: См. сообщение Ульфа Вигера на ту же тему.

Приложение 2: См. Раздел «Изоляция» в руководстве пользователя Mnesia.

Редактировать: Выше все было сделано для таблицы типа набора, match_object поведение блокировки может быть другим для таблицы типа сумки.

0 голосов
/ 01 декабря 2009

Разве вы не можете просто заблокировать таблицу в вашей транзакции заранее с помощью mnesia:write_lock_table(Tab)?

...