[英]SQL: Select only 1st row from lexical ordered table
如何加快该语句的速度(在具有很多行的表上运行)?
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个键字段,而是2个键字段。 但是此表将以迭代的方式增长,其中必须使用我们现在讨论的语句选择一行。 此行将被处理,作为此过程的结果,该行将被更新。 加号:将在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
由于当前表中只有1行,对不起,explain语句的结果不是很有帮助。
但无论如何:我想对此问题有一个更通用的答案,因为它经常发生。
首先,无论记录在磁盘上的布局方式如何,都必须使用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
,也必须进行排序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
它将从该复合索引中只读取一个“行”,然后在数据中查找行(使用PRIMARY KEY
)。 两者都是使用BTree索引的“点查询”。 不管表大小如何,即使没有缓存任何内容,我们正在谈论几毫秒。
请参阅我的构建索引手册。
但是您的“真实”查询不是相同的模式。 它有一个“或”
SELECT `a0`, `b0`, `n`, `an`, `bn`, `m`, `gele`
FROM `numbers`
WHERE `gele` = '?'
OR `gele` = '='
ORDER BY `a0`, `b0`
LIMIT 1;
INDEX(gele, a0, b0)
很诱人,但无法正常工作。 所有的'?'
值根据a0, b0
很好地排序,因此'='
值也是如此。 但是你要两套。 这涉及“合并”两个排序的列表。 优化器可以做到这一点,但很少值得付出。 事实证明,存在两个可能的“最佳”索引,而优化器不能始终正确地在它们之间做出决定:
INDEX(gele) -- do all the filtering; sort later
INDEX(a0,b0) -- avoids sorting, but requires reading an indeterminate number of rows
由于后者是您的PK,因此使用PK有一些优势,这就是Optimizer选择的。 如果不 '?' 直到表中的“最后”行也不会出现“ =”,查询将读取整个表。 :(
有时值得做的一招是将OR
变成UNION
:
( SELECT `a0`, `b0`, `n`, `an`, `bn`, `m`, `gele`
FROM `numbers`
WHERE `gele` = '?'
ORDER BY `a0`, `b0`
LIMIT 1 ) -- Step 1, below
UNION ALL
( SELECT `a0`, `b0`, `n`, `an`, `bn`, `m`, `gele`
FROM `numbers`
WHERE `gele` = '='
ORDER BY `a0`, `b0`
LIMIT 1 ) -- Step 2
ORDER BY a0, b0 -- yes repeated -- Step 3
LIMIT 1; -- Step 4
INDEX(gele, a0, b0)
这可以保证很快,但是有一些开销:
是的,有一个“临时”表和“文件排序”,但是只有两行,所以速度非常快。 不管表的大小如何,此特定公式都可以快速运行。
从提供的信息来看,很难说是否有更好的方法。
根据您的查询:
select * from mytable where val2=4 order by key1, key2, key3 limit 1;
WHERE
子句将首先将行限制为仅包含val2 = 4
的行,然后必须对其余行进行排序以获取所需的顺序。
即使只需要一行,也必须对所有数据进行排序。
仅在val2
字段中包含索引会加快此操作的WHERE
部分。 除此之外,您还处于优化器和硬件速度的控制之下。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.