使用数据库表作为队列

时间:2010-02-01 15:45:22

标签: sql database sql-server-2008 queue

我想将数据库表用作队列。我想插入其中并以插入的顺序(FIFO)从中获取元素。我主要考虑的是性能,因为我每秒都有成千上万的这些交易。所以我想使用一个SQL查询,它给我第一个元素而不搜索整个表。我读的时候不会删除一行。 SELECT TOP 1 .....帮忙吗? 我应该使用任何特殊索引吗?

9 个答案:

答案 0 :(得分:30)

我使用IDENTITY字段作为主键,为每个排队项提供唯一递增的ID,并在其上粘贴聚簇索引。这将表示项目排队的顺序。

要在处理项目时保留队列表中的项目,您需要一个“状态”字段来指示特定项目的当前状态(例如0 =等待,1 =正在处理,2 =处理)。这是为了防止项目被处理两次。

处理队列中的项目时,您需要找到当前未处理的表格中的下一个项目。这将需要以这样的方式,以便防止多个进程同时处理同一个项目,如下所示。请注意实现队列时应注意的table hints UPDLOCK和READPAST。

e.g。在一个sproc中,像这样:

DECLARE @NextID INTEGER

BEGIN TRANSACTION

-- Find the next queued item that is waiting to be processed
SELECT TOP 1 @NextID = ID
FROM MyQueueTable WITH (UPDLOCK, READPAST)
WHERE StateField = 0
ORDER BY ID ASC

-- if we've found one, mark it as being processed
IF @NextId IS NOT NULL
    UPDATE MyQueueTable SET Status = 1 WHERE ID = @NextId

COMMIT TRANSACTION

-- If we've got an item from the queue, return to whatever is going to process it
IF @NextId IS NOT NULL
    SELECT * FROM MyQueueTable WHERE ID = @NextID

如果处理某个项目失败,您希望以后能够再次尝试吗?如果是这样,您需要将状态重置为0或其他。这需要更多的思考。

或者,不要将数据库表用作队列,而是使用像MSMQ这样的东西 - 我以为我会把它扔进混合中!

答案 1 :(得分:7)

如果您不删除已处理的行,那么您将需要某种标志来指示已经处理了一行。

在该标志上放置一个索引,并在您要订购的列上。

将您的表分区到该标志上,因此出列的事务不会堵塞您的查询。

如果您真的每秒都会收到1.000条消息,那么每天会产生86.400.000行。您可能想要想办法清理旧行。

答案 2 :(得分:4)

一切都取决于您的数据库引擎/实现。

对于包含以下列的表上的简单队列:

id / task / priority / date_added

通常有效。

我使用优先级和任务来分组任务,如果任务加倍,我选择了优先级较高的任务。

不要担心 - 对于现代数据库来说,“千千万万”并不特别。

答案 3 :(得分:3)

只要您使用某些东西来跟踪插入的日期时间,这就不会有任何麻烦。请参阅此处查看mysql options。问题是您是否只需要绝对最近提交的项目或是否需要迭代。如果你需要迭代,那么你需要做的就是抓住一个带有ORDER BY语句的块,循环,然后记住最后一个日期时间,这样你就可以在抓住你的时候使用它下一个块。

答案 4 :(得分:2)

或许在你的select语句中添加LIMIT = 1会有助于...在单个匹配后强制返回...

答案 5 :(得分:2)

在日期(或自动增量)列上创建聚簇索引。这将使表中的行大致按索引顺序保留,并在ORDER BY索引列时允许基于索引的快速访问。使用TOP X(或LIMIT X,具体取决于您的RDMBS)将只检索索引中的前x个项目。

性能警告:您应该始终查看查询的执行计划(在实际数据上),以验证优化程序不会执行意外操作。同时尝试对您的查询进行基准测试(再次根据实际数据),以便做出明智的决策。

答案 6 :(得分:2)

由于您没有从表中删除记录,因此需要在(processed, id)上有一个复合索引,其中processed是指示当前记录是否已被处理的列。

最好的方法是为记录创建分区表,并将PROCESSED字段作为分区键。这样,您可以保留三个或更多本地索引。

但是,如果您始终以id顺序处理记录,并且只有两个状态,则更新记录将意味着从索引的第一个叶子中取出记录并将其附加到最后一个叶子

当前处理的记录始终具有所有未处理记录中id个最少的记录以及所有已处理记录中id的最大记录。

答案 7 :(得分:1)

我有一个相同的一般性问题,即“如何将桌子变成队列”,却找不到我想要的答案。

这是我为Node / SQLite / better-sqlite3想到的。 基本上,只需为您的用例修改内部的WHEREORDER BY子句。

module.exports.pickBatchInstructions = (db, batchSize) => {
  const buf = crypto.randomBytes(8); // Create a unique batch identifier

  const q_pickBatch = `
    UPDATE
      instructions
    SET
      status = '${status.INSTRUCTION_INPROGRESS}',  
      run_id = '${buf.toString("hex")}',
      mdate = datetime(datetime(), 'localtime')
    WHERE
      id IN (SELECT id 
        FROM instructions 
        WHERE 
          status is not '${status.INSTRUCTION_COMPLETE}'
          and run_id is null
        ORDER BY
          length(targetpath), id
        LIMIT ${batchSize});
  `;
  db.run(q_pickBatch); // Change the status and set the run id

  const q_getInstructions = `
    SELECT
      *
    FROM
      instructions
    WHERE
      run_id = '${buf.toString("hex")}'
  `;
  const rows = db.all(q_getInstructions); // Get all rows with this batch id

  return rows;
};

答案 8 :(得分:0)

为了不使用事务,锁等,一个非常简单的解决方案是使用更改跟踪机制(而不是数据捕获)。它为每个添加/更新/删除的行使用版本控制,以便您可以跟踪特定版本之后发生的更改。

因此,您将保留最后一个版本并查询新更改。

如果查询失败,您可以随时返回并查询上一版本的数据。 此外,如果您不想通过一个查询获得所有更改,您可以按上一个版本获得最高排序,并存储我必须再次查询的最佳版本。

请参阅此示例Using Change Tracking in SQL Server 2008