MySQL:优化JOIN查询

时间:2009-10-09 03:35:56

标签: sql mysql performance optimization

假设我有两个MyISAM表:

tab_big:   id1, id2, id_a, ord         (5 billion records)
tab_small: id1, id2, id_b              (1 billion records)


CREATE TABLE IF NOT EXISTS `tab_big` (
  `id_a` int(10) unsigned NOT NULL,
  `id1` int(10) unsigned NOT NULL,
  `id2` int(10) unsigned NOT NULL,
  `ord` int(10) unsigned NOT NULL DEFAULT '1',
  PRIMARY KEY (`id_a`,`id1`,`id2`),
  KEY `id1` (`id1`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;


CREATE TABLE IF NOT EXISTS `tab_small` (
  `id_b` int(10) unsigned NOT NULL,
  `id1` int(10) unsigned NOT NULL,
  `id2` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id_b`,`id1`,`id2`),
  KEY `id_b` (`id_b`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

所有字段均为INT。在两个表中,三个id字段(分别为id1,id2,id_a和id1,id2,id_b)值的组合是唯一的,因此我在这三个字段中创建了一个主键。

我需要一个有效的查询,从第一个表中获取id_a的唯一值,其中:

    第二个表中的
  1. id_b是给定值(将其缩小到大约10k个条目)
  2. id1 / id2组合在两个表中都相同
  3. 第一个表中的
  4. id_a与tab_small子集中的id1,id2字段不同(由id_b字段缩小);经过一些摆弄后,似乎在php中生成列表(大约200个ID)并将其作为文本提供比添加另一个JOIN更好。
  5. 我认为它不是可缓存的,因为两个表一直在变化(添加了行)。

    我当前的查询非常简单:

    SELECT tab_big.id_a FROM tab_big, tab_small
        WHERE tab_small.id_b = '$constant'
        AND tab_big.id1 = tab_small.id1 AND tab_big.id2 = tab_small.id2
        AND tab_big.id_a NOT IN ({comma delimited list of 200 ids})
        GROUP BY tab_big.id_a
        ORDER BY SUM(tab_big.ord) DESC
        LIMIT 10
    

    它有效但不够快,无法真正使用它。可以用它做什么?

    EXPLAIN说它首先从tab_big获取远程查询,然后将其应用于tab_small(编辑:在下面添加)。我不知道为什么(EXPLAIN说查询使用主键),但添加tab_big.id1索引似乎有点帮助。另外,尝试使用STRAIGHT_JOIN来反过来,首先从(较小的)tab_small中选择一个10k子集,然后使用它来搜索(更大)tab_big,结果比默认值更糟糕(编辑:使用我的小数据集)现在要测试;在生产数据上,它显然是另一种方式,EXPLAIN看起来像第二个。)

    +----+-------------+-----------+--------+-----------------+---------+---------+-------------------------------------------+---------+----------------------------------------------+
    | id | select_type | table     | type   | possible_keys   | key     | key_len | ref                                       | rows    | Extra                                        |
    +----+-------------+-----------+--------+-----------------+---------+---------+-------------------------------------------+---------+----------------------------------------------+
    |  1 | SIMPLE      | tab_big   | range  | PRIMARY,id1     | PRIMARY | 4       | NULL                                      | 1374793 | Using where; Using temporary; Using filesort | 
    |  1 | SIMPLE      | tab_small | eq_ref | PRIMARY,id_b    | PRIMARY | 12      | const,db.tab_big.id1,db.tab_big.id2       |       1 | Using index                                  | 
    +----+-------------+-----------+--------+-----------------+---------+---------+-------------------------------------------+---------+----------------------------------------------+
    

    在较大的数据集上,EXPLAIN可能看起来更像这样(但忽略'行'值 - 它取自较小的数据集):

    +----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+
    | id | select_type | table     | type | possible_keys       | key     | key_len | ref              | rows  | Extra                                        |
    +----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+
    |  1 | SIMPLE      | tab_small | ref  | PRIMARY,id_b,id1    | PRIMARY | 4       | const            |   259 | Using index; Using temporary; Using filesort | 
    |  1 | SIMPLE      | tab_big   | ref  | PRIMARY,id1         | id1     | 4       | db.tab_small.id1 | 25692 | Using where                                  | 
    +----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+
    

    有什么想法吗?

3 个答案:

答案 0 :(得分:3)

创建以下索引:

CREATE INDEX ix_big_1_2_a ON tab_big (id1, id2, id_a)
CREATE UNIQUE INDEX ux_small_b_2_1 ON tab_small (id_b, id2, id1)

试试这个:

SELECT  DISTINCT
        a.id_a
FROM    tab_small b
JOIN    tab_big a
ON      (a.id1, a.id2) = (b.id1, b.id2)
WHERE   b.id_b = 2
        AND a.id_a NOT IN
        (
        SELECT  id1
        FROM    tab_small b1 /* FORCE INDEX (PRIMARY) */
        WHERE   b1.id_b = 2
        )
        AND a.id_a NOT IN
        (
        SELECT  id2
        FROM    tab_small b2 /* FORCE INDEX (ux_small_b_2_1) */
        WHERE   b2.id_b = 2
        )

,它产生这个查询计划:

1, 'PRIMARY', 'b', 'ref', 'PRIMARY,ux_small_b_2_1', 'PRIMARY', '4', 'const', 1, 100.00, 'Using index; Using temporary'
1, 'PRIMARY', 'a', 'ref', 'ix_big_1_2', 'ix_big_1_2', '8', 'test.b.id1,test.b.id2', 2, 100.00, 'Using where'
3, 'DEPENDENT SUBQUERY', 'b2', 'ref', 'ux_small_b_2_1', 'ux_small_b_2_1', '8', 'const,func', 1, 100.00, 'Using index'
2, 'DEPENDENT SUBQUERY', 'b1', 'ref', 'PRIMARY', 'PRIMARY', '8', 'const,func', 1, 100.00, 'Using index'

它没有那么高效,但我仍然希望它比你的查询更快。

我注释掉了FORCE INDEX语句,但您可能需要取消注释,优化器不会选择这些索引。

如果MySQL能够FULL OUTER JOIN使用MERGE进行SELECT id_a FROM ( SELECT DISTINCT id_a FROM tab_big ad ) a WHERE id_a NOT IN ( SELECT id1 FROM tab_small b1 FORCE INDEX (PRIMARY) WHERE b1.id_b = 2 ) AND id_a NOT IN ( SELECT id2 FROM tab_small b2 FORCE INDEX (ux_small_b_2_1) WHERE b2.id_b = 2 ) AND EXISTS ( SELECT NULL FROM tab_small be JOIN tab_big ae ON (ae.id1, ae.id2) = (be.id1, be.id2) WHERE be.id_b = 2 AND ae.id_a = a.id_a ) ,那么一切都会简单得多,但事实并非如此。

<强>更新

根据您的统计数据,此查询将更有效:

DISTINCT id_a

它的工作原理如下:

  • 构建100,000列表(长id_a行)
  • 过滤出子集
  • 中的值
  • 对于(id_a, id1, id2)的每个值,它会在子集中搜索10的存在。这是通过迭代子集来完成的。由于找到此值的概率很高,因此搜索很可能会在子集开头的EXISTS行左右成功,而1,000,000将在那一刻返回。

这很可能只需评估约1, 'PRIMARY', '<derived2>', 'ALL', '', '', '', '', 8192, 100.00, 'Using where' 5, 'DEPENDENT SUBQUERY', 'be', 'ref', 'PRIMARY,ux_small_b_2_1', 'PRIMARY', '4', 'const', 1, 100.00, 'Using index' 5, 'DEPENDENT SUBQUERY', 'ae', 'eq_ref', 'PRIMARY,ix_big_1_2', 'PRIMARY', '12', 'a.id_a,test.be.id1,test.be.id2', 1, 100.00, 'Using index' 4, 'DEPENDENT SUBQUERY', 'b2', 'ref', 'ux_small_b_2_1', 'ux_small_b_2_1', '8', 'const,func', 1, 100.00, 'Using index' 3, 'DEPENDENT SUBQUERY', 'b1', 'ref', 'PRIMARY', 'PRIMARY', '8', 'const,func', 1, 100.00, 'Using index' 2, 'DERIVED', 'ad', 'range', '', 'PRIMARY', '4', '', 10, 100.00, 'Using index for group-by' 条记录。

确保使用以下计划:

Using index for group-by

,最重要的部分是最后一行{{1}}。

答案 1 :(得分:0)

你试过tab_small LEFT JOIN tab_big吗?您还可以在字段tab_small.id_btab_big.id_a

上创建索引

答案 2 :(得分:0)

我建议在属于连接的所有四列上放一个索引(tb.id1,tb.id2,ts.id1和ts.id2列上的四个独立索引,或者tb.id1上的两个索引) / id2和ts.id1 / id2)。然后看看是否能给你带来更好的表现。 (我认为确实如此,但你不会知道,除非尝试它。)


注意:以下想法不起作用,但我把它留在了所以评论仍然有意义。

此外,您不能使用PHP生成的列表,而是在连接条件中(或者如果您愿意,也可以在where子句中)表达您的限制(3)? (与rexem建议的相似)

SELECT tb.id_a
  FROM TAB_BIG tb
  JOIN TAB_SMALL ts ON ts.id1 = tb.id1
                 AND ts.id2 = tb.id2
                 AND tb.id1 <> ts.id_a
                 AND tb.id2 <> ts.id_a
 WHERE ts.id_b = ?

但这更多是为了清晰和简单而不是表现。 (另请注意,附加条件可能需要id_a上的另一个索引,并且可能需要在tb.id1和tb.id2上单独建立索引。)