【问题标题】:Working out the SQL to query a priority queue table制定查询优先级队列表的 SQL
【发布时间】:2010-10-02 17:10:37
【问题描述】:

我正在实现一个小队列来处理哪个进程首先运行。我正在使用数据库中的表来执行此操作。这是表的结构(我在 SQLite 中模拟它):

        "id" INTEGER PRIMARY KEY  AUTOINCREMENT  NOT NULL ,
        "identifier" VARCHAR NOT NULL ,
        "priority_number" INTEGER DEFAULT 15,
        "timestamp" DATETIME DEFAULT CURRENT_TIMESTAMP,
        "description" VARCHAR

我正在尝试编写 SQL 来告诉我接下来可以运行哪个进程的行。以下是一些示例数据:

id  identifier  priority_number timestamp   description
1   test1   15  2009-01-20 17:14:49 NULL
2   test2   15  2009-01-20 17:14:56 NULL
3   test3   10  2009-01-20 17:15:03 NULL
4   test4   15  2009-01-20 17:15:08 NULL
5   test5   15  2009-01-20 17:32:23 NULL
6   test6   14  2009-01-20 17:32:30 NULL
7   test7   7   2009-01-20 17:32:38 NULL
8   test8   20  2009-01-20 17:32:57 NULL
9   test9   7   2009-01-21 13:47:30 NULL
10  test10  15  2009-01-21 13:50:52 NULL

如果我使用这个 SQL,我可以按正确的顺序获取数据:

select * from queue_manager order by priority_number, timestamp;

这将为我提供顶部具有最低优先级编号(最重要)的项目,并且在这些优先级编号中,最早进入队列(按时间戳)在顶部。

我可以运行此查询,并且只获取第一行,但我宁愿使用 SQL 查询来执行此操作,该查询将为我提供位于队列顶部的进程的一行(在上面的示例数据中, id=7 的行)。

我尝试进行自连接和子查询,但我一定有心理障碍 - 我似乎无法正确处理。

提前致谢!

编辑

我忘了提到我正在寻找一个独立于数据库的查询。我在 SQlite 中对此进行了模拟,但很有可能我会在 DB2 或 Oracle 中实现它。我曾想过在我的查询中使用“limit 1”类型的运算符,但这在不同的数据库引擎之间是不同的。

【问题讨论】:

    标签: sql queue priority-queue queue-table


    【解决方案1】:

    看看这是否有效:

    select * from queue_manager where priority_number = 
    (select min(priority_number) from queue_manager) and  
    timestamp = (select min(timestamp) 
    from queue_manager qm2 
    where qm2.priority_number = queue_manager.priority_number)
    

    【讨论】:

    • 是的,这个有效。我不打算在此表中包含太多行,但您认为这会表现得体面吗?
    • 如果您有很多行,您可能必须在每列上定义一个索引。
    • 刚刚在这里检查了一个数据库,对于一个有 200k 行的表,它在一秒钟内返回,没有使用任何索引。
    • 我也在做一些测试——这个效果很好。非常感谢!
    【解决方案2】:
    select * from queue_manager order by priority_number, timestamp LIMIT 1;
    

    至于所谓的“数据库独立性”,对于大多数现实世界的任务来说都是一个神话。通常,您甚至不能以独立于数据库的方式创建模式。

    【讨论】:

    • 我正在尝试编写我的程序,以便使用什么数据库无关紧要。 DB2 最终实现的可能性很大,而且 DB2 不支持限制 - 它支持“仅获取前 1 行”
    • 如果你必须经历 100 万个工作,并且每次都选择最重要的一个,那么你正在做 O(N * log(N)) where N = 1000000 这非常慢。有时,您最好从表格的最顶端选择一项任务并进行处理,而不是每次都对第一个任务进行排序和选择。但是,鉴于数据库记录很可能存储为二叉树,我想知道该怎么做。
    • @HamishGrubijan:你听说过数据库索引吗?
    【解决方案3】:

    如果您希望它在 InnoDB 之类的东西上“并发安全”:

    1) 添加一个“in_progress”字段。

    2) 关闭自动提交

    3) SELECT * FROM queue_manager where in_progress = 0 order by priority_number, timestamp LIMIT 1 FOR UDPATE;

    4) UPDATE queue_manager SET in_progress = 1 where id = X;

    5) 提交

    6) 做好工作。然后在完成后删除该行。有一个“主进程”处理/重新委托/清理旧的“in_progress”作业。

    【讨论】:

      【解决方案4】:

      最好的方法是依赖于数据库;与游标或其他结构的所有开销相比,为不同的目标 DBMS 提供不同的检索过程要简单得多。

      【讨论】:

        【解决方案5】:

        选择有限数量的行在不同风格的 SQL 中以不同的方式完成,因此根据您使用的方式,可能会有内置的方法来完成。例如,在 MS SQL Server 中:

        SELECT TOP 1
             identifier,
             priority_number,
             timestamp,
             description
        FROM
             dbo.Queue_Manager
        ORDER BY
             priority_number,
             timestamp
        

        要在 ANSI 兼容的 SQL 中执行此操作,应使用以下方法:

            SELECT
                 QM1.identifier,
                 QM1.priority_number,
                 QM1.timestamp,
                 QM1.description
            FROM
                 Queue_Manager QM1
            LEFT OUTER JOIN Queue_Manager QM2 ON
                 QM2.priority_number < QM1.priority_number OR
                 (QM2.priority_number = QM1.priority_number AND QM2.timestamp < QM1.timestamp)
            /* If you're concerned that there might be an exact match by priority_number
        and timestamp then you might want to add a bit more to the join */
            WHERE
                 QM2.identifier IS NULL
        

        或者你可以试试:

        SELECT
             QM1.identifier,
             QM1.priority_number,
             QM1.timestamp,
             QM1.description
        FROM
             Queue_Manager QM1
        INNER JOIN
             (
                  SELECT
                       priority_number
                       MIN(timestamp) AS timestamp,
                  FROM
                       Queue_Manager
                  WHERE
                       priority_number = 
                            (
                                 SELECT
                                      MIN(priority_number)
                                 FROM
                                      Queue_Manager
                            )
                  GROUP BY
                       priority_number
             ) SQ1 ON
                  SQ1.priority_number = QM1.priority_number AND
                  SQ1.timestamp = QM1.timestamp
        

        这两种方法都不能同时考虑priority_number 和timestamp 的完全匹配,因此如果您认为这是可能的(即使您不这样做),您需要添加一两行以使用标识符再上一层或其他保证唯一性的东西。或者只是编写你的前端来处理偶尔返回两行的情况(也许只是忽略第二行 - 你下次会得到它)。

        测试每种方法,看看哪种方法更适合您。

        另外,您希望队列有多大?仅使用您的 ORDER BY 进行查询并且只让前端检索第一行可能是合理的。

        【讨论】:

          【解决方案6】:

          阅读this 部分并选择为您提供最合适兼容性的变体。可能使用游标是唯一或多或少普遍兼容的方式,但有一些性能损失,可能不值得(配置文件!)。

          【讨论】:

            【解决方案7】:

            关系数据库不擅长管理队列。

            尝试查看 Windows 世界中的 MSMQ、Java 世界中的 ActiveMQ 或商业世界中的 Websphere MQ。

            这些产品只做一件事情,管理队列,但它们做得很好。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-01-07
              • 2011-12-20
              • 2012-03-03
              • 1970-01-01
              • 1970-01-01
              • 2010-09-08
              相关资源
              最近更新 更多