【问题标题】:MySQL Speeding up left outer join / check for null queriesMySQL加速左外连接/检查空查询
【发布时间】:2012-06-14 05:29:24
【问题描述】:

我的查询对象是从表 a 中获取所有行,其中性别 = f 并且用户名在表 b 中不存在,其中 campid = xxxx。这是我成功使用的查询:

SELECT `id` 
FROM pool 
  LEFT JOIN sent 
    ON  pool.username = sent.username 
    AND sent.campid = 'YA1LGfh9' 
WHERE sent.username IS NULL 
  AND pool.gender = 'f'

问题是查询需要超过 9 分钟才能完成,池表包含超过 1000 万行,并且发送的表最终会变得更大。我为许多列创建了索引,包括用户名和性别。但是,MySQL 拒绝为此查询使用我的任何索引。我什至尝试使用 FORCE INDEX。这是我的池中的索引和我的查询的 EXPLAIN 输出:

+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| pool  |          0 | PRIMARY  |            1 | id          | A         |     9326880 |     NULL | NULL   |      | BTREE      |         |
| pool  |          1 | username |            1 | username    | A         |     9326880 |     NULL | NULL   |      | BTREE      |         |
| pool  |          1 | source   |            1 | source      | A         |           6 |     NULL | NULL   |      | BTREE      |         |
| pool  |          1 | gender   |            1 | gender      | A         |           9 |     NULL | NULL   |      | BTREE      |         |
| pool  |          1 | location |            1 | location    | A         |       59030 |     NULL | NULL   |      | BTREE      |         |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
6 rows in set (0.00 sec)

mysql> explain SELECT `id` FROM pool FORCE INDEX (username) LEFT JOIN sent ON pool.username = sent.username AND sent.campid = 'YA1LGfh9' WHERE sent.username IS NULL AND pool.gender = 'f';
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows    | Extra                   |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------------------+
|  1 | SIMPLE      | pool  | ALL  | NULL          | NULL | NULL    | NULL | 9326881 | Using where             |
|  1 | SIMPLE      | sent  | ALL  | NULL          | NULL | NULL    | NULL |     351 | Using where; Not exists |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------------------+
2 rows in set (0.00 sec)

另外,这里是我发送表的索引:

+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| sent  |          0 | PRIMARY  |            1 | primary_key | A         |         351 |     NULL | NULL   |      | BTREE      |         |
| sent  |          1 | username |            1 | username    | A         |         351 |     NULL | NULL   |      | BTREE      |         |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
2 rows in set (0.00 sec)

您可以看到没有没有使用索引,因此我的查询花费的时间非常长。如果有人有涉及重新处理查询的解决方案,请向我展示如何使用我的数据结构执行此操作的示例,这样我就不会对如何实现和测试有任何困惑。谢谢。

【问题讨论】:

    标签: mysql performance join null indexing


    【解决方案1】:

    首先,您最初的查询在您放置所有内容时都是正确的……包括营地。通过使用从 Pool 到 Sent 的 LEFT JOIN,然后如前所述将所需的相等性(例如“CAMP”)拉入 WHERE 子句,最终将其转换为 INNER JOIN,因此需要双方都输入。保持原样。

    您已经在已发送表上建立了用户名索引,但我会执行以下操作。

    在 (CampID, UserName) 上的“已发送”表上建立一个索引作为复合(即:多键)索引。这样,左连接将针对两个条目进行优化。

    在您的“池”表上,尝试对(性别、用户名、id)的 3 个字段进行复合索引。

    通过这样做,您可以利用不必浏览包含 10+ 百万条记录的所有实际数据页面的优势。由于索引有比较列,所以不必查找实际记录并查看列,可以直接使用索引的列。

    另外,为了微笑,我添加了关键字“STRAIGHT_JOIN”,它告诉 MySQL 完全按照我显示的方式进行查询,不要试图替我思考。很多时候,我发现这可以显着提高查询性能......在极少数情况下,我收到反馈说它没有帮助。

    SELECT STRAIGHT_JOIN
          p.id
       FROM 
          pool p
             LEFT JOIN sent s
                ON s.campid = 'YA1LGfh9' 
                AND p.username = s.username 
       WHERE 
              p.gender = 'f'
          AND s.username IS NULL 
    

    话虽如此,您仍将返回 10+ 百万中的多少条记录...如果池中有 10+ 百万,而单个阵营只有 5,000。你仍然会返回几乎整个系列。

    【讨论】:

    • 我更喜欢(gender, username, id)
    • @ypercube,好点...通过将用户名保持在第二位将防止该索引弹跳到发送的表,这也将是正确的顺序。我会改的。
    • 好的。我已经按照您的规格设置了所有内容(我认为),但我仍然遇到性能问题。事实上,它现在比我使用不使用索引的初始查询花费的时间更长。这是我所做的:pastebin.com/BhyPPVqa 查询花了将近 13 分钟才能完成。也许我做错了什么?
    • @xendi,这可能是“STRAIGHT_JOIN”的一个例外,请尝试删除该子句,否则应该可以工作。在 10+ 百万条记录中,您预计有多少条记录。
    • 我删除了它,但结果相同。我预计超过 600 万。
    猜你喜欢
    • 2013-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多