2009-10-09 236 views
2

说我有两个MyISAM表:MySQL的:优化连接查询

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. 第一个表中的id_a与tab_small子集中的id1,id2字段中的任一个不相同(如由id_b字段缩小);经过一番小小的调整后,似乎在php中生成列表(大约200个ids)并将其作为文本提供比添加另一个JOIN更好)。

我认为这不是非常缓存,因为两个表都一直在改变(添加行)。

我当前的查询是非常简单的:

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中进行搜索,结果会比默认的结果差得多(编辑:用一个小数据集I现在需要进行测试;对于生产数据,它显然是相反的,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         | 
+----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+ 

有什么想法?

+0

你可以摆脱NOT IN并把它写成IN吗?这通常有助于解决性能问题。 – 2009-10-09 03:39:07

+0

不,不幸的是,我只知道我不想找的东西。 :/ – Mike 2009-10-09 03:41:20

+0

你可以在SQL中发布表结构吗? – wenbert 2009-10-09 04:02:37

回答

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行)
  • 过滤掉t他存在于子集中的值
  • 对于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最重要的部分。

+0

我不明白你为什么要按照你的建议定义索引。为了让连接处理索引,并不是所有在连接中使用的列都必须进行索引,并且与连接条件中的顺序相同? 我的感觉是,由于连接,声明很慢......不是因为子查询! – Thorsten 2009-10-09 12:22:56

+0

'JOIN'中使用的列在'ix_big_1_2_a'中编入索引。由于'JOIN',语句可能会(或可能不会)缓慢,但是我们不能确定它是真正的原因,直到我们知道'tab_big'中有多少行满足'JOIN'条件。 – Quassnoi 2009-10-09 12:30:18

+0

不错! 首先,ix_big_1_2_a与原始查询有很大区别。其次,您建议的查询效果更好。不幸的是,它丢失了原始查询中的ORDER BY部分(应该首先提供最合适的条目),但是我可能会在此作弊。 非常感谢!对此,我真的非常感激。 :) – Mike 2009-10-09 17:09:20

0

你试过tab_small LEFT JOIN tab_big?您也可以在字段创建索引tab_small.id_btab_big.id_a

+0

试过左加入以防万一,实际上工作更糟糕。我其实有一个tab_small id_b索引;然而,添加tab_big.id_a索引并没有帮助。 – Mike 2009-10-09 04:22:59

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可能单独的索引另一个指标。)

+0

试图添加id1,id2索引,没有帮助(解释仍然说它使用PRIMARY)。 这里的<>子句不会排除那些id1,id2和id \ _a在这个特定条目中相同的条目吗?我需要排除在特定ID \ _b的ts记录中出现的_all_ id(id1或id2)。 – Mike 2009-10-09 12:07:00

+0

好的,那么通过rexem的EXISTS会是正确的(或Quassnoi的声明)。为了清晰起见,我会在帖子中留下建议。 – Thorsten 2009-10-09 12:17:13