在MySQL排名中,如何通过频繁更新和大量数据集获得最佳性能?

在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

我希望在一个非常大的表上进行分组排名,我已经找到了解决这个问题的两个方法,例如在网络上和其他地方。然而,我无法找出这些解决方案的最坏情况的复杂性。具体问题由一个表组成,其中每一行都有若干点和一个关联的名称。我希望能够请求排名间隔,如1-4。以下是一些数据示例:

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。