【问题标题】:Optimize query selecting a period优化查询选择时间段
【发布时间】:2023-03-16 23:15:02
【问题描述】:

给定下表:

Table events
id
start_time
end_time

有没有办法快速搜索常数?

例如

SELECT *
FROM events
WHERE start_time<='2009-02-18 16:27:12' 
AND     end_time>='2009-02-18 16:27:12'

我正在使用 MySQL。在任一字段上都有索引仍然需要检查范围。此外,两个字段上的索引不会产生影响(只会使用第一个)。

我可以向表中添加字段/索引(因此添加包含两个字段信息的索引构造字段是可以接受的)。

附:对此的需求来自这个问题:Optimize SQL that uses between clause

【问题讨论】:

    标签: sql mysql query-optimization


    【解决方案1】:

    我的解决方案有一个警告:

    1) 对此解决方案的警告是,您必须将 MyISAM 引擎用于事件表。如果您不能使用 MyISAM,那么此解决方案将不起作用,因为空间索引仅支持 MyISAM。

    因此,假设以上对您来说不是问题,以下应该可以工作并为您提供良好的性能:

    此解决方案利用 MySQL 对空间数据的支持(请参阅documentation here)。虽然可以将空间数据类型添加到各种存储引擎中,但为了获得所需的性能,空间 R-Tree 索引(请参阅documentation here)仅支持 MyISAM。另一个限制是空间数据类型仅适用于数字数据,因此您不能将此技术用于基于字符串的范围查询。

    我不会详细介绍空间类型如何工作以及空间索引如何有用背后的理论,但您应该查看Jeremy Cole's explanation here,了解如何使用空间数据类型和索引进行 GeoIP 查找。如果您需要原始性能并且可以放弃一些准确性,还可以查看 cmets,因为它们提出了一些有用的点和替代方案。

    基本前提是我们可以取开始/结束并使用它们中的两个来创建四个不同的点,一个用于在 xy 网格上以 0,0 为中心的矩形的每个角,然后进行快速查找进入空间索引,以确定我们关心的特定时间点是否在矩形内。如前所述,请参阅 Jeremy Cole 的解释,以更全面地了解其工作原理。

    在您的特定情况下,我们需要执行以下操作:

    1) 将表更改为 MyISAM 表(请注意,除非您完全了解此类更改的后果,例如缺少与 MyISAM 关联的事务和表锁定行为,否则不应这样做)。

    alter table events engine = MyISAM;
    

    2) 接下来我们添加将保存空间数据的新列。我们将使用多边形数据类型,因为我们需要能够容纳一个完整的矩形。

    alter table events add column time_poly polygon NOT NULL;
    

    3) 接下来我们用数据填充新列(请记住,任何更新或插入表事件的进程都需要进行修改以确保它们也填充新列)。由于开始和结束范围是时间,我们需要使用 unix_timestamp 函数将它们转换为数字(请参阅 documentation here 了解其工作原理)。

    update events set time_poly := LINESTRINGFROMWKB(LINESTRING(
        POINT(unix_timestamp(start_time), -1),
        POINT(unix_timestamp(end_time), -1),
        POINT(unix_timestamp(end_time), 1),
        POINT(unix_timestamp(start_time), 1),
        POINT(unix_timestamp(start_time), -1)
      ));
    

    4) 接下来我们将空间索引添加到表中(如前所述,这仅适用于 MyISAM 表,并且会产生错误“ERROR 1464 (HY000): The used table type does not support SPATIAL index” )。

    alter table events add SPATIAL KEY `IXs_time_poly` (`time_poly`);
    

    5) 接下来您需要使用以下选择,以便在查询数据时使用空间索引。

    SELECT * 
    FROM events force index (IXs_time_poly)
    WHERE MBRCONTAINS(events.time_poly, POINTFROMWKB(POINT(unix_timestamp('2009-02-18 16:27:12'), 0)));
    

    强制索引可以 100% 确保 MySQL 将使用索引进行查找。如果一切顺利,在上述选择上运行解释应该显示类似于以下内容:

    mysql> explain SELECT *
        -> FROM events force index (IXs_time_poly)
        -> on MBRCONTAINS(events.time_poly, POINTFROMWKB(POINT(unix_timestamp('2009-02-18 16:27:12'), 0)));
    +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+
    | id | select_type | table | type  | possible_keys | key           | key_len | ref  | rows | Extra       |
    +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+
    |  1 | SIMPLE      | B     | range | IXs_time_poly | IXs_time_poly | 32      | NULL |    1 | Using where | 
    +----+-------------+-------+-------+---------------+---------------+---------+------+------+-------------+
    1 row in set (0.00 sec)
    

    请参阅 Jeremy Cole 的分析,详细了解此方法与 between 子句相比的性能优势。

    如果您有任何问题,请告诉我。

    谢谢,

    -地平

    【讨论】:

    • 非常有趣的解决方案,链接文章中的性能数字令人印象深刻。
    • 您的解释是宝藏,提供的链接非常有用。我确实尝试阅读有关 r-tree 索引的内容,但感到困惑并放弃了。谢谢。
    【解决方案2】:

    MySQL 中没有有效的方法来准确执行此查询。

    但是,如果您的范围不重叠,您可以使用 start_time &lt;= constORDER BY start_time DESC LIMIT 1 并进一步检查 end_time &gt;= const

    您需要在函数中执行此操作,因为如果范围条件取自超级查询,则 MySQL 出于某种原因不会在子查询中将 INDEX RANGE SCAN 用于 ORDER BY

    CREATE UNIQUE INDEX ux_b_start ON b (start_date);
    
    CREATE FUNCTION `fn_get_last_b`(event_date TIMESTAMP) RETURNS int(11)
    BEGIN
      DECLARE id INT;
      SELECT b.id
      INTO id
      FROM b
      FORCE INDEX (ux_b_start)
      WHERE b.start_time <= event_date
      ORDER BY
        b.start_time DESC
      LIMIT 1;
      RETURN id;
    END;
    
    SELECT COUNT(*) FROM a;
    
    1000
    
    
    SELECT COUNT(*) FROM b;
    
    200000
    
    SELECT *
    FROM (
      SELECT fn_get_last_b(a.event_time) AS bid,
             a.*
      FROM a
    ) ao, b FORCE INDEX (PRIMARY)
    WHERE b.id = ao.bid
      AND b.end_time >= ao.event_time
    
    1000 rows fetched in 0,0143s (0,1279s)
    

    【讨论】:

    • 您的“start_time
    【解决方案3】:

    我对 MySQL 没有太多经验,但在 MS SQL Server 上,在两列上添加索引允许在 1M 行表上进行索引查找和返回时间从 1-2 秒变为毫秒响应时间。

    您似乎看到了不同的结果。我想知道约束是否有所作为。我有一个检查约束来强制执行 start_time

    【讨论】:

    • MS SQL 在这种情况下使用“索引组合”。它使用两个索引选择两个范围,并使用哈希连接找到一个交集。如果你放置一个同时有很多 start_times 和很多 end_times 满足适当条件的常量,这将是最低效的情况。
    • 或者如果你创建一个多列索引,它将使用和索引扫描来检查这两个条件。在这种情况下,常数越大,查询越慢。
    • 如果它有 start_time > @time 那么它不应该需要一个完整的索引扫描,所以常数的时间(我假设这就是你所说的“更大”的意思)应该无关紧要。至于谁否决了一个理由可能会很高兴看到我所说的一切都不正确,它已经过测试,我解释说它是针对 MS
    • 好吧,我当然也希望看到人们对我的回答投反对票的愚蠢理由。特别是考虑到它实际上也完美无瑕。但很明显,这个地方到处都是想不到就开枪的人。
    • 如果 (start_time, end_time) 上有索引,则查询将从 @time 到 MIN(start_time) 对此索引执行 RANGE SCAN,跳过不适合的 end_time。如果@time 很深,RANGE SCAN 将只扫描几条记录,如果@time = NOW(),RANGE SCAN 将扫描整百万条记录。
    【解决方案4】:

    您基本上得到了一个带有 2 个截然不同的范围条件的查询。您正在使用 >=,对于 MySQL,这始终是范围扫描。有文档here 可以优化范围扫描。

    底线是 MySQL 执行额外检查以过滤掉满足范围条件的行,然后满足 WHERE 子句的其余部分,在您的情况下这是另一个范围条件。

    【讨论】:

      【解决方案5】:

      我打算问一个关于优化事件搜索的类似问题(具有开始和停止时间的项目),我已经在使用不同的方法,所以我会把它扔在那里。

      基本上,如果您知道您的事件永远不会大于给定的持续时间,您可以搜索大于最大持续时间的有界范围,然后添加限制以消除匹配的额外内容。因此,要获得与搜索时间相交的时间:

      SELECT *
      FROM events
      WHERE 
         ( start_time BETWEEN ( 'search_start' - INTERVAL 2 DAY ) and 'search_end' )
         AND end_time >= 'search_start'
      

      ...您需要在start_time 上建立索引。

      (注意——我的表在 4 年内有数百万个事件,没有超过 24 小时的记录......我不知道这相对于空间搜索方法的表现如何,因为我将不得不去自己试试。)

      【讨论】:

        【解决方案6】:

        您在一张桌子上无能为力。如果优化这些查询 1) 是必要的 2) 必须在 SQL 级别完成,那么您需要创建一个派生表:

        Table event_times
        id
        event_id
        mark_time
        

        并为每个事件跨越的每个时间单位添加一条记录。那你就

        SELECT *
        FROM events
        LEFT JOIN event_times ON event_id = events.id
        WHERE mark_time = '2009-02-18 16:27:12'
        

        您可以通过定义“时间单位”的方式使该表不那么荒谬,即如果您将 mark_time 的分辨率限制为分钟或小时而不是秒。

        【讨论】:

          猜你喜欢
          • 2011-04-11
          • 2023-03-12
          • 2016-01-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-20
          • 1970-01-01
          相关资源
          最近更新 更多