Erlang Thursday - ETS的查询、分页和并发的数据插入

上星期的Erlang Thursday的结尾,我说我们将继续研究ets模块的select函数的特性。

所以在开始我们的任何试验前,我们设置好我们的测试ETS表,这次我们将创建一个 ordered_set 类型的表。

1
2
3
4
5
6
7
8
9
10
11
12
Fun = fun() -> receive after infinity -> ok end end.
% #Fun<erl_eval.20.54118792>
SomeProcess = spawn(Fun).
% <0.52.0>
TestOrderedSetTable = ets:new(ordered_set_table, [public, ordered_set]).
% 16402
TestTable = ets:new(ets_table, [public]).
% 20499
ets:give_away(TestTable, SomeProcess, []).
% true
ets:give_away(TestOrderedSetTable, SomeProcess, []).
% true

接着我们给我们的测试ETS表装载一些测试数据,在数据的序列中留出一些空隙,为了后面我们填补这些空隙。

1
2
3
4
5
6
[[ets:insert(TestTable, {X, X}) || X <- lists:seq(1, 30, 2)]].
% [[true,true,true,true,true,true,true,true,true,true,true,
% true,true,true,true]]
[[ets:insert(TestOrderedSetTable, {X, X}) || X <- lists:seq(1, 30, 2)]].
% [[true,true,true,true,true,true,true,true,true,true,true,
% true,true,true,true]]

然后我们从表里查询所有数据,我们可以看到不同类型的表的数据排序是怎么样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ets:select(TestTable, [{{'$1', '$2'}, [], [{{'$1', '$2'}}]}]).
% [{15,15},
% {25,25},
% {13,13},
% {21,21},
% {11,11},
% {1,1},
% {23,23},
% {7,7},
% {3,3},
% {9,9},
% {19,19},
% {29,29},
% {27,27},
% {17,17},
% {5,5}]
ets:select(TestOrderedSetTable, [{{'$1', '$2'}, [], [{{'$1', '$2'}}]}]).
% [{1,1},
% {3,3},
% {5,5},
% {7,7},
% {9,9},
% {11,11},
% {13,13},
% {15,15},
% {17,17},
% {19,19},
% {21,21},
% {23,23},
% {25,25},
% {27,27},
% {29,29}]

ets模块也有一个函数 ets:select_reverse ,我们停下来看一看它能做些什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ets:select_reverse(TestTable, [{{'$1', '$2'}, [], [{{'$1', '$2'}}]}]).
% [{15,15},
% {25,25},
% {13,13},
% {21,21},
% {11,11},
% {1,1},
% {23,23},
% {7,7},
% {3,3},
% {9,9},
% {19,19},
% {29,29},
% {27,27},
% {17,17},
% {5,5}]
ets:select_reverse(TestOrderedSetTable, [{{'$1', '$2'}, [], [{{'$1', '$2'}}]}]).
% [{29,29},
% {27,27},
% {25,25},
% {23,23},
% {21,21},
% {19,19},
% {17,17},
% {15,15},
% {13,13},
% {11,11},
% {9,9},
% {7,7},
% {5,5},
% {3,3},
% {1,1}]

我们比较 ets:select/2 和 ets:select_reverse/2 的结果,TestTable的结果是一样的,而TestOrderedSetTable的结果刚好是反序,这个结果和官方文档对 ets:select_reverse/2 描述一样。其实我们思考一下也就明白了。

结束了这个简单的小插曲,我们接着运行我们上面相同的匹配规则只是结果集限制在5个纪录,同时我们得到一个continuation。

1
2
3
4
5
{Result, Continuation} = ets:select(TestTable, [{{'$1', '$2'}, [], [{{'$1', '$2'}}]}], 5).
% {[{19,19},{29,29},{27,27},{17,17},{5,5}],
% {20499,214,5,<<>>,[],0}}
{OrdSetResult, OrdSetContinuation} = ets:select(TestOrderedSetTable, [{{'$1', '$2'}, [], [{{'$1', '$2'}}]}], 5).
% {[{1,1},{3,3},{5,5},{7,7},{9,9}],{16402,9,[],5,<<>>,[],0,0}}

有了这些continuation,我们看看我们取回来的下一个结果集是怎么样的。

1
2
3
4
5
ets:select(Continuation).
% {[{1,1},{23,23},{7,7},{3,3},{9,9}],{20499,111,5,<<>>,[],0}}
ets:select(OrdSetContinuation).
% {[{11,11},{13,13},{15,15},{17,17},{19,19}],
% {16402,19,[],5,<<>>,[],0,0}}

还记得我们用来创建元组的数字序列里留的那些间隙吗?

现在来看看我们填上那些序列间隙,同时我们用已经获取的continuation来查询数据会发生什么。

1
2
3
4
5
6
[[ets:insert(TestOrderedSetTable, {X, X}) || X <- lists:seq(2, 30, 2)]].
% [[true,true,true,true,true,true,true,true,true,true,true,
% true,true,true,true]]
[[ets:insert(TestTable, {X, X}) || X <- lists:seq(2, 30, 2)]].
% [[true,true,true,true,true,true,true,true,true,true,true,
% true,true,true,true]]

现在我们用前面获取到的continuation重新运行 ets:select/1 函数。

1
2
3
4
5
6
ets:select(Continuation).
% {[{12,12},{7,7},{3,3},{10,10},{9,9}],
% {20499,224,5,<<>>,[],0}}
ets:select(OrdSetContinuation).
% {[{10,10},{11,11},{12,12},{13,13},{14,14}],
% {16402,14,[],5,<<>>,[],0,0}}

如果我们比较以前的结果,我们看到现在的结果里有了偶数元素在列表里。对于我们的 TestTable ,如果我们看上面的Continuation变量的值,它有一个continuation 的点,值是214,因为它是当前continuation和调用 ets:select(Countinuation) 后的结果continuation里唯一变化的值,所以通过这个值我们很难推断continuation的变化。

另一方面,OrdsetContinuation,它的第二个元素的值是9,也就是ETS表id 16402 后面的元素。而后面其它的continuation的这个值分别是19和14也碰巧和对应结果集的最后元素匹配上。因此在有序集合的情况下,我们能推断作为ordered_set类型的ETS表的continuation的一部分,这个continuation告诉我们返回结果集里最后纪录的键,我们可以不管任何同时插入动作发生而继续从这个纪录开始获取数据。

下次我们将看看 ets:is_compiled_ms/1 这个函数,并基于对官方文档的该函数的阅读,我们如何将匹配规则应用在continuation上。

原文链接: https://www.proctor-it.com/erlang-thursday-ets-selects-continuations-and-concurrent-inserts/