SQL:从词法有序表

时间:2016-11-25 12:20:23

标签: mysql sql optimization

简而言之

如何加快此语句(在包含很多行的表上运行)?:

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

由于目前表中只有一行,因此解释声明的结果不是很有帮助,抱歉。

但无论如何:我想要一个更通用的答案来解决这个问题,因为它经常发生。

2 个答案:

答案 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)时更快。

你的

  1. 选择字段val2中值为4的所有行。
  2. 按key1排序,然后按key2排序,再按key3
  3. 排序
  4. 仅返回此有序行集的第一行
  5. 非常简单,非常有效,通过

    完成
    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

    这保证很快,但它有一些开销:

    1. 搜索&#39;?&#39; - 及时找到行。写到tmp表。
    2. 搜索&#39; =&#39; - 及时找到行。附加到tmp表。
    3. 对tmp表进行排序。
    4. 剥掉一排。
    5. 是的,有一个&#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部分的速度。除此之外,您将受到优化器和硬件速度的支配。