【问题标题】:MySQL is not using prmary indexMySQL 没有使用主索引
【发布时间】:2014-03-06 09:13:45
【问题描述】:

我有这个问题:

SELECT SQL_NO_CACHE
    COUNT(*) AS `numrows`
FROM
    (`citations`)
        LEFT JOIN
    `projects` ON `projects`.`project_id` = `citations`.`project_id`
        LEFT JOIN
    `users` ON `users`.`user_id` = `projects`.`user_id`
WHERE
    `users`.`role` = '0'
        AND `citations`.`created` BETWEEN 1360213200 AND 1360299599
        AND `citations`.`in_card` = '0'
        AND `citations`.`citation_id` NOT IN (SELECT 
            user_stats_citations.citation_id
        FROM
            user_stats_citations,
            user_stats FORCE INDEX (user_stats_type_index)
        WHERE
            user_stats_citations.user_stat_id = user_stats.id
                AND user_stats.type IN (69 , 70, 71, 75, 76));

我在用户表上有这些索引:

users            0  PRIMARY                             1  user_id      A                42836    (NULL)  (NULL)          BTREE                               
users            1  users_industry_id_index             1  industry_id  A                  118    (NULL)  (NULL)  YES     BTREE                               
users            1  users_sponsor_index                 1  sponsor      A                   12    (NULL)  (NULL)  YES     BTREE

这是 EXPLAIN EXTENDED 的输出

  id    select_type table   type    possible_keys   key key_len ref rows    filtered    Extra

  1 PRIMARY users   ALL PRIMARY \N  \N  \N  42836   100.00  Using where
  1 PRIMARY projects    ref PRIMARY\,projects_user_id_index projects_user_id_index  4   citelighter.users.user_id   1   100.00  Using where; Using index
  1 PRIMARY citations   ref citations_project_id_index  citations_project_id_index  4   citelighter.projects.project_id 4   100.00  Using index condition; Using where
  2 SUBQUERY    user_stats  range   user_stats_type_index   user_stats_type_index   2   \N  410768  100.00  Using where; Using index
  2 SUBQUERY    user_stats_citations    ref user_stats_citations_index_user_stat_id\,user_stats_citations_index_citation_id user_stats_citations_index_user_stat_id 8   citelighter.user_stats.id   1   100.00  \N

我尝试在用户 LEFT JOIN 上添加 FORCE INDEX,但未使用该索引。你能帮我解决这个问题吗,因为这个查询在我的本地需要 10 秒,在生产环境需要 1 秒。

【问题讨论】:

  • FWIW,我喜欢用 \G 而不是 ; 来解释
  • 另外,"WHERE users.role = '0'" = INNER JOIN

标签: mysql sql optimization


【解决方案1】:

我注意到的第一件事是 where 子句中的这个谓词:WHERE users.role = '0' 将您的 LEFT JOINs 转换为 INNER JOINs,因此您不妨将它们设为内连接。

其次,MySQL 在优化相关子查询方面存在问题,并且在派生表方面也表现不佳。例如在this simple query:

SELECT *
FROM (SELECT * FROM T) T
JOIN (SELECT * FROM T) T2 ON T.ID = T2.ID;

即使 ID 是 T 上的主键,主键也不用于连接,因为它不能级联到派生表之外。有时写的时候也是这样:

SELECT *
FROM T
WHERE Afield NOT IN (SELECT Afield FROM T WHERE AnotherField = 1);

MySQL 不一定实现子查询并使用它,它通常会将查询重写为:

SELECT *
FROM T
WHERE NOT EXISTS (SELECT 1 
                    FROM T T2 
                    WHERE T.Afield = T2.Afield  
                    AND T2.AnotherField = 1);

并且子查询是针对外部查询中的每一行执行的,因此如果外部查询中有大量行,为每一行执行子查询会变得非常昂贵。解决方案是尽可能避免子查询。在您的情况下,您可以将查询重写为:

SELECT  SQL_NO_CACHE
        COUNT(*) AS `numrows`
FROM    `citations`
        INNER JOIN `projects` 
            ON `projects`.`project_id` = `citations`.`project_id`
        INNER JOIN `users` 
            ON `users`.`user_id` = `projects`.`user_id`
        LEFT JOIN (user_stats_citations
            INNER JOIN user_stats
                ON user_stats_citations.user_stat_id = user_stats.id
                AND user_stats.type IN (69 , 70, 71, 75, 76))
            ON user_stats_citations.citation_id = `citations`.`citation_id`
WHERE   `users`.`role` = '0'
AND     `citations`.`created` BETWEEN 1360213200 AND 1360299599
AND     `citations`.`in_card` = '0'
AND      user_stats_citations.citation_id IS NULL;

没有子查询就没有派生表,也没有子查询的逐行执行。这应该会缩短执行时间。

【讨论】:

    【解决方案2】:

    这给了你什么?

    SELECT COUNT(*) numrows
      FROM citations c
      JOIN projects p
        ON p.project_id = c.project_id
      JOIN users u
        ON u.user_id = p.user_id
    
      LEFT
      JOIN 
         ( SELECT uc.citation_id
             FROM user_stats_citations uc
             JOIN user_stats us
               ON uc.user_stat_id = us.id
              AND us.type IN (69,70,71,75,76)
         ) x
        ON x.citation_id = c.citation_id
     WHERE u.role = 0
       AND c.created BETWEEN 1360213200 AND 1360299599
       AND c.in_card = 0
       AND x.citation_id IS NULL
    

    【讨论】:

    • 此解决方案在生产环境中大约需要 2 秒。它还返回 0 numrows(正确的结果是 209)
    • 我用COUNT(*) 代替COUNT(1) 它会加快速度,因为它不必从表中检索字段
    • 是的,我发现别名是错误的。我将 * 替换为 1 但在生产环境中。我有 1.060 秒。所以没有改善。此外,我仍然认为用户表没有使用 PRIMARY 索引。
    • user_stats 是否在 (id,type) 上有复合索引?
    • 两列都没有。那里有来自 user_stats PRIMARY 的索引
    猜你喜欢
    • 2011-06-19
    • 1970-01-01
    • 1970-01-01
    • 2013-04-07
    • 2010-12-07
    • 2020-09-29
    • 2018-09-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多