mnesia的transform_table可以实现事务性的表结构转换,但是它本身有两个糟糕的地方:占用内存大、速度不够快。
先来看看mnesia:transform_table到底做了什么:
transform_table(Tab, Fun, NewAttrs, NewRecName) when is_function(Fun), is_list(NewAttrs), is_atom(NewRecName) -> schema_transaction(fun() -> do_transform_table(Tab, Fun, NewAttrs, NewRecName) end); transform_table(Tab, ignore, NewAttrs, NewRecName) when is_list(NewAttrs), is_atom(NewRecName) -> schema_transaction(fun() -> do_transform_table(Tab, ignore, NewAttrs, NewRecName) end); transform_table(Tab, Fun, NewAttrs, NewRecName) -> {aborted,{bad_type, Tab, Fun, NewAttrs, NewRecName}}. schema_transaction(Fun) -> case get(mnesia_activity_state) of undefined -> Args = [self(), Fun, whereis(mnesia_controller)], Pid = spawn_link(?MODULE, schema_coordinator, Args), receive {transaction_done, Res, Pid} -> Res; {'EXIT', Pid, R} -> {aborted, {transaction_crashed, R}} end; _ -> {aborted, nested_transaction} end. %% This process may dump the transaction log, and should %% therefore not be run in an application process %% schema_coordinator(Client, _Fun, undefined) -> Res = {aborted, {node_not_running, node()}}, Client ! {transaction_done, Res, self()}, unlink(Client); schema_coordinator(Client, Fun, Controller) when is_pid(Controller) -> %% Do not trap exit in order to automatically die %% when the controller dies link(Controller), unlink(Client), %% Fulfull the transaction even if the client dies Res = mnesia:transaction(Fun), Client ! {transaction_done, Res, self()}, unlink(Controller), % Avoids spurious exit message unlink(whereis(mnesia_tm)), % Avoids spurious exit message exit(normal).
看起来其实就是个transaction,不过同时做了schema的更新,这里有个坑后面会提到。总结下transform_table做的事情:
trans begin -> update schema -> copy records(write to list args) and update records -> trans end
既然如此,这里使用事务可以保证数据以及结构是同步更新的,而事务的过程中会拷贝至少一份数据(单节点),因此我们可以使用如下的思路来实现:
1. 保证更新数据时只有一个操作者(串行化)
2. trans begin -> update schema -> trans end -> update records
最终实现为:
%% @doc user-defined的transform userdefined_transform() -> io:format("userdefined_transform begin time ~p~n",
), {atomic, ok} = mnesia:transform_table(db_test_transform, ignore, [k, v, v2]), [begin case mnesia:dirty_read(db_test_transform, K) of {test, K, V} -> mnesia:dirty_write(db_test_transform, {test, K, V, 0}); _ -> ok end end || K <- mnesia:dirty_all_keys(db_test_transform)], io:format("userdefined_transform end time ~p~n",
), ok.
详细代码在这里: https://github.com/qingliangcn/erlang_test
结果:
测试自定义transform_table => mnesia_userdefined_transform:test_userdefined_transform/0 delete table db_test_transform create table db_test_transform begin to insert test data 1000000 finish insert test data userdefined_transform begin time {{2014,3,31},{20,19,22}} userdefined_transform end time {{2014,3,31},{20,19,22}} 测试mnesia:transform_table => mnesia_userdefined_transform:test_mnesia_transform/0 delete table db_test_transform create table db_test_transform begin to insert test data 1000000 finish insert test data mnesia_transform begin time {{2014,3,31},{20,19,33}} mnesia_transform end time {{2014,3,31},{20,19,41}}