优化查询选择期间

时间:2009-02-18 14:40:20

标签: sql mysql query-optimization

鉴于下表:

Table events
id
start_time
end_time

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

E.g。

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

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

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

P.S。对此的需求来自这个问题:Optimize SQL that uses between clause

6 个答案:

答案 0 :(得分:6)

我的解决方案有一点需要注意:

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

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

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

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

基本前提是我们可以采用开始/结束并使用它们中的两个来创建四个不同的点,一个用于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):使用的表类型不支持SPATIAL索引” )。

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的分析,了解有关此方法的性能优势的详细信息,与之间的条款相比较。

如果您有任何问题,请与我们联系。

谢谢,

-Dipin

答案 1 :(得分:2)

MySQL中没有有效的方法可以完成此查询。

但是,如果您的范围不重叠,则只需使用start_time <= constORDER BY start_time DESC LIMIT 1,然后再检查end_time >= const

您需要在函数中执行此操作,因为MySQL由于某种原因,如果范围条件取自superquery,则子查询中的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)

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

你基本上得到了一个包含两个截然不同的范围条件的查询。您正在使用&gt; =,对于MySQL,这始终是范围扫描。有文档here来优化范围扫描。

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

答案 4 :(得分:0)

我打算在优化事件搜索(具有开始和停止时间的项目)上提出类似的问题,而且我已经使用了不同的方法,所以我会把它扔出去。

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

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

...您希望start_time上有索引。

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

答案 5 :(得分:-1)

在一张桌子里你可以做的不多。如果优化这些查询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的分辨率限制在几分钟或几小时而不是几秒钟。