我有以下人表:
| Id | FirstName | Children |
|----|-----------|----------|
| 1 | mark | 4 |
| 2 | paul | 0 |
| 3 | mike | 3 |
注意我在 FirstName 中有一个非唯一索引,在孩子中有另一个索引。
我需要获得前10000个名字以及每个有孩子的人的孩子数量。所以我决定采用这个解决方案:
SELECT firstName, children FROM people
WHERE children > 0
ORDER BY children DESC
LIMIT 0, 10000
事情是,从具有260万条记录的表中返回结果需要4秒。这是解释:
| ID | SELECT_TYPE | TABLE | TYPE | POSSIBLE_KEYS | KEY | KEY_LEN | REF | ROWS | EXTRA |
|----|-------------|--------|-------|---------------|----------|---------|--------|------------|-------------|
| 1 | SIMPLE | people | range | children | children | 4 | (null) | 2677610 | Using where |
正如我所看到的,范围告诉我正在扫描索引并将其与值进行比较(在这种情况下,这是 children> 0 )。我说这应该足够快。然后,我的猜测是,在获取所有匹配的索引元素之后,DBMS通过内部将索引中的值与表中的值相连接从表中获取 firstName 。
如果我将前一段翻译成SQL,我会得到类似的结果:
SELECT firstName, children FROM people
JOIN (
SELECT id FROM people
WHERE children > 0
ORDER BY children DESC
LIMIT 0, 10000
) s
ON people.id = s.id
ORDER BY children DESC
以前的SQL语句的解释是:
| ID | SELECT_TYPE | TABLE | TYPE | POSSIBLE_KEYS | KEY | KEY_LEN | REF | ROWS | EXTRA |
|----|-------------|------------|--------|---------------|----------|---------|--------|---------|---------------------------------|
| 1 | PRIMARY | <derived2> | ALL | (null) | (null) | (null) | (null) | 10000 | Using temporary; Using filesort |
| 1 | PRIMARY | p | eq_ref | PRIMARY | PRIMARY | 4 | s.id | 1 | |
| 2 | DERIVED | people | range | children | children | 4 | (null) | 2687462 | Using where; Using index |
令我惊讶的是,此查询比第一个查询执行了几次更快。但是,我增加 LIMIT X 越多,这个差异变得越大(EG:对于 LIMIT 1000000,10000 ,第二个查询仍然不到1秒,第一个查询超过20秒)。这引出了以下问题:
附加说明:
答案 0 :(得分:3)
我非常确定您可以通过children, firstname
上的索引来修复第一个查询的性能。这是查询的覆盖索引,因此它应该消除对数据页的访问。
第一个执行计划表明该索引正用于where
。最后应用了limit
,因此在应用firstname
之前似乎正在获取所有行的limit
。这看起来很奇怪,但它与你所看到的表现一致。
在第二个版本中,正在读取10000个ID。假设它们是主键,那么数据页面查找应该非常快 - 并且由限制明确控制。这可能表明为什么这个版本更快,虽然看起来有点神秘。但是,大多数情况下,我希望children, firstname
上的索引能够改进查询的第一个版本。
答案 1 :(得分:0)
似乎我在 High Performance MySQL - B. Schwartz 一书中详细介绍了这个问题。
在第193页中,有一些高偏移(即 LIMIT 1000000,10 )查询的示例以及一些改进它们的替代方法。之后我引用:
优化此类查询的另一个好方法是使用延迟连接,这也是我们使用覆盖索引来检索您最终将检索的行的主键列的术语。然后,您可以将其连接回表以检索所有所需的列。这有助于最大限度地减少MySQL必须收集的数据量,这些数据只会丢弃。这是一个需要(性别,评级)指数才能有效工作的例子:
SELECT <cols> FROM profiles INNER JOIN ( SELECT <primary key cols> FROM profiles WHERE x.sex='M' ORDER BY rating LIMIT 100000, 10 ) AS x USING(<primary key cols>);
所以似乎关键因素是使用(现有的)主键作为内部查询的覆盖索引。
回答我自己的问题:
MySQL处理与第二个查询不同的第一个查询的方式是什么?
似乎第一个不仅仅是在偏移之前获取所有行的主键。
有没有办法提示MySQL执行第一个查询的方式是执行第二个查询?
显然不是。您将不得不重新重写整个查询。
可以公平地说,从中学到的教训是,每当我想获取一个不属于正在使用的索引一部分的值时,双顺序by和join是正确的方法吗? / p>
看起来如此。但是,对于小偏移,使用延迟连接可能不会提高性能。