3

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

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)

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

4

1 回答 1

4

首先,您最初的查询在您放置所有内容时都是正确的……包括营地。通过使用从 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。你仍然会返回几乎整个集合。

于 2012-06-11T11:11:01.987 回答