Mnesia:如何同时锁定多行,以便我可以写/读一组"一致"的记录

Jr0*_*Jr0 7 erlang mnesia

我希望我如何开始讨论我的问题

拿一个包含26个键的表,az并让它们具有整数值.创建一个过程,哎呀,一遍又一遍地做两件事

  1. 在一个事务中,为a,bc写入随机值,使得这些值总是总和为10
  2. 在另一个事务中,读取a,bc的值,并在其值不等于10时进行投诉

如果你甚至旋转了一些这些过程,你会很快看到a,bc处于其值不总和为10的状态.我相信没有办法让mnesia"锁定这3条记录开始写入(或读取)之前,人们只能让记录锁定记录(可以这么说),这允许记录集的值违反我的"必须总和到10"的约束.

如果我是对的,这个问题的解决方案包括

  1. 在写入(或读取)3个记录集之前锁定整个表 - 我讨厌将整个表锁定为3个记录,
  2. 创建一个流程,跟踪谁正在读取或写入哪些密钥,并保护批量操作与其他任何人一起编写或读取,直到操作完成.当然我必须确保所有进程都使用了这个...废话,我想这意味着将我自己的AccessMod写为activity/4的第四个参数,这似乎是一个非常重要的练习
  3. 其他一些我不够聪明的事情.

想法?

好吧,我是一个雄心勃勃的Erlang newbee,很抱歉,如果这是一个愚蠢的问题,但是

我正在构建一个特定于应用程序的内存分布式缓存,我需要能够在一个事务中编写Key,Value对的集合,并在一个事务中检索值集.换句话说,我需要1)将40个键值对写入缓存,并确保在这个多键写操作期间没有其他人可以读取或写入这40个键中的任何一个; 并且,2)在一次操作中读取40个键并获得40个值,知道从该读取操作开始到结束的所有40个值都没有改变.

我能想到这样做的唯一方法是将整个表锁定在fetch_keylist([ListOfKeys])的开头或write_keylist的开头([KeyValuePairs],但我不想这样做,因为我有许多进程同时执行自己的multi_key读取和写入,并且我不想在任何进程需要读取/写入相对较小的记录子集时锁定整个表.

救命?

试图更清楚:我不认为这只是关于使用香草交易

我要问一个比这更微妙的问题.想象一下,我有一个进程,在一个事务中,迭代10个记录,锁定它们.现在假设这个过程开始但在它迭代到第3个记录之前另一个过程更新了第3个记录.就事务而言,这将是正常的,因为第一个进程尚未锁定第三个记录(尚未),而OTHER进程修改它并在第一个进程到达之前将其释放.我想要的是保证,一旦我的第一个进程启动 ,没有其他进程可以触及10个记录,直到第一个进程完成它们.

问题解决了 - 我是个傻瓜......我猜...谢谢你的病人,特别是Hynek -Pichi- Vychodil!我准备我的测试代码显示的问题,我可以在事实上重现该问题.然后我简化了代码的可读性,问题就消失了.我无法再次重现这个问题.这对我来说既尴尬又神秘,因为我有几天这个问题.mnesia也从不抱怨我在事务之外执行操作而且我的代码中没有任何脏事务,我不知道我是如何将这个bug引入我的代码的!

我把隔离的概念砸到脑海里,不会再怀疑它是否存在.

谢谢你的教育.

实际上,事实证明问题是事务中使用try/catch围绕mnesia操作.请看这里了解更多.

Hyn*_*dil 2

Mnesia 交易正是为你做这件事。这就是事务的用途,除非你做了肮脏的操作。因此,只需将写入和读取操作放入一个事务中,mnesia 就会完成其余操作。一个事务中的所有操作都作为一个原子操作完成。Mnesia 事务隔离级别有时称为“可串行化”,即最强的隔离级别。

编辑:

看来你错过了关于 Erlang 并发进程的一个重要观点。(公平地说,这不仅在 Erlang 中如此,而且在任何真正的并发环境中都是如此,当有人争论时,它不是真正的并发环境。)除非您进行一些同步,否则您无法区分哪个操作首先发生,哪个操作第二个发生。实现此同步的唯一方法是使用消息传递。关于 Erlang 中的消息,您只能保证一件事,即从一个进程发送到另一进程的消息的顺序。这意味着当您发送两条消息时M1,它们在M2进程A之间B以相同的顺序到达。但是,如果您M1Ato发送消息BM2Cto发送消息B,它们可以按任何顺序到达。只是因为您如何知道您先发送了哪条消息?M1如果您从Ato发送消息B,然后M2Ato发送消息C,并且何时M2到达从toC发送消息,则您无法保证在发送消息之前到达 to ,情况会更糟。即使在当前实施中,它也会发生在一台虚拟机中。但你不能依赖它,因为它没有得到保证,甚至在下一个版本的 VM 中也可能会因为不同调度程序之间的消息传递实现而发生变化。M3CBM1BM3

它说明了并发进程中的事件排序问题。现在回到 mnesia 事务。Mnesia 交易必须没有副作用fun。这意味着可能没有任何消息从交易向外发送。所以你无法判断哪个事务首先开始以及何时开始。您唯一能判断交易是否成功的事情是,他们命令您只能根据其效果来确定。当你考虑到这一点时,你的微妙澄清就没有意义了。即使在事务实现中是一个一个地读取一个键,一个事务也会以原子操作的方式读取所有键,并且您的写操作也将作为原子操作执行。您无法判断在第一个事务中读取第一个密钥后是否发生了对第二个事务中的第四个密钥的写入,因为从外部无法观察到它。两个事务都将作为单独的原子操作按特定顺序执行。从外部角度来看,所有密钥都将在同一时间点被读取,这是记忆力强制执行的工作。如果您从事务内部发送消息,则违反了 mnesia 事务属性,并且它会表现得很奇怪,您不会感到惊讶。具体来说,这个消息可以发送多次。

编辑2:

如果您启动其中的几个进程,您很快就会看到 a、b 和 c 处于其值总和不等于 10 的状态。

我很好奇你为什么认为它会发生或者你测试过它?向我展示你的测试用例,我将展示我的测试用例:

-module(transactions).

-export([start/2, sum/0, write/0]).

start(W, R) ->
  mnesia:start(),
  {atomic, ok} = mnesia:create_table(test, [{ram_copies,[node()]}]),
  F = fun() ->
      ok = mnesia:write({test, a, 10}),
      [ ok = mnesia:write({test, X, 0}) || X <-
        [b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z]],
      ok
  end,
  {atomic, ok} = mnesia:transaction(F),
  F2 = fun() ->
    S = self(),
    erlang:send_after(1000, S, show),
    [ spawn_link(fun() -> writer(S) end) || _ <- lists:seq(1,W) ],
    [ spawn_link(fun() -> reader(S) end) || _ <- lists:seq(1,R) ],
    collect(0,0)
  end,
  spawn(F2).

collect(R, W) ->
  receive
    read -> collect(R+1, W);
    write -> collect(R, W+1);
    show ->
      erlang:send_after(1000, self(), show),
      io:format("R: ~p, W: ~p~n", [R,W]),
      collect(R, W)
  end.

keys() ->
  element(random:uniform(6),
    {[a,b,c],[a,c,b],[b,a,c],[b,c,a],[c,a,b],[c,b,a]}).

sum() ->
  F = fun() ->
      lists:sum([X || K<-keys(), {test, _, X} <- mnesia:read(test, K)])
  end,
  {atomic, S} = mnesia:transaction(F),
  S.

write() ->
  F = fun() ->
      [A, B ] = L = [ random:uniform(10) || _ <- [1,2] ],
      [ok = mnesia:write({test, K, V}) || {K, V} <- lists:zip(keys(),
          [10-A-B|L])],
      ok
  end,
  {atomic, ok} = mnesia:transaction(F),
  ok.

reader(P) ->
  case sum() of
    10 ->
      P ! read,
      reader(P);
    _ ->
      io:format("ERROR!!!~n",[]),
      exit(error)
  end.

writer(P) ->
  ok = write(),
  P ! write,
  writer(P).
Run Code Online (Sandbox Code Playgroud)

如果它不起作用,那将是一个非常严重的问题。有一些重要的应用程序,包括依赖它的支付系统。如果您的测试用例显示它已损坏,请通过 erlang-bugs@erlang.org 报告错误