【问题标题】:Left Joins are what I want but they are very slow?左连接是我想要的,但它们很慢?
【发布时间】:2023-04-02 17:15:02
【问题描述】:

概览:

我有三个表 1) 订阅者、简历和衬衫尺码,我需要找到没有简历或衬衫尺码的订阅者

表格的布局如

订阅者

| season_id |  user_id |

简介

| bio_id | user_id |

衬衫尺码

| bio_id | shirtsize |

而且我需要找到所有给定季节没有个人简历或衬衫尺码的用户(如果没有个人简历;则通过关系没有衬衫尺码)。

我最初写了这样一个查询:

SELECT *
   FROM subscribers s 
   LEFT JOIN bio b ON b.user_id = subscribers.user_id 
   LEFT JOIN shirtsizes ON shirtsize.bio_id = bio.bio_id 
WHERE s.season_id = 185181 AND (bio.bio_id IS NULL OR shirtsize.size IS NULL);

但现在需要 10 秒才能完成。

我想知道如何重组查询(或可能是问题),以便它能够合理地执行。

这里是 mysql 解释:(ogu = 订阅者,b = bio,tn = shirtshize)

| id | select_type | table | type  | possible_keys | key     | key_len | ref         | rows   | Extra       |   
+----+-------------+-------+-------+---------------+---------+---------+-------------+--------+-------------+    
|  1 | SIMPLE      | ogu   | ref   | PRIMARY       | PRIMARY | 4       | const       |    133 | Using where |
|  1 | SIMPLE      | b     | index | NULL          | PRIMARY | 8       | NULL        | 187644 | Using index |
|  1 | SIMPLE      | tn    | ref   | nid           | nid     | 4       | waka2.b.nid |      1 | Using where | 

上面的内容很干净,这里是realz信息:

mysql> DESCRIBE subscribers
+-----------+---------+------+-----+---------+-------+
| Field     | Type    | Null | Key | Default | Extra |
+-----------+---------+------+-----+---------+-------+
| subscribers  | int(11) | NO   | PRI |         |       | 
| uid       | int(11) | NO   | PRI |         |       | 


mysql> DESCRIBE bio;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| bio_id   | int(10) unsigned | NO   | PRI | 0       |       | 
| uid   | int(10) unsigned | NO   | PRI | 0       |       | 


mysql> DESCRIBE shirtsize;
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| bio_id   | int(10) unsigned | NO   | PRI | 0       |       | 
| shirtsize   | int(10) unsigned | NO   | PRI | 0       |       | 

真正的查询看起来像:

SELECT ogu.nid, ogu.is_active, ogu.uid, b.nid AS bio_node, tn.nid AS size
                  FROM og_uid ogu
                  LEFT JOIN bio b ON b.uid = ogu.uid
                  LEFT JOIN term_node tn ON tn.nid = b.nid
                  WHERE ogu.nid = 185033 AND ogu.is_admin = 0
                  AND (b.nid IS NULL OR tn.tid IS NULL)

nid 是 season_id 或 bio_id(带有类型); term_node 将是衬衫尺寸

【问题讨论】:

  • 这些表有索引吗?
  • @jskulksi:我们可以要求您为每个表添加“SHOW CREATE TABLE”输出吗?

标签: sql optimization join


【解决方案1】:

查询应该没问题。我会通过查询分析器运行它并优化表上的索引。

【讨论】:

  • 我想这就是我相信贡献的模块具有正确索引的结果。谢谢!
【解决方案2】:

联接是您可以对 SQL 查询执行的最昂贵的操作之一。虽然它应该能够在某种程度上自动优化您的查询,但也许可以尝试对其进行重组。首先,我会代替 SELECT *,确保从哪些关系中指定您需要哪些列。这会加快速度。

如果您只需要用户 ID 例如:

SELECT s.user_id
   FROM subscribers s 
   LEFT JOIN bio b ON b.user_id = subscribers.user_id 
   LEFT JOIN shirtsizes ON shirtsize.bio_id = bio.bio_id 
WHERE s.season_id = 185181 AND (bio.bio_id IS NULL OR shirtsize.size IS NULL);

这将使 SQL 数据库能够更有效地自行重组您的查询。

【讨论】:

  • 常规连接的成本适中;外连接更昂贵;典型的相关子查询是恶魔般的。
  • 我发布了真正的查询并且只抓取了几列,但是谢谢。
【解决方案3】:

显然我还没有检查过这个,但您似乎想要选择没有匹配的简历或简历和衬衫尺寸之间的连接失败的任何订阅者。对于这种情况,我会考虑使用NOT EXISTS。您可能需要 bio.user_id 和 shirtsizes.bio_id 上的索引。

select *
from subscribers
where s.season_id = 185181
      and not exists (select *
                      from bio join shirtsizes on bio.bio_id = shirtsizes.bio_id
                      where bio.user_id = subscribers.user_id)

编辑

根据您的更新,您可能希望在每列上创建单独的键,而不是/除了具有复合主键。连接可能无法充分利用复合主索引,而连接列本身的索引可能会加快速度。

【讨论】:

  • 不少人对此进行了调查,结果发现在 EXISTS 查询中 SELECT * 通常比 SELECT 1 / SELECT NULL / etc 稍快。但是,是的,使用 EXISTS 通常比 join 更快,但并非总是如此,它是在每种情况下都可以尝试的武器……
【解决方案4】:

bio_id是bios的主键吗?真的有可能有b.user_id = subscribers.user_idb.bio_id NULL 的bios 行吗?

shirtsize.bio_id NULL 是否有衬衫尺寸行?这些行是否曾经有 shirtsize.size 不为 NULL?

【讨论】:

    【解决方案5】:

    在相关季节的订阅者列表和带有简历和衬衫尺码的季节的订阅者列表之间进行区分会更快吗?

    SELECT *
       FROM Subscribers
       WHERE season_id = 185181
         AND user_id NOT IN
             (SELECT DISTINCT s.user_id
                 FROM subscribers s
                 JOIN bios b ON s.user_id = b.user_id
                 JOIN shirtsizes z ON b.bio_id = z.bio_id
                 WHERE s.season_id = 185181
             )
    

    这避免了外连接,外连接不如内连接快,因此可能更快。另一方面,它可能会创建两个大列表,它们之间的差异很小。目前尚不清楚子查询中的 DISTINCT 是否会提高或损害性能。它意味着排序操作(昂贵),但如果 MySQL 优化器支持此类操作,则为合并连接铺平了道路。

    可能还有其他可用的符号 - 例如,减号或差异。

    【讨论】:

    • 没有理由在子查询中使用 DISTINCT。
    【解决方案6】:

    如果您准确定义您要查找的内容而不是 SELECT * 它可能会加快速度... OR 也不是最快的查询,如果您可以在没有 OR 的情况下重新编写它,它将是更快。

    另外...您可以尝试联合而不是左连接吗?

    选择 s.user_id FROM 订阅者 左连接 bio b ON b.user_id = s.user_id LEFT JOIN shirtsizes ON shirtsize.bio_id = bio.bio_id WHERE s.season_id = 185181 AND (bio.bio_id 为 NULL 或 shirtsize.size 为 NULL);

    会是这样的:

    (从订阅者 s 中选择 s.user_id,其中 s.season_id = 185181) 联盟 (SELECT b.user_id, b.bio_id FROM bio b WHERE bio.bio_id IS NULL) 联盟 (SELECT shirtsizes.bio_id FROM shirtsizes WHERE shirtsizes.size 为 NULL)

    (老实说,这对我来说看起来不正确...但是我从不使用 joins 或 join 语法或联合...)

    我愿意:

    选择 * FROM 订户 s, bio b, shirtsize sh WHERE s.season_id = 185181 AND shirtsize.bio_id = bio.bio_id AND b.user_id = s.user_id AND(bio.bio_id 为空 要么 shirtsize.size 为 NULL);

    【讨论】:

    • "我从不使用联接或联合" 当然你确实使用联接。 FROM 订阅者 s, bio b, shirtsizes sh WHERE s.season_id = 185181 AND shirtsize.bio_id = bio.bio_id AND b.user_id = s.user_id 是一组连接。它使用的是过时的连接样式,但它仍然是一个连接。
    • 我想写“我避免使用连接语法,我从不使用联合”会更正确
    【解决方案7】:

    您现在编写的查询会评估所有bioterm_node(如果存在),然后将它们过滤掉。

    但是你想要的只是找到没有term_nodeog_uid(没有bio 也意味着没有term_node

    因此,您只想在找到第一个现有 term_node 后立即停止评估 bioterm_node

    SELECT  *
    FROM    (
            SELECT  ogu.nid, ogu.is_active, ogu.uid,
                    (
                    SELECT  1
                    FROM    bio b, term_node tn
                    WHERE   b.uid = ogu.uid
                            AND tn.nid = b.nid
                    LIMIT   1
                    ) AS ex
            FROM    og_uid ogu
            WHERE   ogu.nid = 185033
                    AND ogu.is_admin = 0
            ) ogu1
    WHERE   ex IS NULL
    

    这将对每个og_uid 最多评估一个bio 和一个term_node,而不是评估所有现有的数千个并将它们过滤掉。

    应该工作得更快。

    【讨论】:

      【解决方案8】:
      select * from subscribers where user_id not in (
        select user_id from bio where bio_id not in (
          select bio_id from shirt_sizes
        )
      ) and season_id=185181
      

      【讨论】:

        【解决方案9】:

        我认为您的“大表”是订阅者,并且该 season_id 可能既不是选择性也不是索引(如果它不是选择性的,索引它是毫无意义的,无论如何),这意味着您必须全面扫描订阅者,无论如何.分开时,我将加入(使用内部连接)另外两个表 - 请注意,如果 shirt_size 中没有 bio_id,那么您的查询与没有 bio 完全相同。 第一点:

        select uid
        from bio
             inner join shirtsizes
                     on shirtsizes.bio_id = bio.bio_id
        

        此时您要检查衬衫尺寸是否已在 bio_id 上编入索引。 现在您可以将此查询左外部加入订阅者:

        select *
        from subscribers s
             left outer join (select uid
                              from bio
                              inner join shirtsizes
                                      on shirtsizes.bio_id = bio.bio_id) x
                          on x.uid = s.uid
        where s.season_id = 185181
          and x.uid is null
        

        如果生物和衬衫尺寸都不是巨大的,它可能会跑得相当快......

        【讨论】:

          猜你喜欢
          • 2015-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-02-22
          • 2014-04-19
          • 2021-10-27
          • 1970-01-01
          相关资源
          最近更新 更多