【问题标题】:How Could this Mysql Query be Improved?如何改进这个 Mysql 查询?
【发布时间】:2013-07-21 16:07:20
【问题描述】:

这个查询试图做一些 mysql 不容易做的事情,那就是限制每组的行数。 user_id's 的列表被传递给查询,并返回了一些,但组需要限制为每组 4 行。查询有效,但根据 Sequel Pro,200-500 毫秒的速度有点慢。

请在举报前继续阅读!!

SELECT id, user_id, article_id, row_number
FROM (
    SELECT a2.id, a2.user_id, a2.post_id,
        @num:= if(@group = a2.user_id, @num + 1, 1) as row_number
    FROM (
        SELECT a1.id, a1.user_id, a1.post_id
        FROM articles as a1
        WHERE a1.user_id IN (3,14,1,2,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,19,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,38,39,13,114,1111,12,223,2234,225,226,227,228,229,2210)
        ORDER BY a1.date DESC
    ) as a2, 
    (SELECT @num := 0) t
) as f
WHERE row_number <= 4;

这个查询的解释是:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   PRIMARY <derived2>  ALL         NULL    NULL    NULL    NULL    10516   Using where
2   DERIVED <derived4>  system      NULL    NULL    NULL    NULL    1   
2   DERIVED <derived3>  ALL         NULL    NULL    NULL    NULL    10516   
4   DERIVED NULL        NULL        NULL    NULL    NULL    NULL    NULL    No tables used
3   DERIVED s1          ALL         Reco... NULL    NULL    NULL    1180931 Using filesort

我曾考虑将其分解为多个查询,但我似乎仍然遇到将每个组结果限制为 4 个的问题。总而言之,我试图避免大量查询和昂贵的查询。

关于通过分解并将其中的一部分移动到应用程序中来提高此查询速度的最佳方法的任何想法?

【问题讨论】:

  • @MarcB 您立即对此进行了标记,但它不是重复的,我正在寻找通过 Baron Schwartz 提供的答案的替代解决方案。如果可能的话,我希望将繁重的工作从查询中转移到应用程序中。
  • 你想按什么分组?您是否尝试获取每个用户最近 4 篇文章的列表?
  • @noz:查询有点奇怪,它非常奇怪。
  • 问题中的查询不起作用(article_id 未定义)。解释与另一个查询有关(s1 来自哪里?)。

标签: php mysql optimization query-optimization greatest-n-per-group


【解决方案1】:

要回答您的问题,我看不出有任何有效的方法可以“分解”此查询。您仍然需要确定来自那个 user_id (@group) 的文章是否按日期连续,没有来自其他 user_id 之一的干预帖子。将所有行按日期排序在一起将是最好的方法。

如果要消除的行数是行的很大一部分,那么在客户端过滤这些行将需要向客户端发送更大的结果集。但如果只是一小部分行被过滤掉,那么将所有行(对于列表中的所有用户)传输到客户端进行处理更具吸引力。

SELECT a.id
     , a.user_id
     , a.post_id
  FROM articles a
 WHERE a.user_id IN (3,14,1,2,3,4,5,6,7,8,9,10,11,12,...)
 ORDER BY a.date DESC

然后客户端可以获取行,检查单个 user_id (@group) 的连续行序列,并忽略第五、第六等行,直到找到具有不同 user_id 的行。

如果结果集的规格不同,则可以将其拆分。但是现在编写查询的方式,任何“分解”查询的结果集都需要组合,以便获得当前查询当前返回的相同结果集。


(此查询与 Marc B 标记为可能重复的问题中的查询显着不同。)

这是一个奇怪的结果集;我们在任何地方都看不到 @group 在语句中被赋值,所以大概是在执行该语句之前设置的。所以,表达式

@group = a2.user_id

测试user_id 是否等于一个常数。这意味着查询从articles 中识别由单个user_id 发布的行,并在该用户连续发布两篇(或更多)文章时递增row_number,而@987654326 中没有任何其他user_id 发布的干预文章@ 列表(按 DATE 列的顺序)。由另一个 user_id(在 IN 列表中)发布的文章,会将计数器重置为 1。

最终结果是,此查询返回 IN 列表中指定的所有用户的所有文章,除了单个 user_id(可能在列表中也可能不在列表中)。只要有五个或更多文章由该用户连续发布一个单一的常量 user_id,在 IN 列表中没有来自另一个 user_id 的干预文章...每当发生这种情况时,查询只保留来自该指定 user_id 的前四(最新四)行连续文章。

如果date 列是DATE 数据类型,没有时间组件,那么您很可能会有多行具有相同的日期。并且在date 列之外没有指定排序,因此结果集是不确定的。 (也就是说,同一组行的多个序列可以满足 ORDER BY。)它也与 DATETIME 不确定,但如果这些值中的大多数都包含唯一的时间分量(即不是常量,例如午夜),那么这不太可能是一个问题。

奇怪的是,同一组行可以以两种方式排序,并给出不同的结果。假设@group 标识用户“abc”:

Date       user   id        Date       user   id
---------- ------ --        ---------- ------ --
2103-07-22 abc     1        2103-07-22 abc     1
2103-07-22 abc     2        2103-07-22 abc     2
2103-07-22 abc     3        2103-07-22 abc     3
2103-07-22 EFGHI   4        2103-07-22 abc     5
2103-07-22 abc     5        2103-07-22 abc     6
2103-07-22 abc     6        2103-07-22 abc     7
2103-07-22 abc     7        2103-07-22 EFGHI   4

7 rows selected.            5 rows selected.

两个结果集都符合规范,所以都可以返回。

返回这样的结果集并没有错。就是有点奇怪。


就性能而言,前导列为 (user_id) 的索引可能适合 WHERE 子句中的谓词,前提是要消除大部分行。

或者,具有(date,user_id) 前导列的索引可能更合适,因为 MySQL 可以避免“使用文件排序”操作,并按日期降序检索行,然后过滤掉带有谓词的行user_id 作为行被访问。

实际上,(date, user_id, post_id, id) 列上的覆盖索引可能更有益。

【讨论】:

    【解决方案2】:

    这里有一点假设 - 如果您尝试为给定用户列表中的每个用户列出最新的 4 篇文章,我认为您的查询可能会更好:

    SET @gr=0, @row=0;
    SELECT 
        id,user_id,post_id,row_number
    FROM
        (SELECT 
            id,
                user_id,
                post_id,
                @row:=if(user_id <> @gr, 0, @row + 1) as row_number,
                @gr:=user_id
        FROM
            articles
        WHERE
            user_id IN (3 , 14, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 38, 39, 13, 114, 1111, 12, 223, 2234, 225, 226, 227, 228, 229, 2210)
        ORDER BY user_id , date DESC) as a1
    WHERE
        row_number < 4
    

    【讨论】:

    • 注意:此查询的结果集与 OP 问题中查询的结果集明显不同。
    • 这很重要 - 因此一开始就是“假设的一点”。
    【解决方案3】:

    也许可以避免使用变量。

    加入表格对自己,加入用户ID和日期,找到所有那些日期大于或相同的文章。然后得到你真正想要的字段分组的匹配文章数,超过4个的就丢弃。

    没有经过类似的测试。

    SELECT a1.id, a1.user_id, a1.post_id, COUNT(a1_plus.id) AS other_count
    FROM articles as a1
    INNER JOIN articles a1_plus
    ON a1.user_id = a1_plus.user_id
    AND a1.date <= a1_plus.date
    WHERE a1.user_id IN (3,14,1,2,3,4,5,6,7,8,9,10,11,12,14,15,16,17,18,19,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,38,39,13,114,1111,12,223,2234,225,226,227,228,229,2210)
    GROUP BY a1.id, a1.user_id, a1.post_id
    HAVING other_count <= 4
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-02
      • 1970-01-01
      • 1970-01-01
      • 2013-08-08
      • 1970-01-01
      相关资源
      最近更新 更多