Mnesia, mnesia_frag, and table locks using index_match_object et al.

Scott Lystig Fritchie <>
Tue Feb 28 06:14:00 CET 2006


Greetings.  I just spent some time experimenting with a Mnesia table
like this one:

    -record(thing, {
              primary_key,
              secondary_key,
              other_attribute_1,
              other_attribute_2
              }).
    
    ok = mnesia:start(),
    {atomic, ok} =
        mnesia:create_table(thing, [{attributes, record_info(fields, thing)},
                                    {ram_copies, [node()]}, {type, set}]),
    {atomic, ok} = mnesia:add_table_index(thing, secondary_key).
    {atomic, ok} = mnesia:change_table_frag(thing, {activate, []}),
    [{atomic, ok} = mnesia:change_table_frag(thing, {add_frag, [node()]}) ||
        _I <- lists:seq(1, 4)],
    MatchHead = #thing{secondary_key = bound_atom_value, _ = '_'},
    Thing = #thing{primary_key = blah_key, secondary_key = bound_atom_value},
    F = fun() -> mnesia:match_object(thing, MatchHead, write),
                 timer:sleep(10*1000),
		  end,
    mnesia:activity(transaction, F, [], mnesia_frag).

If I run this snippet of code, then sneak over onto another shell on
the same node and run mnesia:info(), I see this:

    ()2> mnesia:info().
    ---> Processes holding locks <--- 
    Lock: {{thing,'______WHOLETABLE_____'},write,{tid,10,<4491.36.0>}}
    Lock: {{thing_frag4,'______WHOLETABLE_____'},write,{tid,10,<4491.36.0>}}
    Lock: {{thing_frag2,'______WHOLETABLE_____'},write,{tid,10,<4491.36.0>}}
    Lock: {{thing_frag5,'______WHOLETABLE_____'},write,{tid,10,<4491.36.0>}}
    Lock: {{thing_frag3,'______WHOLETABLE_____'},write,{tid,10,<4491.36.0>}}
    ---> Processes waiting for locks <--- 
    [...]

The mnesia:index_read() function doesn't have an option to specify a
write lock.  The mnesia:index_match_object() function only support
'read' locking, but it also takes whole table locks.  Also,
mnesia:select() (using a full "match spec" using a guard list of []
and a result list of ['$_']) and mnesia:match_object() take whole
table locks, too.

After recovering from shock, I looked at mnesia_frag.erl.  Sure
enough, mnesia_frag:do_select/5 unconditionally takes a whole table
lock.(*)  Bummer.

The search element in MatchHead is indeed bound.  I was expecting that
I'd see something like this instead:

    ---> Processes holding locks <--- 
    Lock: {{thing,bound_atom_value},write,{tid,91994,<0.2828.0>}}
    Lock: {{thing_frag2,bound_atom_value},write,{tid,91994,<0.2828.0>}}
    [... one for each table fragment ...]

Question: Is it really necessary to take whole table locks in cases
like this?

For my purpose, I would like to insert a single record into the
'thing' table *and* guarantee that both primary_key and secondary_key
are unique.  The former is trivial: the table type is 'set'.  I was
hoping to do the latter by the following (simplifed!) without whole
table locks:

       %% Borrow the bindings for Thing and MatchHead from above
       [] = mnesia:read({thing, blah_key}),
       [] = mnesia:match_object(thing, MatchHead, write),
       ok = mnesia:write(Thing).

I don't want to be forced to create a separate Mnesia table based on:

    -record(secondary_to_primary, {
            secondary_key,	% matches secondary_key in 'thing' table
	    primary_key		% matches primary_key in 'thing' table
        }).

... where I take a write lock on secondary_to_primary's primary key.

So ... Am I smoking crack, wishing to do something crazy?

-Scott

(*) I'm assuming that all the Mnesia functions mentioned above, when
used with mnesia_frag, eventually get funnelled into
mnesia_frag:do_select/5.



More information about the erlang-questions mailing list