在次线性时间从MySQL表中选择加权随机项
我见过提倡使用类似于在次线性时间从MySQL表中选择加权随机项,mysql,optimization,random,Mysql,Optimization,Random,我见过提倡使用类似于SELECT*fromtblorderby-LOG(RAND())/weights LIMIT 1的东西从表中选择一个随机条目。然而,这在我看来效率非常低,因为我们必须在排序之前遍历整个表并为每个表生成随机数。这似乎是朝着正确方向迈出的一步:假设预先计算了总的总和,现在我们有了一个简单的线性搜索 尽管如此,确实有可能做得更好,但脑海中浮现的方法似乎都需要粗略的操作 我们可以存储累积权重分布,生成一个介于0和最大权重之间的随机数,并使用权重上的索引以及之间的来查找帖子。然而,在
SELECT*fromtblorderby-LOG(RAND())/weights LIMIT 1的东西
从表中选择一个随机条目。然而,这在我看来效率非常低,因为我们必须在排序之前遍历整个表并为每个表生成随机数。这似乎是朝着正确方向迈出的一步:假设预先计算了总的总和,现在我们有了一个简单的线性搜索
尽管如此,确实有可能做得更好,但脑海中浮现的方法似乎都需要粗略的操作
权重
之间的随机数,并使用权重
上的索引以及之间的来查找帖子。然而,在中间删除或移动条目需要大量的工作来更新它之后的权重。
sqrt(n)
较小的表,并计算其中的权重之和。我们首先搜索这些范围,直到找到包含所选随机数的范围,然后对该表执行线性搜索。然而,为大型n
拥有如此多的表似乎是糟糕的数据库设计,理想情况下,我希望将其降到对数时间,而不是O(sqrt(n))
我已经为这个问题挣扎了一段时间,这是我想出的最好的办法。还有其他想法吗?让我试着大声思考
CREATE TABLE entries (
entry_id int(10) unsigned NOT NULL AUTO_INCREMENT,
weight float NOT NULL DEFAULT 0.,
data varchar(50),
PRIMARY KEY (entry_id) USING BTREE,
KEY weights (weight) USING BTREE
) ENGINE=InnoDB;
INSERT INTO entries (weight) VALUES (0.), (0.3), (0.1), (0.3), (0.0), (0.2), (0.1);
我们所能想到的最佳查询将有一个从rand()值到特定条目id的现成映射。在这种情况下,我们只需要通过主键找到一个条目。正如我所说,用于此类查询的表将占用一些空间,但假设我们已经准备好了。我们可能希望将映射保留在内存中,因此可以使用内存引擎,它使用散列索引作为主键(这也很好,因为我们需要将一个值映射到特定的值)
让我们看看我们的表格:
mysql> SELECT entry_id, weight FROM entries ORDER BY weight;
+----------+--------+
| entry_id | weight |
+----------+--------+
| 1 | 0 |
| 5 | 0 |
| 3 | 0.1 |
| 7 | 0.1 |
| 6 | 0.2 |
| 2 | 0.3 |
| 4 | 0.3 |
+----------+--------+
让我们创建另一个表并用值填充它:
CREATE table int2entry (
an_int int(10) unsigned NOT NULL AUTO_INCREMENT,
entry_id int(10) unsigned NOT NULL,
PRIMARY KEY (an_int)
) ENGINE=Memory;
TRUNCATE int2entry;
INSERT INTO int2entry (entry_id)
VALUES (3), (7), (6), (6), (2), (2), (2), (4), (4), (4);
其思想是,特定条目id的引用数量与权重成正比。仅使用SQL可能很难更新该表,并且在每次权重更改后都必须截断并更新它,但是,正如我所说的,当更新很少时,这仍然是一个选项。以下是获取条目\u id的查询,您可以将其加入到entries表中(您应该知道映射表中的行数):
另一种解决方案是使用累积权重并利用索引中的顺序
当我们选择数据时,数据以某种索引顺序被选择(对于主键顺序中的select*)。权重
索引是从权重到条目ID的有序映射。如果我们只选择权重和条目ID,则可以直接从权重
索引中获取值,也就是说,数据将按索引顺序读取。我们可以使用ORDERBY以相反的索引顺序强制迭代(最后存储的权重越大,但匹配的频率越高)。为什么它很重要?因为我们要在WHERE子句中添加一些骇人的魔法,并依赖于处理行的特定顺序:
SET @rand:= RAND(), @cum_weight:=0.;
SELECT entry_id, weight, @cum_weight, @rand
FROM entries
WHERE @rand < @cum_weight:=@cum_weight+weight
ORDER BY weight DESC
LIMIT 1;
+----------+--------+----------------------------------+--------------------+
| entry_id | weight | @cum_weight | @rand |
+----------+--------+----------------------------------+--------------------+
| 6 | 0.2 | 0.800000026822090100000000000000 | 0.6957228003961247 |
+----------+--------+----------------------------------+--------------------+
SET
SET @rand:= RAND(), @cum_weight:=0.;
SELECT entry_id, weight, @cum_weight, @rand
FROM entries
WHERE @rand < @cum_weight:=@cum_weight+weight
ORDER BY weight DESC
LIMIT 1;
+----------+--------+----------------------------------+--------------------+
| entry_id | weight | @cum_weight | @rand |
+----------+--------+----------------------------------+--------------------+
| 6 | 0.2 | 0.800000026822090100000000000000 | 0.6957228003961247 |
+----------+--------+----------------------------------+--------------------+
SELECT *
FROM entries
JOIN (
SELECT entry_id
FROM entries
JOIN (SELECT @rand:= RAND(), @cum_weight:=0.) as init
WHERE @rand < @cum_weight:=@cum_weight+weight
ORDER BY weight DESC
LIMIT 1) as rand_entry USING (entry_id);
TABLE intervals:
id int_start int_size
1 0 95
7 95 95
9 190 190
SELECT int_start + int_size AS total_interval
FROM intervals
WHERE int_start =
SELECT MAX(int_start)
FROM intervals
SELECT id from intervals
WHERE int_start =
(SELECT MAX(int_start)
FROM intervals
WHERE int_start <= :rand_n * :total_interval)
UPDATE intervals
SET int_start = int_start - :inst_size_for_deleted_row
WHERE int_start >= :int_start_for_deleted_row