【问题标题】:How to optimize this SQL select query?如何优化这个 SQL 选择查询?
【发布时间】:2010-01-14 08:23:43
【问题描述】:

我能够完成此查询,但需要 25 秒。那太长了!如何优化此查询?

SELECT COUNT(DISTINCT u1.User_ID ) AS total
FROM UserClicks u1
INNER JOIN (SELECT DISTINCT User_ID 
              FROM UserClicks 
             WHERE (Date BETWEEN DATE_SUB(:startDate, INTERVAL 1 MONTH) AND :startDate)) u2
            ON u1.User_ID = u2.User_ID
WHERE (u1.Date BETWEEN :startDate AND :endDate)

这是在 MySQL 数据库上使用的

【问题讨论】:

  • 您的 UserClicks.User_ID 字段是否不唯一且已编入索引?这应该使您能够摆脱查询的两个 DISTINCT 部分。无论如何,我认为@Parrots 下面有你的答案。
  • @andrew:真的有你想做的吗,那些在开始日期前一个月和开始日期和结束日期之间点击的人? (见下面的 Quassnoi cmets)

标签: sql mysql


【解决方案1】:
SELECT  COUNT(*) AS total
FROM    (
        SELECT  DISTINCT User_ID 
        FROM    UserClicks 
        WHERE   Date BETWEEN DATE_SUB(:startDate, INTERVAL 1 MONTH) AND :startDate
        ) u1
WHERE   EXISTS
        (
        SELECT  NULL
        FROM    UserClicks u2
        WHERE   u2.User_ID = u1.User_ID
                AND u2.Date BETWEEN :startDate AND :endDate
        )

(User_ID, Date)上创建复合索引:

CREATE INDEX ix_userclicks_user_date ON UserClicks (User_ID, Date)

如果您的用户少但点击次数多,并且有一个表格Users,您可以使用Users 表格而不是DISTINCT

SELECT  COUNT(*)
FROM    Users u
WHERE   EXISTS
        (
        SELECT  NULL
        FROM    UserClicks uc1
        WHERE   uc1.UserId = u.Id
                AND uc1.Date BETWEEN DATE_SUB(:startDate, INTERVAL 1 MONTH) AND :startDate
        )
        AND EXISTS
        (
        SELECT  NULL
        FROM    UserClicks uc2
        WHERE   uc2.UserId = u.Id
                AND u2.Date BETWEEN :startDate AND :endDate
        )

【讨论】:

  • 创建复合索引后我要改变什么?
  • 还...复合索引是否需要唯一? (对不起,如果这是一个愚蠢的问题)
  • 复合索引有助于查询运行得更快(尤其是第二个查询)
  • 不,它不必是唯一的。但是,如果本质上是UNIQUE(即不能同时有一个用户的两次点击),则可以设为UNIQUE
  • 那么只要创建索引,查询就运行得更快了?
【解决方案2】:

您是否尝试将语句之外的 DATE_SUB(:startDate, INTERVAL 1 MONTH) 移动到变量中?你有 UserClicks.Date 的索引吗?

【讨论】:

    【解决方案3】:

    为什么不只使用一个 select 语句而不是运行一对嵌套的 select。现在,您实际上是在运行两个查询。试试这个:

    SELECT COUNT(DISTINCT UserClicks.User_ID) AS total
    FROM UserClicks
    WHERE (UserClicks.Date BETWEEN :startDate AND :endDate)
    AND (UserClicks.Date BETWEEN DATE_SUB(:startDate, INTERVAL 1 MONTH) AND :startDate)
    

    如果您在日期列上添加索引也可能会有所帮助:

    ALTER TABLE  `UserClicks` ADD INDEX (  `Date` );
    

    【讨论】:

    • 这不会返回原始查询返回的内容。
    • @Quassnoi 查询结果会有什么不同?我很难看出区别。嵌套的基本上是说“在开始日期和结束日期之间获取所有人”“现在从开始日期到+1个月之间获取所有人”。这与 just 和 AND 操作有什么不同?
    • asker 查询返回在start_date 之前的一个月内以及start_dateend_date 之间点击both 的用户计数。您的查询返回完全点击start_date的用户数。
    • @Parrots 换句话说,提问者的查询执行连接,而您的查询执行相交。这些是不同的操作。
    【解决方案4】:

    MySQL 在处理子查询时倾向于忽略索引,因此它必须处理每一行。改为自加入怎么样?这只是我的想法,所以它可能不太正确,但它至少应该为您指明正确的方向。

    SELECT COUNT(DISTINCT u1.User_ID) AS total
    FROM   UserClicks AS u1
    JOIN   UserClicks AS u2 USING (User_ID)
    WHERE  u1.Date BETWEEN :startDate AND :endDate
    AND    u2.Date BETWEEN DATE_SUB(:startDate, INTERVAL 1 MONTH) AND :startDate)
    

    【讨论】:

      猜你喜欢
      • 2013-05-27
      • 1970-01-01
      • 2015-01-05
      • 1970-01-01
      • 1970-01-01
      • 2018-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多