用于在MySQL中定位未处理条目的高效索引

时间:2016-04-06 11:03:08

标签: mysql indexing

我有一个包含数百万条目的MySQL表。

每个条目必须在某个时候由cron作业处理。

我需要能够使用索引快速找到未处理的条目。

到目前为止,我使用了以下方法:我添加了一个可为空的索引processedOn列,其中包含处理该条目的时间戳:

CREATE TABLE Foo (
    ...
    processedOn INT(10) UNSIGNED NULL,
    KEY (processedOn)
);

然后使用以下方法检索未处理的条目:

SELECT * FROM Foo WHERE processedOn IS NULL LIMIT 1;

感谢MySQL的IS NULL optimization,查询速度非常快,只要未处理的条目数量很小(几乎总是如此)。

这种方法已经足够了:它完成了这项工作,但与此同时我觉得索引被浪费了,因为它只用于WHERE processedOn IS NULL查询,而且从不用于定位精确值或该字段的值范围。因此,这对存储空间和INSERT性能产生了不可避免的影响,因为每个时间戳都没有索引。

有更好的方法吗?理想情况下,索引只包含指向未处理行的指针,而不包含指向任何已处理行的指针。

我知道我可以把这张桌子分成两张桌子,但是我想把它放在一张桌子里。

2 个答案:

答案 0 :(得分:1)

我想到的是创建一个isProcessed列,默认值=' N'你设置为' Y'处理时(同时设置processedOn列)。然后在isProcessed字段上创建索引。当您查询(使用where子句WHERE isProcessed =' N')时,它将以非常快的速度响应。

UPDATE:ALTERNATIVE with partitioning:

使用分区创建表并定义一个只有2个值1或0的字段。这将为field = 1的记录创建一个分区,为field = 0的记录创建另一个分区。

create table test (field1 int, field2 int DEFAULT 0)
PARTITION BY LIST(field2) (
    PARTITION p0 VALUES IN (0),
    PARTITION p1 VALUES IN (1)  
);

这样,如果您只想查询字段等于其中一个值的记录,只需执行以下操作:

select * from test partition (p0);

上面的查询将仅显示field2 = 0的记录。 如果您需要一起查询所有记录,您只需正常查询该表:

select * from test;

据我所知,这将有助于满足您的需求。

答案 1 :(得分:1)

我对其他人有多个答案和评论'答案。

首先,我假设PRIMARY KEY Fooid INT UNSIGNED AUTO_INCREMENT(4个字节),表格为Engine = InnoDB。

索引额外列

额外列的索引是每行,额外列的宽度和PRIMARY KEY,以及一堆开销。使用processedOn,您说的是8个字节(2个INT)。用一个简单的标志,5个字节。

单独的表格

对于未处理的项,此表格只有id。填充它需要额外的代码。它的大小会留在某些高水位线上。#34;因此,如果有一批未经处理的项目,它会增长,但不会收缩。 (这里是罕见的案例,其中OPTIMIZE TABLE很有用。)InnoDB需要PRIMARY KEYid才能完美运行。所以,一列,没有额外的索引。它比上面讨论的额外索引要小很多。寻找可以解决的问题:

$id = SELECT id FROM tbl LIMIT 1;   -- don't care which one
process it
DELETE FROM tbl where id = $id

2个PARTITION,一个已处理,一个未

没有。将行从已处理更改为未处理时,必须从一个分区中删除该行并将其插入另一个分区。这是由UPDATE ... SET flag = 1在幕后完成的。此外,两个分区都有"高水位"问题 - 它们会增长但不会缩小。分区的空间开销可能与其他解决方案一样多。

SELECT by PARTITION ...需要5.6。如果没有这个,你需要INDEX,所以你回到索引问题。

持续扫描

这会产生零额外磁盘空间。 (那比你想象的要好,对吗?)而且效率不高。这是它的工作原理。这里有一些伪代码可以放入你的cron作业。但是,不要把它变成一项任务。相反,让它一直运行。 (原因很明显,我希望。)

SELECT @a := 0;
Loop:
    # Get a clump
    SELECT @z := id FROM Foo WHERE id > @a ORDER BY id LIMIT 1000,1;
    if no results, Set @z to MAX(id)
    # Find something to work on in that clump:
    SELECT @id := id FROM Foo
        WHERE id > @a
          AND id <= @z
          AND not-processed
        LIMIT 1;
    if you found something, process it and set @z := @id
    SET @a := @z;
    if @a >= MAX(id), set @a := 0;   # to start over
    SLEEP 2 seconds   # or some amount that is a compromise
Go Loop

注意:

  • 它以最小的影响穿过桌子。
  • 即使id中存在空白也是如此。 (如果没有间隙,可以更简单。)(如果PK不是AUTO_INCREMENT,它几​​乎相同。)
  • sleep是一个“好人”。

选择性指数

MariaDB的动态专栏和MySQL 5.7的JSON可以为事物编制索引,我认为他们是&#34;选择性&#34;。一个状态是将列清空,另一个状态是在dynamic / json列中设置标志。这将需要一些研究来验证,并可能需要升级。