问题包括对多个子查询的高效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的单独索引
我正在运行查询:
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秒。
非常感谢任何帮助。
答案 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)分别通过OR
和phoneNumberHash
上的索引进行优化。该联合中的第三个查询需要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个索引。
users
的索引(uuid)
,(phoneNumberHash)
,(secondaryPhoneNumberHash)
和(tag_name, tag_value)
OR
试试这个:
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
子句)中,因此它在加入时执行,而不是在连接后执行,将提升性能,特别是如果有大量已删除的用户。