2009-12-01 98 views
6

我在mnesia中有一个表,我需要更新其中记录中的单个字段。据Erlang : Mnesia : Updating a single field value in a row,如果我这样做:Erlang:Mnesia:查询并更新基于密钥以外的字段

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

现在我明白了,上面的代码读取根据Key记录P,获取关于记录写锁定,这样就没有其他事务修改该记录,而正在读取和写回(或简而言之,已更新)。到现在为止还挺好。

现在我的要求是我需要能够根据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返回的锁定记录。文档明确指出只支持读锁。有什么替代品吗?

更新: 我一直在尝试一点点,并且我认为可能的解决方案是使用复合键。假设我有写像一个表中的数据:

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). 

回答

1

您不必担心。从mnesia文档:

读锁可能是共享的,这意味着如果一个事务设法获取对一个项目的读锁定,其他事务也可能获得对同一项目的读锁定。但是,如果某人有读锁,则无法在同一项目上获取写锁。如果某人有写入锁定,则无法在同一项目上获取读取锁定或写入锁定。

如果事务对对象具有读锁,则该对象不能被另一事务编辑。

说你有两笔交易,T1和T2,这是在并行执行:

  1. T1确实mnesia:match_object,并获得所有返回的对象读取锁。
  2. T2执行一个等效的mnesia:match_object,并获取相同对象上的读取锁定。
  3. T2尝试获取对象的写入锁定(以编辑它)。
  4. Mnesia自动放弃T2,稍后重试。
  5. T1获取对象的写入锁定,并对其进行编辑。
  6. T1完成。
  7. Mnesia重试T2。

请注意,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)确实工作,似乎遵循相同的模式作为'读',即。如果指定密钥,则只有匹配的记录将被写入锁定;如果您不指定密钥,则整个表将被写入锁定。

你可以做这样的事情自己测试一下:

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). 

通过改变产生的进程,传递给mnesia:read模式,并传递给match_object锁类型和密钥号和锁型(或使用mnesia:write),您可以测试各种锁定行为。

附录:在同一主题上看到此post by Ulf Wiger

附录2:请参阅Mnesia用户指南中的section on "Isolation"

编辑:以上都是在集合型表上完成的,match_object锁定行为在袋型表上可能不同。

+0

这对我很有意义,谢谢! – ErJab 2009-12-03 06:11:01

0

你就不能与mnesia:write_lock_table(Tab)锁定表中您的交易事先?

+0

我认为这一点,但我只需要更新,从而锁定一个记录。该表将被多个客户端同时使用,并且锁定整个表以进行一次记录更新将会降低吞吐量。 – ErJab 2009-12-01 10:55:20

2

即使它将它们返回到read锁定下,仍然可以在写入后更新它们。整个读/写事情只是一个优化。

您可以使用ordered_set表来实现this mnesia-trick on compound keys

+0

我的应用程序需要几个客户端同时访问表。如果我首先使用mnesia:match_object获取读取的锁定记录,然后更新记录上的锁定,则在我用match_object获取记录并升级到写入锁定之间的时间内,不存在另一个事务的可能性使用mnesia:match_object读取记录,根据我的并发访问要求? – ErJab 2009-12-01 10:59:58

+1

不,整个mnesia:事务是原子的,它看到的是一致的数据版本。交易将被重试许多次,直到提交完成。 – Christian 2009-12-01 11:35:54

2

这样,

 case mnesia:match_object(#rec{name=Name, _='_'}) of 
      [] -> not_found; 
      [First|Rest] -> something 
     end,