MySQL加速左外连接/检查空查询

时间:2012-06-11 09:57:32

标签: mysql performance join null indexing

我的查询的对象是从表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)

您可以看到没有使用任何索引,因此我的查询花费的时间太长。如果有人有一个涉及重新处理查询的解决方案,请给我一个如何使用我的数据结构来做这个的例子,这样我就不会对如何实现和测试有任何困惑。谢谢。

1 个答案:

答案 0 :(得分:4)

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

您已发送的表上已有用户名索引,但我会执行以下操作。

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

在“池”表中,尝试3个字段(性别,用户名,ID)的综合索引。

通过这样做,您可以利用NOT不必浏览包含1000多万条记录的所有实际数据页面。由于索引是用于比较的列,因此它不必查找实际记录并查看列,它可以直接使用索引。

另外,对于笑话,我添加了关键字“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万多人中的记录......如果游泳池有1000多万,而单一营地只有5,000。你几乎可以回到整个场景。