来自具有OR和ORDER BY / LIMIT的多个表的高效多个子查询

时间:2017-06-20 21:34:07

标签: mysql sql join subquery query-performance

问题包括对多个子查询的高效SQL查询的疑虑:

我有3张桌子。我希望从表1中获取详细信息,基于表2和表3中的过滤。目前我在表2和表3中使用IN子句,但对于2M用户则需要大约6秒。我也试过加入,但它比子查询慢。

表1:

的MySQL>描述用户;

  Field                | Type             | Null | Key | Default   
| uuid                 | varchar(36)      | NO   | PRI | NULL  
| firstname            | varchar(512)     | YES  |     | NULL 
| status               | varchar(512)     | YES  |     | NULL 
| createdAt            | timestamp        | YES  |     | CURRENT_TIMESTAMP 

表2:

描述家园;

| Field                    | Type             | Null | Key | Default           | Extra
| uuid                     | varchar(50)      | NO   | PRI | NULL 
| phoneNumberHash          | varchar(512)     | YES  | MUL | NULL 
| secondaryPhoneNumberHash | varchar(512)     | YES  | MUL | NULL  

表3:

描述utility_tags:

| Field      | Type        | Null | Key | Default | 
| tag_name   | varchar(50) | NO   | MUL | NULL    |
| tag_value  | varchar(50) | NO   | MUL | NULL    | 
| user_id    | varchar(50) | NO   | MUL | NULL    | 

我有所有必填字段的索引,即。

  • 用户表:uuid索引

  • 主页表:phoneNumberHash和secondaryPhoneNumberHash的单独索引

  • Utility_Tags:tag_name和tag_value
  • 上的单独索引

我正在运行查询:

SELECT uuid, firstname 
FROM users 
WHERE ( uuid in (
   SELECT `uuid` 
   FROM `homes` 
   WHERE ( ( `phoneNumberHash` = '02c' OR `secondaryPhoneNumberHash` = '02c' ))
 ) 
 OR uuid in (
   SELECT `user_id` 
   FROM `utility_tags`  
   WHERE  ( `tag_name` = 'ACCOUNT_NUMBER' AND `tag_value`= '13' )
 )) 
 AND `status` != 'DELETED' 
 ORDER BY `createdAt` DESC LIMIT 10 OFFSET 0;

当用户和家庭表中有2M行时,查询很慢并且大约需要6秒。

我尝试了加入查询:

SELECT users.uuid, firstname 
FROM users inner join homes  on homes.uuid=users.uuid 
inner join utility_tags on utility_tags.user_id=users.uuid 
WHERE  ( phoneNumberHash = '02c' OR secondaryPhoneNumberHash = '02cd0' ) 
   OR  ( tag_name = 'ACCOUNT_NUMBER' AND tag_value= '1311851988' ) 
AND `status` != 'DELETED' 
ORDER BY `createdAt` DESC
LIMIT 10 OFFSET 0;

这大约需要30秒。

非常感谢任何帮助。

2 个答案:

答案 0 :(得分:1)

您正在根据其他表格中的匹配从byte[] bytes = body.getBytes("UTF-8");表中选择某些行。您正在使用复杂的users子句。

让我们看看该子句的内容以获得优化的可能性。这是生成一组IN( ... )值的一种方法。

uuid

这是另一个

SELECT uuid 
  FROM homes 
 WHERE phoneNumberHash = '02c' 
    OR secondaryPhoneNumberHash = '02c'

让我们将所有这些重写为 SELECT user_id FROM utility_tags WHERE tag_name = 'ACCOUNT_NUMBER' AND tag_value= '13' 几组UNION值,就像这样。

uuid

三个查询的联合与所有 SELECT uuid FROM homes WHERE phoneNumberHash = '02c' UNION SELECT uuid FROM homes WHERE secondaryPhoneNumberHash = '02c' UNION SELECT user_id AS uuid FROM utility_tags WHERE tag_name = 'ACCOUNT_NUMBER' AND tag_value= '13' 子句的作用相同。前两个查询应该(如果您使用的是InnoDB)分别通过ORphoneNumberHash上的索引进行优化。该联合中的第三个查询需要secondaryPhoneNumberHash上的复合索引才能有效执行。

关于(tag_name, tag_value, user_id)的一个很酷的事情是它与UNION执行相同类型的集创建,但允许您在OR内编写更有可能使用索引的查询。我建议您尝试使用此UNION查询和相应的索引,直到您对其性能感到满意为止。然后,您可以在外部查询中使用它。

(查询规划器可能已经变得足够聪明,可以单独处理UNION作为UNION,一个接一个地利用你的两个索引。最近的MySQL版本在查询规划方面取得了很大进展。)< / p>

这样我们就得到了外部查询:

phoneNumberHash = '02c' OR secondaryPhoneNumberHash = '02c'

这很难sargable。查询规划器不喜欢SELECT uuid, firstname FROM users WHERE matching uuids AND status != 'DELETED' ORDER BY createdAt DESC LIMIT 10 OFFSET 0 个运算符。它最喜欢!=,因为索引相等扫描很便宜。它喜欢=<<=>=,因为范围扫描几乎一样便宜。但你仍然坚持使用>

此外,查询计划程序讨厌 !=,因为它必须对整个行进行排序,只是为了丢弃除了一小部分之外的所有行。

以下覆盖索引的化合物可以优化此查询:ORDER BY ... LIMIT。如果查询规划器具有提供匹配条件和所需结果的索引,则查询规划器可能能够躲避单独的(createdAt, status, uuid, firstname)。这个指数也可能更好。 ORDER BY你需要同时尝试它们。不要把它们都放在一起,只保留最好的那个。

全部放在一起:

(createdAt, status, uuid, status, firstname)

当您想要亚秒级查询响应时,megarow表上的内容会变得很有趣。 http://use-the-index-luke.com/是这个东西的一个很好的参考。

答案 1 :(得分:0)

您的主要问题是您首先从users 中选择 - 将其移至最后,以便可以使用其索引(子查询无法编入索引)。

此外,SQL OR是臭名昭着的,主要是因为(几乎总是)最多可以使用1个索引。

  1. 从子查询中选择 first ,以便可以使用users的索引
  2. 确保所有已查找列都有索引,即(uuid)(phoneNumberHash)(secondaryPhoneNumberHash)(tag_name, tag_value)
  3. 分解您的查询以消除OR
  4. 试试这个:

    SELECT uuid, firstname 
    FROM (
        SELECT uuid
        FROM homes
        WHERE phoneNumberHash = '02c'
        UNION
        SELECT uuid
        FROM homes
        WHERE secondaryPhoneNumberHash = '02c'
        SELECT user_id 
        FROM utility_tags 
        WHERE tag_name = 'ACCOUNT_NUMBER'
        AND tag_value = 13
    ) x
    JOIN users ON users.uuid = x.uuid
       AND status != 'DELETED' 
    ORDER BY createdAt DESC
    LIMIT 10 OFFSET 0
    

    另请注意,status != 'DELETED'的测试位于 join 条件(不是WHERE子句)中,因此它在加入时执行,而不是在连接后执行,将提升性能,特别是如果有大量已删除的用户。