2

在大多数应用程序中,很难避免查询用户想要查询的大量信息browse through。这就是导致我使用光标的原因。使用 mnesia,游标是使用qlc:cursor/1 或 qlc:cursor/2实现的。与他们合作了一段时间,多次面对这个问题,

11> qlc:next_answers(QC,3)。
** 异常错误:{qlc_cursor_pid_no_longer_exists,<0.59.0>}
     在函数 qlc:next_loop/3 中(qlc.erl,第 1359 行)
12>
我突然想到,整个游标必须在一个 mnesia 事务中:作为一个整体执行一次。像下面这样
E:\>erl
Eshell V5.9(使用 ^G 中止)
1> 失忆症:开始()。
行
2> rd(obj,{键,值})。
对象
3> mnesia:create_table(obj,[{attributes,record_info(fields,obj)}])。
{原子,好的}
4> 写 = fun(Obj) -> mnesia:transaction(fun() -> mnesia:write(Obj) end) 结束。
#Fun<erl_eval.6.111823515>
5> [写入(#obj{key = N,value = N * 2}) || N <- 列表:seq(1,100)],好的。
行
6> 记忆:交易(乐趣()->
            QC = cursor_server:cursor(qlc:q([XX || XX <- mnesia:table(obj)])),
            Ans = qlc:next_answers(QC,3),
            io:format("\n\tAns: ~p~n",[Ans])
    结尾)。
        答:[{obj,20,40},{obj,21,42},{obj,86,172}]
{原子,好的}
7>
当你尝试qlc:next_answers/2在 mnesia 事务之外调用 say: 时,你会得到一个异常。不仅在事务之外,而且即使该方法由与创建游标的进程不同的进程执行,也必然会发生问题。

另一个有趣的发现是,一旦您退出 mnesia 事务,mnesia 游标中涉及的一个进程(显然 mnesia 在后台生成一个进程)就会退出,导致游标无效。看看下面这个:
-模块(光标服务器)。
-编译(export_all)。
光标(Q)-> 案例记忆:is_transaction() 的 假-> F = fun(QH)-> qlc:cursor(QH,[]) 结束, mnesia:activity(transaction,F,[Q],mnesia_frag); 真-> qlc:光标(Q,[]) 结尾。
%% --- 模块结束 ------------------------------ -
然后在shell中,我使用该方法:
7> QC = cursor_server:cursor(qlc:q([XX || XX <- mnesia:table(obj)]))。
{qlc_cursor,{<0.59.0>,<0.30.0>}}
8> erlang:is_process_alive(list_to_pid("<0.59.0>"))。
错误的
9> erlang:is_process_alive(list_to_pid("<0.30.0>"))。
真的
10> 自我()。
<0.30.0>
11> qlc:next_answers(QC,3)。
** 异常错误:{qlc_cursor_pid_no_longer_exists,<0.59.0>}
     在函数 qlc:next_loop/3 中(qlc.erl,第 1359 行)
12>
因此,这使得构建一个用户需要浏览一组特定结果的 Web 应用程序变得非常困难,一组一组说:给他/她前 20 个,然后是下一个 20 等等 这涉及到,获得第一个结果,将它们发送到网页,然后等待用户单击NEXT然后请求qlc:cursor/2下一个 20 等等。这些操作无法在挂在 mnesia 事务中时完成!!!唯一可能的方法是生成一个将挂在那里的进程,接收并发送回下一个答案作为消息,并接收 next_answers 请求作为这样的消息:

-定义(CURSOR_TIMEOUT,计时器:小时(1))。

%% 初始请求在下面进行
请求(页面大小)->
    我 = 自我(),    
    CursorPid = spawn(?MODULE,cursor_pid,[Me,PageSize]),
    收到
        {initial_answers,Ans} ->
            %% 找到隐藏光标 Pid 的方法
            %% 在页面中以便后续请求
            %% 随之而来
            {Ans,pid_to_list(CursorPid)}
    在 ?CURSOR_TIMEOUT -> 超时之后
    结尾。

cursor_pid(ParentPid,PageSize)->
    F = 乐趣(Pid,N)->
            QC = cursor_server:cursor(qlc:q([XX || XX <- mnesia:table(obj)])),
            Ans = qlc:next_answers(QC,N),
            皮德!{initial_answers,Ans},
            收到
                {From,{next_answers,Num}} ->
                    从 !{next_answers,qlc:next_answers(QC,Num)},
                    %% 这里有问题!如何循环回
                    %% 检查:Erlang Y-Combinator
                删除->
                    %% 它可能已经死了,所以我们在这里要小心!
                    试试 qlc:delete_cursor(QC) 的
                        _ -> 好的
                    抓住
                        _:_ -> 好的
                    结尾,
                    退出(正常)
            在 ?CURSOR_TIMEOUT -> 退出(正常)之后
            结尾
        结尾,
    mnesia:activity(transaction,F,[ParentPid,PageSize],mnesia_frag)。

next_answers(CursorPid,PageSize)->
    list_to_pid(CursorPid) !{self(),{next_answers,PageSize}},
    收到
        {next_answers,Ans} ->
            {Ans,pid_to_list(CursorPid)}
    在 ?CURSOR_TIMEOUT -> 超时之后
    结尾。

这将产生管理进程退出、跟踪/监控等更复杂的问题我想知道为什么 mnesia 实施者没有看到这一点!

现在,这让我想到了我的问题。我一直在网上寻找解决方案,您可以查看出现问题的这些链接:mnemosyneUlf Wiger's Solution to Cursor ProblemsAMNESIA - mnesia 的 RDBMS 实现

1.有没有人知道如何以不同于记录的方式处理 mnesia 查询游标,并且值得分享?

2. mnesia 实现者决定在单个事务中强制使用游标的原因是什么:甚至是对next_answers?

3.从我所介绍的内容中,有什么我不明白的地方(除了我的糟糕的错误插图代码 - 请忽略那些)?

4. AMNESIA(在我上面给出的链接的第 4.7 节),有一个很好的游标实现,因为对 next_answers 的后续调用,不需要在同一个事务中,也不需要在同一个过程中。你会建议任何人因此而从 mnesia 切换到 amnesia 吗?而且,这个库是否仍然受支持?

5. Ulf Wiger,(许多 erlang 库,尤其是 GPROC 的作者),建议使用mnesia:select/4. 我将如何使用它来解决 Web 应用程序中的光标问题?

注意:请不要建议我离开 mnesia 并使用其他东西,因为我想使用 mnesia 来解决这个特定问题。感谢您抽出宝贵时间阅读所有这些问题。

4

2 回答 2

3

动机是事务抓取(在您的情况下)读取锁。锁不能保留在事务之外。

如果需要,您可以在dirty_context 中运行它,但是您会丢失事务属性,即表可能会在调用之间发生变化。

make_cursor() ->
    QD = qlc:sort(mnesia:table(person, [{traverse, select}])),
    mnesia:activity(async_dirty, fun() -> qlc:cursor(QD) end, mnesia_frag).

get_next(Cursor) ->
    Get = fun() -> qlc:next_answers(Cursor,5) end,
    mnesia:activity(async_dirty, Get, mnesia_frag).

del_cursor(Cursor) ->
    qlc:delete_cursor(Cursor).
于 2012-08-20T13:30:27.270 回答
1

我认为这可能会对您有所帮助:

使用async_dirty而不是事务

{Record,Cont}=mnesia:activity(async_dirty, fun mnesia:select/4,[md,[{Match_head,[Guard],[Result]}],Limit,read])

然后读取下一个限制记录数:

mnesia:activity(async_dirty, fun mnesia:select/1,[Cont])

完整代码:

-record(md,{id,name}).
batch_delete(Id,Limit) ->
    Match_head = #md{id='$1',name='$2'},
    Guard = {'<','$1',Id},
    Result = '$_',
    {Record,Cont} = mnesia:activity(async_dirty, fun mnesia:select/4,[md,[{Match_head,[Guard],[Result]}],Limit,read]),
    delete_next({Record,Cont}).

delete_next('$end_of_table') ->
    over;
delete_next({Record,Cont}) ->
    delete(Record),
    delete_next(mnesia:activity(async_dirty, fun mnesia:select/1,[Cont])).

delete(Records) ->
    io:format("delete(~p)~n",[Records]),
    F = fun() ->
        [ mnesia:delete_object(O) || O <- Records]
    end,
    mnesia:transaction(F).

请记住,您不能在一项事务中使用游标

于 2013-08-17T13:09:42.607 回答