假设我有两个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的唯一值,其中:
我认为它不是可缓存的,因为两个表一直在变化(添加了行)。
我当前的查询非常简单:
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 |
+----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+
有什么想法吗?
答案 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_b
和tab_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上单独建立索引。)