【发布时间】:2009-10-09 03:35:55
【问题描述】:
假设我有两个 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 的唯一值,其中:
- 第二个表中的 id_b 是一个给定值(将其缩小到大约 10k 个条目)
- id1/id2 组合在两个表中是相同的
- 第一个表中的 id_a 与 tab_small 子集中的 id1、id2 字段中的任何一个都不相同(由 id_b 字段缩小);经过一番摆弄之后,似乎在 php 中生成列表(大约 200 个 id)并将其作为文本提供比添加另一个 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(编辑:添加在下面)。我不知道为什么(解释说查询使用主键),但添加 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 |
+----+-------------+-----------+------+---------------------+---------+---------+------------------+-------+----------------------------------------------+
有什么想法吗?
【问题讨论】:
-
你能把NOT IN去掉,写成IN吗?这通常有助于解决性能问题。
-
不,不幸的是,我只知道我不想要的东西。 ://
-
能把表结构贴在SQL里吗?
-
你能把EXPLAIN的输出也贴出来吗?
-
两者都添加了;请注意,报告的行数可能比生产中的要少一些(我目前只有一个小数据集)。
标签: sql mysql performance optimization