Управление инкрементными счетчиками в СУБД mnesia? - PullRequest
3 голосов
/ 03 января 2011

Я понял, что mnesia не поддерживает функцию автоинкремента, как это делает MySQL или другие СУБД. Счетчики, о которых говорилось в документации mnesia, не очень хорошо объяснены.Например, я обнаружил одну функцию во всей документации, которая манипулирует счетчиками

mnesia:dirty_update_counter({Tab::atom(),Key::any()}, Val::positive_integer())

Итак, это меня некоторое время беспокоило, так как он работает с записями типа

{TabName, Key, Integer}
Это также неясно ивозможно, потому что ни одна книга erlang или документация по mnesia не предоставляют пример для объяснения этого. Это вынудило меня реализовать мои собственные API для манипулирования счетчиками. Так как я хотел иметь возможность получать доступ к своим записям и управлять ими с помощью счетчиков, мне пришлось включить поле под названием «counter 'в моих записях, затем в точке добавления записи в таблицу, которая должна иметь счетчики, я делаю это так:
#recordname{field1 = Val1,...,counter = auto_increment(?THIS_TABLE)}

Положение полей счетчиков не имеет значения.API реализован следующим образом:

%% @doc this function is called whenever u are writing a new record in the table<br>%% by giving its result to the counter field in your record.<br>%% @end<br>%%<br>%% @spec auto_increment(TableName::atom()) -> integer() | exit(Reason)<br><b>
auto_increment(TableName)->
    case lists:member(counter,table_info(TableName,attributes)) of
        false -> erlang:exit({counter,field,not_found,in_table,TableName});
        true -> table_info(TableName,size) + 1
    end.<br>
table_info(Tab,Item)->
    F = fun({X,Y}) -> mnesia:table_info(X,Y) end,
    mnesia:activity(transaction,F,[{Tab,Item}],mnesia_frag).</b>

Чтобы объяснить это, если поле счетчика не является атрибутом таблицы, я принудительно позволяю процессу, который пытается выполнить этот код, выйти с причиной, поэтомуесли бы программисты вызывали это внутри try ... catch или case (catch ...) тела, они легко увидели бы, что не так.В качестве альтернативы, я мог бы спросить, выполняется ли этот фрагмент кода в транзакции, используя mnesia:is_transaction(), и если это возвращает true, я вызываю mnesia:abort/1, если false, я мог бы просто выйти с причиной. Также я использую mnesia_frag в mnesiaфункция деятельности, потому что эта реализация будет работать независимо от свойств фрагментации таблицы. Если я использую mnesia:transaction(Fun), фрагментированные таблицы станут несовместимыми, потому что этот вызов будет обращаться только к начальному фрагменту (базовой таблице).
Теперь, когда запись удалена из таблицы со счетчиками, нам нужно изменить порядок в таблице.Эта операция является дорогостоящей, так как требует итерации по всей таблице. Так как, если они удаляют запись с счетчиком = 5, запись с счетчиком = 6 должна стать счетчиком = 5 и так далее, следуя шаблону.Все записи, счетчики которых были больше удаленных, должны быть уменьшены.Таким образом, передав значение счетчика, удаленное и TableName, можно выполнить итерацию по таблице, используя
mnesia:foldl/3 or mnesia:foldr/3 , the difference between these two comes in only with ordered table types
Вот функция, которая позаботится об этом:

<b>
auto_decrement(Counter_deleted,TableName)->
    Attrs = table_info(TableName,attributes),
    case lists:member(counter,Attrs) of
        false -> erlang:exit({counter,field,not_found,in_table,TableName});
        true -> 
            Counter_position = position(counter,Attrs) + 1,         
            Iterator =  fun(Rec,_) when element(Counter_position,Rec) > Counter_deleted -> 
                            Count = element(Counter_position,Rec),
                            New_rec = erlang:setelement(Counter_position,Rec,Count - 1),
                            mnesia:write(TableName,New_rec,read),
                            [];
                        (_,_) -> []
                        end,
            Find = fun({Fun,Table}) -> mnesia:foldl(Fun, [],Table) end,
            mnesia:activity(transaction,Find,[{Iterator,TableName}],mnesia_frag)
    end.</b>


Вы заметили, что у меня есть код, который помогает мне динамически находить положение поля счетчика по записи.Код, который помогает мне сделать это, показан ниже:

<b>
position(_,[]) -> -1;
position(Value,List)->
    find(lists:member(Value,List),Value,List,1).

find(false,_,_,_) -> -1;
find(true,V,[V|_],N)-> N;
find(true,V,[_|X],N)->
    find(V,X,N + 1).

find(V,[V|_],N)-> N;
find(V,[_|X],N) -> find(V,X,N + 1).
</b>


Это так, потому что этот модуль не должен знать какие-либо записи программистов, чтобы помочь ему со счетчиками. Поэтому для доступа кзначение счетчика из записи с использованием функций манипулирования кортежем, таких как element(N::integer(),Tuple::tuple()), я должен динамически вычислять его положение в представлении кортежа записи.

These two functions have worked for me and are still working till auto_increment<br>is implemented in mnesia. 

Например, используя qlc (понимание списка запросов) для запроса таблиц с динамическими ограничениями, рассмотрите следующие фрагменты кода:
<b>
select(Q)->
    F = fun(QH) -> qlc:e(QH) end,
    mnesia:activity(transaction,F,[Q],mnesia_frag).

read_by_custom_validation(Validation_fun,From_table)->
    select(qlc:q([X || X <- mnesia:table(From_table),Validation_fun(X) == true])).

%% Applying the two functions....
find_records_with_counter(From_this,To_that) when <br>       is_integer(From_this),is_integer(To_that),To_that > From_this ->
    F = fun(#recordName{counter = N}) when N >= From_this,N =< To_That -> true;
        (_) -> false
    end,
    read_by_custom_validation(F,TableName).
</b>

В системе управления запасами этоработает ...

<b>

(stock_project@127.0.0.1)6> stock:get_items_in_range(1,4).
[#item{item_id = "D694",name = "cement",
       time_stamp = {"30/12/2010","11:29:10 am"},
       min_stock = 500,units = "bags",unit_cost = 20000,
       state = available,last_modified = undefined,
       category = "building material",counter = 1},
 #item{item_id = "131B",name = "nails",
       time_stamp = {"30/12/2010","11:29:10 am"},
       min_stock = 20000,units = "kgs",unit_cost = 1000,
       state = available,last_modified = undefined,
       category = "building material",counter = 2},
 #item{item_id = "FDD9",name = "iron sheets",
       time_stamp = {"30/12/2010","11:29:10 am"},
       min_stock = 20,units = "bars",unit_cost = 50000,
       state = available,last_modified = undefined,
       category = "building material",counter = 3},
 #item{item_id = "09D4",name = "paint",
       time_stamp = {"30/12/2010","11:29:10 am"},
       min_stock = 30000,units = "tins",unit_cost = 5000,
       state = available,last_modified = undefined,
       category = "building material",counter = 4}]
(stock_project@127.0.0.1)7>
</b>

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

1 Ответ

4 голосов
/ 13 мая 2011

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

  1. Если вы хотите подсчитать, сколько элементов в таблице. Создайте отдельную таблицу записей {count, TableName, Count} и используйте операцию dirty_update_counter в операциях добавления и удаления ваших таблиц хранения.
  2. Если вы хотите получить данные в определенном порядке (например, в порядке их добавления), используйте решение OrderedBy, показанное здесь .
  3. Если вы хотите быстрый поиск по внешнему ключу, я бы посмотрел на игру с типом ваших ключей (двоичные значения для item_id, возможно, быстрее) и проверил, если вы получите какое-либо существенное улучшение.

Кроме того, я не понимаю, почему необходимо обновлять счетчик для каждой записи при удалении записи в таблице. Я не думал, что MySQL или любая другая СУБД сделали это.

...