优化具有数亿行的表的查询

时间:2012-10-23 13:22:09

标签: mysql query-optimization

这感觉就像是为我做的功课"有点问题,但我真的卡在这里试图使这个查询快速运行对很多行的表。显示架构的Here's a SQLFiddle(或多或少)。

我已经使用了索引,试图获得能显示所有必需列的内容,但却没有取得多大成功。这是create

CREATE TABLE `AuditEvent` (
    `auditEventId` bigint(20) NOT NULL AUTO_INCREMENT,
    `eventTime` datetime NOT NULL,
    `target1Id` int(11) DEFAULT NULL,
    `target1Name` varchar(100) DEFAULT NULL,
    `target2Id` int(11) DEFAULT NULL,
    `target2Name` varchar(100) DEFAULT NULL,
    `clientId` int(11) NOT NULL DEFAULT '1',
    `type` int(11) not null,
    PRIMARY KEY (`auditEventId`),
    KEY `Transactions` (`clientId`,`eventTime`,`target1Id`,`type`),
    KEY `TransactionsJoin` (`auditEventId`, `clientId`,`eventTime`,`target1Id`,`type`)
)

select的一个版本:

select ae.target1Id, ae.type, count(*)
from AuditEvent ae
where ae.clientId=4
    and (ae.eventTime between '2011-09-01 03:00:00' and '2012-09-30 23:57:00')
group by ae.target1Id, ae.type;

我最终得到了一个'使用临时'和'使用filesort'同样。我尝试删除count(*)并使用select distinct代替,但这并不会导致“使用文件传输”。如果有join方法可以获得计数,那么这可能没问题。

最初,决定跟踪目标的target1Name和target2Name,因为它们在创建审计记录时存在。我也需要这些名字(最近会这样做)。

目前,查询(上面缺少target1Name和target2Name列)在大约4秒内运行~2400万条记录。我们的目标是数亿,我们希望继续按照这些方式执行查询(希望将其保持在1-2分钟之内,但我们希望它能更好),但是我担心的是,一旦我们击中它赢得的大量数据(正在进行模拟其他行的工作)。

我不确定获取其他字段的最佳策略。如果我将这些列直接添加到select中,我会丢失“使用索引”#39;在查询上。我尝试了一个join回到表格中,这样可以保留'使用索引'但需要大约20秒。

我确实尝试将eventTime列更改为int而不是datetime,但这似乎不会影响索引的使用或时间。

1 个答案:

答案 0 :(得分:4)

正如您可能理解的那样,这里的问题是范围条件ae.eventTime between '2011-09-01 03:00:00' and '2012-09-30 23:57:00'(它一直如此)打破了Transactions索引的有效使用(即索引实际上仅用于clientId } equation和范围条件的第一部分,索引不用于分组)。

通常,解决方案是使用等式检查替换范围条件(在您的情况下,将period列,组eventTime引入句点并将BETWEEN子句替换为一个period IN (1,2,3,4,5))。但这可能成为你桌子的开销。

您可能尝试的另一个解决方案是添加另一个索引(如果不再使用,可能会替换Transactions):(clientId, target1Id, type, eventTime),并使用以下查询:

SELECT
  ae.target1Id,
  ae.type,
  COUNT(
    NULLIF(ae.eventTime BETWEEN '2011-09-01 03:00:00' 
                            AND '2012-09-30 23:57:00', 0)
  ) as cnt,
FROM AuditEvent ae
WHERE ae.clientId=4
GROUP BY ae.target1Id, ae.type;

这样,你将a)将范围条件移动到最后,b)允许使用索引进行分组,c)使索引成为查询的覆盖索引(即查询不需要磁盘IO操作)

<强> UPD1: 对不起,yesteday我没有仔细阅读你的帖子,也没有注意到你的问题是要检索target1Nametarget2Name。首先,我不确定您是否正确理解Using index的含义。缺少Using index并不意味着没有索引用于查询,Using index意味着索引本身包含足够的数据来执行子查询(即索引覆盖)。由于target1Nametarget2Name未包含在任何索引中,因此获取它们的子查询将不会有Using index

如果您质疑如何将这两个字段添加到您的查询中(您认为足够快),请尝试以下操作:

SELECT a1.target1Id, a1.type, cnt, target1Name, target2Name
FROM (
  select ae.target1Id, ae.type, count(*) as cnt, MAX(auditEventId) as max_id
  from AuditEvent ae
  where ae.clientId=4
      and (ae.eventTime between '2011-09-01 03:00:00' and '2012-09-30 23:57:00')
  group by ae.target1Id, ae.type) as a1
JOIN AuditEvent a2 ON a1.max_id = a2.auditEventId
;