繁体   English   中英

SQL:从词法排序表中仅选择第一行

[英]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 )。

你的

  1. 在字段val2中选择所有值为4的行。
  2. 按key1,key2,key3对这些行进行排序
  3. 仅返回此行排序集中的第一行

非常简单,非常有效地通过

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)

这可以保证很快,但是有一些开销:

  1. 搜索 '?' -立即找到该行。 写入tmp表。
  2. 搜索“ =”-立即找到该行。 追加到tmp表。
  3. 排序tmp表。
  4. 剥下1行。

是的,有一个“临时”表和“文件排序”,但是只有两行,所以速度非常快。 不管表的大小如何,此特定公式都可以快速运行。

从提供的信息来看,很难说是否有更好的方法。

根据您的查询:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM