在MySQL排名中,如何通过频繁更新和大量数据集获得最佳性能?
我希望在一个非常大的表上进行分组排名,我已经找到了解决这个问题的两个方法,例如在网络上和其他地方。然而,我无法找出这些解决方案的最坏情况的复杂性。具体问题由一个表组成,其中每一行都有若干点和一个关联的名称。我希望能够请求排名间隔,如1-4。以下是一些数据示例:在MySQL排名中,如何通过频繁更新和大量数据集获得最佳性能?,sql,mysql,rdbms,Sql,Mysql,Rdbms,我希望在一个非常大的表上进行分组排名,我已经找到了解决这个问题的两个方法,例如在网络上和其他地方。然而,我无法找出这些解决方案的最坏情况的复杂性。具体问题由一个表组成,其中每一行都有若干点和一个关联的名称。我希望能够请求排名间隔,如1-4。以下是一些数据示例: name | points Ab 14 Ac 14 B 16 C 16 Da 15 De 13 使用这些值创建以下“排名”: Query id | Rank | Name 1
name | points
Ab 14
Ac 14
B 16
C 16
Da 15
De 13
使用这些值创建以下“排名”:
Query id | Rank | Name
1 1 B
2 1 C
3 3 Da
4 4 Ab
5 4 Ac
6 6 De
应该可以在查询id上创建以下间隔:2-5,给出排名:1、3、4和4
数据库保存了大约300万条记录,因此如果可能的话,我希望避免使用复杂度大于log(n)的解决方案。数据库上不断有更新和插入,因此这些操作最好也以日志(n)复杂度执行。但我不确定这是否可能,我已经试着用我的头来绕它一段时间了。我已经得出结论,二进制搜索应该是可能的,但我还不能创建一个这样做的查询。我使用的是MySQL服务器
我将详细说明过滤的伪代码是如何工作的。首先,需要(点、名称)的索引。作为输入,您给出一个fromrank和一个tillrank。数据库中的记录总数为n。伪代码应该如下所示:
找到中间点值,计算小于该值的行数(计数给出了排名的粗略估计,不考虑具有相同点数的行数)。如果返回的数字大于fromrank分隔符,我们将前半部分细分,并找到它的中间值。我们一直这样做,直到我们被精确定位到fromrank应该从多少点开始。然后,我们使用名称索引在该数量的点内执行相同的操作,并找到中间值,直到到达正确的行。我们对tillrank做了完全相同的事情
结果应为对数(n)个细分。因此,如果中位数和计数可以在log(n)时间内完成,那么应该可以在最坏情况下解决复杂性log(n)的问题。如果我错了,请更正。您需要一个存储过程才能使用参数调用此函数:
CREATE TABLE rank (name VARCHAR(20) NOT NULL, points INTEGER NOT NULL);
CREATE INDEX ix_rank_points ON rank(points, name);
CREATE PROCEDURE prc_ranks(fromrank INT, tillrank INT)
BEGIN
SET @fromrank = fromrank;
SET @tillrank = tillrank;
PREPARE STMT FROM
'
SELECT rn, rank, name, points
FROM (
SELECT CASE WHEN @cp = points THEN @rank ELSE @rank := @rn + 1 END AS rank,
@rn := @rn + 1 AS rn,
@cp := points,
r.*
FROM (
SELECT @cp := -1, @rn := 0, @rank = 1
) var,
(
SELECT *
FROM rank
FORCE INDEX (ix_rank_points)
ORDER BY
points DESC, name DESC
LIMIT ?
) r
) o
WHERE rn >= ?
';
EXECUTE STMT USING @tillrank, @fromrank;
END;
CALL prc_ranks (2, 5);
如果您创建索引并强制使用MySQL
(如在我的查询中),那么查询的复杂性将根本不取决于行数,它将只取决于tillrank
它实际上会从索引中提取最后一个tillrank
值,对它们执行一些简单的计算,然后过滤掉第一个fromrank
值
如您所见,此操作的时间仅取决于tillrank
,而不取决于有多少条记录
我刚刚签入了400000
行,它在0004
秒内(即瞬间)从5
到100
选择列组
重要提示:仅当您按降序对名称排序时,此选项才有效MySQL
不支持索引中的DESC
子句,这意味着点
和名称
必须按一个顺序排序才能使用索引排序
(要么是升序
,要么是降序
)。如果要按name
进行快速ASC
排序,则需要在数据库中保留负值点,并更改选择
子句中的符号
您还可以完全从索引中删除名称
,并在不使用索引的情况下执行最后的命令
:
CREATE INDEX ix_rank_points ON rank(points);
CREATE PROCEDURE prc_ranks(fromrank INT, tillrank INT)
BEGIN
SET @fromrank = fromrank;
SET @tillrank = tillrank;
PREPARE STMT FROM
'
SELECT rn, rank, name, points
FROM (
SELECT CASE WHEN @cp = points THEN @rank ELSE @rank := @rn + 1 END AS rank,
@rn := @rn + 1 AS rn,
@cp := points,
r.*
FROM (
SELECT @cp := -1, @rn := 0, @rank = 1
) var,
(
SELECT *
FROM rank
FORCE INDEX (ix_rank_points)
ORDER BY
points DESC
LIMIT ?
) r
) o
WHERE rn >= ?
ORDER BY rank, name
';
EXECUTE STMT USING @tillrank, @fromrank;
END;
这将影响大范围的性能,但在小范围内您几乎不会注意到这一点。很高兴我的帖子派上了用场。嗯,你试过第二种方法吗?使用group_concat?如果我没有弄错的话,我认为该方法具有复杂度n,此外,我不知道如何合理地修改它以支持检索任何列组范围。不幸的是,计数本身是这里成本最高的操作,时间将取决于实际计数的行,而不是搜索的行,所以O(N)看起来还是很好的,这个查询的复杂度是多少,是否在log(N)范围内,如果是的话,你能解释一下原因吗。但缺少的一件事是,如果两行的点数相同,则将名称排序为第二优先级。我的排名所依赖的算法是,点数最多的一行的排名为1,如果一些人的点数相同,我希望他们按照自己的名称进行排序。因此,如果两个人的得分最高,那么得分第三高的那一行将被记录为排名3,而其他两个人的得分都为排名1感谢您的热烈响应!我做了一些测试,据我所知,只要你选择高数值中的小间隔,时间就会增加。我认为这是由于限制条款,你确定它可以优化吗。我认为限制是一个便利条款,并没有以任何方式优化。我在偏移量90000-90050处做了一个50记录请求,花了一秒钟时间,虽然计算机速度有点慢,但要花200毫秒才能获得排名1-50。