强制MySQL使用非包容性索引来避免表扫描?

时间:2011-03-07 10:09:29

标签: mysql indexing

背景: 具有字段ID AUTO INCREMENT PRIMARY KEY的巨大表,以及在行插入操作时存储UNIX时间戳的另一列时间戳。该时间戳列不在任何索引中,由于性能原因,我无法对其进行索引。

情况:我们需要在特定的时间戳值之前查询这个巨大的表中的行;我们可以发出SELECT语句并在WHERE子句中指定该条件,但这会导致全表扫描,因为时间戳列未被索引。

建议:这两列的性质随着每行插入而增加:AUTO INCREMENT列增加,timestamp列也增加。我可以每次将表分成两个相等的行,并检查每个边界的时间戳,依此类推,直到我到达一行,然后使用该ID对它进行正常SELECT

此解决方案存在问题:这很难,需要大约25个查询才能完成该操作,随着表的增长,数字也会增加。

所以问题是:可以指示MySQL在原子上下文中执行该操作吗?

3 个答案:

答案 0 :(得分:3)

我会尝试为您的主要构建一个辅助表,至少作为查询的限制基础。用......之类的东西来填充表格(显然,将indexe放在日期基础上)

create table DailyStartKey as 
SELECT 
      DATE( FROM_UNIXTIME( YourTimeStampColumn ) ) AS DateBasis,
      min( YourAutoIncColumn ) as FirstPKForDay
   from 
      YourTable
   group by 
      1

然后,您可以预先查询此表以获得查询中的最小PK(如果查找日期范围,则可能是最大值)。为了保持它的维护而不必继续重建它,我会在你的主表中添加一个触发器,如果​​尚未插入日期,则尝试插入这个DailyStartKey表。

利用这个的这种查询可能是......

select
      YourTable.*
   from 
      ( select FirstPKForDay
            from DailyStartKey
            where DateBasis = "2011-02-12" ) StartDate,
      ( select FirstPKForDay
            from DailyStartKey
            where DateBasis = "2011-02-25" ) LastDate,
      YourTable
   where
         YourTable.YourAutoIncColumn >= StartDate.FirstPKForDay
     and YourTable.YourAutoIncColumn <= LastDate.FirstPKForDay

编辑澄清另一个TRIGGER实施。

为了防止需要继续查询“DailyStartKey”表,您可以创建另一个表,该表始终只有一条记录与创建条目的最后一天。 (或日/小时取决于您想要的粒度)。

然后,在触发器中查看是否需要新条目,只需

Select * from LastDateEntryTable where LastDate = CurrentDate

如果已找到一个,则忽略..否则,触发器应插入每日密钥表并更新“LastDateEntryTable”。

答案 1 :(得分:1)

即兴创作Drapp的想法:创建一个帮助表和一个触发器,在该表中为每一个100中添加1行。此表将等效于以下视图,但它将在datetimeBasis上有一个索引。

CREATE VIEW HundredRowsStartKey AS
  SELECT 
      YourTimeStampColumn AS datetimeBasis,
      YourAutoIncColumn AS id
    FROM 
      YourTable
    WHERE
      YourAutoIncColumn % 100 = 0
;

还对最终查询详细信息进行了改进,因此它对大表的最多200行进行日期时间扫描。使用索引获取所有其他匹配的行和所需的中间数据:

  • 在帮助表上搜索2次 (HundredRowsStartKey)和
  • 3范围 检查大桌子的索引。

因此,日期时间范围查询:

SELECT *
  FROM 
    YourTable
  WHERE 
    YourTimeStampColumn BETWEEN "2011-02-12-01.00.23" 
                            AND "2011-03-15-12.00.00"
;

会变成:

WITH starting AS
  SELECT
      max(id) AS startLow
    FROM
      HundredRowsStartKey h
    WHERE datetimeBasis <= "2011-02-12-01.00.23"
;

WITH ending AS
  SELECT
      max(id) AS endLow
    FROM
      HundredRowsStartKey h
    WHERE datetimeBasis <= "2011-03-15-12.00.00"
;

SELECT *
  FROM 
    YourTable
  WHERE
    -- these are guaranteed
    ( YourAutoIncColumn >= starting.startLow+100
    AND YourAutoIncColumn <= ending.endLow-1
    ) 
    -- and these 200 we have to filter
    OR
    ( ( YourAutoIncColumn BETWEEN starting.startLow 
                              AND starting.startLow+99
        OR
        YourAutoIncColumn BETWEEN ending.endLow 
                              AND ending.endLow+99
      )
      -- with the original filter
      AND
      ( YourTimeStampColumn BETWEEN "2011-02-12-01.00.23" 
                                AND "2011-03-15-12.00.00"
      )
    )
;

可能需要进行少量编辑以确保捕获所有边缘情况(例如,在表格中所有记录之前开始日期时间等等)

答案 2 :(得分:0)

您是否考虑过根据日期范围对表格进行分区?如果您的查询中包含日期范围&amp;表是分区的,可以避免全表扫描。您也可以对索引进行分区。

http://dev.mysql.com/doc/refman/5.1/en/partitioning-overview.html

http://dev.mysql.com/tech-resources/articles/mysql_5.1_partitions.html