如何加快此语句(在包含很多行的表上运行)?:
select * from mytable where val2=4 order by key1, key2, key3 limit 1;
这是我的表格(这里显示的是按其三个关键字段排序),我想从中选择我用箭头标记的一行。主索引中有3个字段:key1,然后是key2,然后是key3。
知道我的真实表有更多列和大约100,000行(以及列val2上的索引)。
key1 | key2 | key3 | val1 | val2
-----+------+------+------+------
2 | 1 | 0 | 1 | 1
3 | 1 | 0 | 2 | 2
3 | 2 | 0 | 3 | 3
3 | 2 | 1 | 1 | 4 <==
4 | 1 | 0 | 2 | 5
4 | 2 | 0 | 3 | 1
4 | 2 | 1 | 1 | 2
4 | 3 | 0 | 2 | 3
4 | 3 | 1 | 3 | 4
4 | 3 | 2 | 1 | 5
5 | 1 | 0 | 2 | 1
5 | 2 | 0 | 3 | 2
5 | 2 | 1 | 1 | 3
5 | 3 | 0 | 2 | 4
5 | 3 | 1 | 3 | 5
5 | 3 | 2 | 1 | 1
5 | 4 | 0 | 2 | 2
5 | 4 | 1 | 3 | 3
5 | 4 | 2 | 1 | 4
5 | 4 | 3 | 2 | 5
这是完全传递所需行的声明,并且还详细说明了我想要的内容:
select * from mytable where val2=4 order by key1, key2, key3 limit 1;
我想这样做(在顺序伪代码中):
1. Select all rows which have the value 4 in field val2.
2. Sort those rows by key1, then by key2, then by key3
3. Return only the first single row of this sorted set of rows
我的select语句需要读取整个表,然后必须先排序大量的行才能找到我想要的那一行。
我认为这可以通过嵌套的子选择更快地完成(我知道这种语法是错误的,但我希望你理解我想做的事):
select * from mytable where key1+key2+key3 = (
select key1, key2, min(key3) from mytable where val2=4 and key1+key2 = (
select key1, min(key2) from mytable where val2=4 and key1 = (
select min(key1) from mytable where val2=4
)
)
)
但我不知道如何用正确的sql语法编写它,我不确定这是否真的是一种更好的方法。我认为,必须有一个优雅的解决方案,使用连接(连接表自己),但我找不到这样的解决方案。
请帮忙吗?
好的,我们来谈谈我真实的桌子:
目前,此表中只有一行,它没有3个键字段。但是这个表将在迭代过程中增长,其中必须使用我们现在讨论的语句选择一行。将处理此行,并且作为此过程的结果,将更新此行。加:将插入0到2个新行。然后重复:将选择,分析和更新新行,并再次插入0到2个新行。
在开始时,此过程将添加许多新行,需要稍后阅读。最后希望这个过程停止,因为没有更多的行与WHERE子句匹配。然后必须分析剩余的行。
因此,这是创建表并插入起始行的语句:
CREATE TABLE `numbers` (
`a0` int(10) UNSIGNED NOT NULL DEFAULT '0',
`b0` int(10) UNSIGNED NOT NULL DEFAULT '0',
`n` int(10) UNSIGNED NOT NULL DEFAULT '0',
`an` int(10) UNSIGNED NOT NULL DEFAULT '0',
`bn` int(10) UNSIGNED NOT NULL DEFAULT '0',
`m` double NOT NULL DEFAULT '0',
`gele` char(1) NOT NULL DEFAULT '?'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `numbers` (`a0`, `b0`, `n`, `an`, `bn`, `m`, `gele`) VALUES
(1, 0, 0, 0, 0, 0, '?');
ALTER TABLE `numbers`
ADD PRIMARY KEY (`a0`,`b0`),
ADD KEY `gele` (`gele`);
以下是我的发言:
SELECT `a0`, `b0`, `n`, `an`, `bn`, `m`, `gele`
FROM `numbers`
WHERE `gele` = '?' OR `gele` = '='
ORDER BY `a0`, `b0`
LIMIT 1;
这是EXPLAIN SELECT ....
:
id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra
1 | SIMPLE | numbers | NULL | index | gele | PRIMARY | 8 | NULL | 1 | 100.00 | Using where
由于目前表中只有一行,因此解释声明的结果不是很有帮助,抱歉。
但无论如何:我想要一个更通用的答案来解决这个问题,因为它经常发生。
答案 0 :(得分:1)
首先,无论记录如何在磁盘上布局,都必须使用ORDER BY
来保证SELECT
的记录顺序。优化程序(通常)会注意到记录的顺序,并且可以决定不执行任何操作。对于ORDER BY
。
在InnoDB中,记录按PRIMARY KEY
排列。因此,在PRIMARY KEY (a0,b0)
和ORDER BY a0, b0
的情况下,优化程序可能只需按顺序读取行而无需进行排序。
但是......如果您有一个WHERE
条款,比如说WHERE c0 > 3
并且您有INDEX(c0, b0)
,则优化工具可能使用过滤的索引,然后必须排序,即使你说ORDER BY a0, b0
。这可能比执行表扫描(避免排序)和过滤所有行(执行WHERE
)时更快。
你的
非常简单,非常有效,通过
完成INDEX(val2, key1, key2, key3)
SELECT ...
WHERE val2 = 4 -- filter column goes first
ORDER BY key1, key2, key3 -- sort columns next
LIMIT 1
它会准确地读出一行&#39;从该复合索引中,然后在数据中查找行(使用PRIMARY KEY
)。两者都是&#34;点查询&#34;,使用BTree索引。我们正在谈论几毫秒,即使没有任何缓存,无论表大小如何。
有关构建索引的信息,请参阅my cookbook。
但你真实的&#39;查询不相同的模式;它有一个&#39; OR&#39;
SELECT `a0`, `b0`, `n`, `an`, `bn`, `m`, `gele`
FROM `numbers`
WHERE `gele` = '?'
OR `gele` = '='
ORDER BY `a0`, `b0`
LIMIT 1;
INDEX(gele, a0, b0)
很诱人,但它不会奏效。根据{{1}},所有'?'
值的排序都很顺序,a0, b0
值也是如此。但你想要两套。这涉及&#34;合并&#34;两个排序列表。优化器有一种方法可以做到,但它很少值得努力。事实证明,有两个可能最好的&#39;索引,优化器不能总是在它们之间正确决定:
'='
由于后者是你的PK,并且使用PK有一些优势,这就是优化器选择的。如果没有&#39;?&#39;也不是&#39; =&#39;直到“最后”发生在表中的行中,查询将读取整个表。 :(
有时值得做的一件事就是将INDEX(gele) -- do all the filtering; sort later
INDEX(a0,b0) -- avoids sorting, but requires reading an indeterminate number of rows
变成OR
:
UNION
这保证很快,但它有一些开销:
是的,有一个&#39; temp&#39; table和&#39; filesort&#39;,但只有2行,它非常快。无论桌子大小如何,这种特殊配方都能快速运行。
答案 1 :(得分:0)
根据提供的信息,很难说是否有更好的方法。
鉴于您的疑问:
select * from mytable where val2=4 order by key1, key2, key3 limit 1;
WHERE
子句将首先将行限制为仅包含val2 = 4
的行,然后必须对其余进行排序以获得所需的顺序。
即使您只想要一行,也必须对所有数据进行排序。
只有val2
字段中包含索引才能加快WHERE
部分的速度。除此之外,您将受到优化器和硬件速度的支配。